summaryrefslogtreecommitdiffstats
path: root/drivers/input/serio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/input/serio
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/input/serio')
-rw-r--r--drivers/input/serio/Kconfig321
-rw-r--r--drivers/input/serio/Makefile35
-rw-r--r--drivers/input/serio/altera_ps2.c164
-rw-r--r--drivers/input/serio/ambakmi.c210
-rw-r--r--drivers/input/serio/ams_delta_serio.c192
-rw-r--r--drivers/input/serio/apbps2.c222
-rw-r--r--drivers/input/serio/arc_ps2.c274
-rw-r--r--drivers/input/serio/ct82c710.c241
-rw-r--r--drivers/input/serio/gscps2.c465
-rw-r--r--drivers/input/serio/hil_mlc.c1025
-rw-r--r--drivers/input/serio/hp_sdc.c1127
-rw-r--r--drivers/input/serio/hp_sdc_mlc.c355
-rw-r--r--drivers/input/serio/hyperv-keyboard.c442
-rw-r--r--drivers/input/serio/i8042-acpipnpio.h1740
-rw-r--r--drivers/input/serio/i8042-io.h89
-rw-r--r--drivers/input/serio/i8042-ip22io.h72
-rw-r--r--drivers/input/serio/i8042-jazzio.h65
-rw-r--r--drivers/input/serio/i8042-snirm.h71
-rw-r--r--drivers/input/serio/i8042-sparcio.h168
-rw-r--r--drivers/input/serio/i8042.c1671
-rw-r--r--drivers/input/serio/i8042.h91
-rw-r--r--drivers/input/serio/ioc3kbd.c216
-rw-r--r--drivers/input/serio/libps2.c494
-rw-r--r--drivers/input/serio/maceps2.c206
-rw-r--r--drivers/input/serio/olpc_apsp.c272
-rw-r--r--drivers/input/serio/parkbd.c223
-rw-r--r--drivers/input/serio/pcips2.c217
-rw-r--r--drivers/input/serio/ps2-gpio.c507
-rw-r--r--drivers/input/serio/ps2mult.c304
-rw-r--r--drivers/input/serio/q40kbd.c174
-rw-r--r--drivers/input/serio/rpckbd.c154
-rw-r--r--drivers/input/serio/sa1111ps2.c386
-rw-r--r--drivers/input/serio/serio.c1049
-rw-r--r--drivers/input/serio/serio_raw.c441
-rw-r--r--drivers/input/serio/serport.c309
-rw-r--r--drivers/input/serio/sun4i-ps2.c338
-rw-r--r--drivers/input/serio/userio.c285
-rw-r--r--drivers/input/serio/xilinx_ps2.c371
38 files changed, 14986 insertions, 0 deletions
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
new file mode 100644
index 000000000..f39b7b3f7
--- /dev/null
+++ b/drivers/input/serio/Kconfig
@@ -0,0 +1,321 @@
+# 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_CT82C710
+ tristate "ct82c710 Aux port controller"
+ depends on X86
+ help
+ Say Y here if you have a Texas Instruments TravelMate notebook
+ equipped with the ct82c710 chip and want to use a mouse connected
+ to the "QuickPort".
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ct82c710.
+
+config SERIO_Q40KBD
+ tristate "Q40 keyboard controller"
+ depends on Q40
+
+config SERIO_PARKBD
+ tristate "Parallel port keyboard adapter"
+ depends on PARPORT
+ help
+ Say Y here if you built a simple parallel port adapter to attach
+ an additional AT keyboard, XT keyboard or PS/2 mouse.
+
+ More information is available: <file:Documentation/input/input.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
+ 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_XILINX_XPS_PS2
+ tristate "Xilinx XPS PS/2 Controller Support"
+ depends on PPC || MICROBLAZE
+ help
+ This driver supports XPS PS/2 IP from the Xilinx EDK on
+ PowerPC platform.
+
+ To compile this driver as a module, choose M here: the
+ module will be called xilinx_ps2.
+
+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
+ default HYPERV
+ 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
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
new file mode 100644
index 000000000..6d97bad7b
--- /dev/null
+++ b/drivers/input/serio/Makefile
@@ -0,0 +1,35 @@
+# 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_CT82C710) += ct82c710.o
+obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
+obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
+obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
+obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
+obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
+obj-$(CONFIG_HP_SDC) += hp_sdc.o
+obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o
+obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o
+obj-$(CONFIG_SERIO_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_XILINX_XPS_PS2) += xilinx_ps2.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
diff --git a/drivers/input/serio/altera_ps2.c b/drivers/input/serio/altera_ps2.c
new file mode 100644
index 000000000..3a92304f6
--- /dev/null
+++ b/drivers/input/serio/altera_ps2.c
@@ -0,0 +1,164 @@
+// 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 resource *res;
+ struct serio *serio;
+ int error, irq;
+
+ ps2if = devm_kzalloc(&pdev->dev, sizeof(struct ps2if), GFP_KERNEL);
+ if (!ps2if)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ps2if->base = devm_ioremap_resource(&pdev->dev, res);
+ 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(sizeof(struct serio), GFP_KERNEL);
+ 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 int altera_ps2_remove(struct platform_device *pdev)
+{
+ struct ps2if *ps2if = platform_get_drvdata(pdev);
+
+ serio_unregister_port(ps2if->io);
+
+ return 0;
+}
+
+#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);
diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c
new file mode 100644
index 000000000..c391700fc
--- /dev/null
+++ b/drivers/input/serio/ambakmi.c
@@ -0,0 +1,210 @@
+// 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(sizeof(struct amba_kmi_port), GFP_KERNEL);
+ io = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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 __maybe_unused 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 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",
+ .owner = THIS_MODULE,
+ .pm = &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");
diff --git a/drivers/input/serio/ams_delta_serio.c b/drivers/input/serio/ams_delta_serio.c
new file mode 100644
index 000000000..ec93cb457
--- /dev/null
+++ b/drivers/input/serio/ams_delta_serio.c
@@ -0,0 +1,192 @@
+// 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(sizeof(*serio), GFP_KERNEL);
+ 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 int ams_delta_serio_exit(struct platform_device *pdev)
+{
+ struct ams_delta_serio *priv = platform_get_drvdata(pdev);
+
+ serio_unregister_port(priv->serio);
+
+ return 0;
+}
+
+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);
diff --git a/drivers/input/serio/apbps2.c b/drivers/input/serio/apbps2.c
new file mode 100644
index 000000000..9c9ce097f
--- /dev/null
+++ b/drivers/input/serio/apbps2.c
@@ -0,0 +1,222 @@
+// 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/of_device.h>
+#include <linux/module.h>
+#include <linux/serio.h>
+#include <linux/errno.h>
+#include <linux/interrupt.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 reciever and it's 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;
+ struct resource *res;
+
+ priv = devm_kzalloc(&ofdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&ofdev->dev, "memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ /* Find Device Address */
+ res = platform_get_resource(ofdev, IORESOURCE_MEM, 0);
+ priv->regs = devm_ioremap_resource(&ofdev->dev, res);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ /* Reset hardware, disable interrupt */
+ iowrite32be(0, &priv->regs->ctrl);
+
+ /* IRQ */
+ irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
+ 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(sizeof(struct serio), GFP_KERNEL);
+ 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 int apbps2_of_remove(struct platform_device *of_dev)
+{
+ struct apbps2_priv *priv = platform_get_drvdata(of_dev);
+
+ serio_unregister_port(priv->io);
+
+ return 0;
+}
+
+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");
diff --git a/drivers/input/serio/arc_ps2.c b/drivers/input/serio/arc_ps2.c
new file mode 100644
index 000000000..0af9fba5d
--- /dev/null
+++ b/drivers/input/serio/arc_ps2.c
@@ -0,0 +1,274 @@
+// 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(sizeof(struct serio), GFP_KERNEL);
+ 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;
+ struct resource *res;
+ 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(struct arc_ps2_data),
+ GFP_KERNEL);
+ if (!arc_ps2) {
+ dev_err(&pdev->dev, "out of memory\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ arc_ps2->addr = devm_ioremap_resource(&pdev->dev, res);
+ 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 int 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);
+
+ return 0;
+}
+
+#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");
diff --git a/drivers/input/serio/ct82c710.c b/drivers/input/serio/ct82c710.c
new file mode 100644
index 000000000..3da751f4a
--- /dev/null
+++ b/drivers/input/serio/ct82c710.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 1999-2001 Vojtech Pavlik
+ */
+
+/*
+ * 82C710 C&T mouse port chip driver for Linux
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/serio.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("82C710 C&T mouse port chip driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * ct82c710 interface
+ */
+
+#define CT82C710_DEV_IDLE 0x01 /* Device Idle */
+#define CT82C710_RX_FULL 0x02 /* Device Char received */
+#define CT82C710_TX_IDLE 0x04 /* Device XMIT Idle */
+#define CT82C710_RESET 0x08 /* Device Reset */
+#define CT82C710_INTS_ON 0x10 /* Device Interrupt On */
+#define CT82C710_ERROR_FLAG 0x20 /* Device Error */
+#define CT82C710_CLEAR 0x40 /* Device Clear */
+#define CT82C710_ENABLE 0x80 /* Device Enable */
+
+#define CT82C710_IRQ 12
+
+#define CT82C710_DATA ct82c710_iores.start
+#define CT82C710_STATUS (ct82c710_iores.start + 1)
+
+static struct serio *ct82c710_port;
+static struct platform_device *ct82c710_device;
+static struct resource ct82c710_iores;
+
+/*
+ * Interrupt handler for the 82C710 mouse port. A character
+ * is waiting in the 82C710.
+ */
+
+static irqreturn_t ct82c710_interrupt(int cpl, void *dev_id)
+{
+ return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0);
+}
+
+/*
+ * Wait for device to send output char and flush any input char.
+ */
+
+static int ct82c170_wait(void)
+{
+ int timeout = 60000;
+
+ while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE))
+ != (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) {
+
+ if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA);
+
+ udelay(1);
+ timeout--;
+ }
+
+ return !timeout;
+}
+
+static void ct82c710_close(struct serio *serio)
+{
+ if (ct82c170_wait())
+ printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
+
+ outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS);
+
+ if (ct82c170_wait())
+ printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
+
+ free_irq(CT82C710_IRQ, NULL);
+}
+
+static int ct82c710_open(struct serio *serio)
+{
+ unsigned char status;
+ int err;
+
+ err = request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL);
+ if (err)
+ return err;
+
+ status = inb_p(CT82C710_STATUS);
+
+ status |= (CT82C710_ENABLE | CT82C710_RESET);
+ outb_p(status, CT82C710_STATUS);
+
+ status &= ~(CT82C710_RESET);
+ outb_p(status, CT82C710_STATUS);
+
+ status |= CT82C710_INTS_ON;
+ outb_p(status, CT82C710_STATUS); /* Enable interrupts */
+
+ while (ct82c170_wait()) {
+ printk(KERN_ERR "ct82c710: Device busy in open()\n");
+ status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON);
+ outb_p(status, CT82C710_STATUS);
+ free_irq(CT82C710_IRQ, NULL);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/*
+ * Write to the 82C710 mouse device.
+ */
+
+static int ct82c710_write(struct serio *port, unsigned char c)
+{
+ if (ct82c170_wait()) return -1;
+ outb_p(c, CT82C710_DATA);
+ return 0;
+}
+
+/*
+ * See if we can find a 82C710 device. Read mouse address.
+ */
+
+static int __init ct82c710_detect(void)
+{
+ outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */
+ outb_p(0xaa, 0x3fa); /* Inverse of 55 */
+ outb_p(0x36, 0x3fa); /* Address the chip */
+ outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */
+ outb_p(0x1b, 0x2fa); /* Inverse of e4 */
+ outb_p(0x0f, 0x390); /* Write index */
+ if (inb_p(0x391) != 0xe4) /* Config address found? */
+ return -ENODEV; /* No: no 82C710 here */
+
+ outb_p(0x0d, 0x390); /* Write index */
+ ct82c710_iores.start = inb_p(0x391) << 2; /* Get mouse I/O address */
+ ct82c710_iores.end = ct82c710_iores.start + 1;
+ ct82c710_iores.flags = IORESOURCE_IO;
+ outb_p(0x0f, 0x390);
+ outb_p(0x0f, 0x391); /* Close config mode */
+
+ return 0;
+}
+
+static int ct82c710_probe(struct platform_device *dev)
+{
+ ct82c710_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!ct82c710_port)
+ return -ENOMEM;
+
+ ct82c710_port->id.type = SERIO_8042;
+ ct82c710_port->dev.parent = &dev->dev;
+ ct82c710_port->open = ct82c710_open;
+ ct82c710_port->close = ct82c710_close;
+ ct82c710_port->write = ct82c710_write;
+ strscpy(ct82c710_port->name, "C&T 82c710 mouse port",
+ sizeof(ct82c710_port->name));
+ snprintf(ct82c710_port->phys, sizeof(ct82c710_port->phys),
+ "isa%16llx/serio0", (unsigned long long)CT82C710_DATA);
+
+ serio_register_port(ct82c710_port);
+
+ printk(KERN_INFO "serio: C&T 82c710 mouse port at %#llx irq %d\n",
+ (unsigned long long)CT82C710_DATA, CT82C710_IRQ);
+
+ return 0;
+}
+
+static int ct82c710_remove(struct platform_device *dev)
+{
+ serio_unregister_port(ct82c710_port);
+
+ return 0;
+}
+
+static struct platform_driver ct82c710_driver = {
+ .driver = {
+ .name = "ct82c710",
+ },
+ .probe = ct82c710_probe,
+ .remove = ct82c710_remove,
+};
+
+
+static int __init ct82c710_init(void)
+{
+ int error;
+
+ error = ct82c710_detect();
+ if (error)
+ return error;
+
+ error = platform_driver_register(&ct82c710_driver);
+ if (error)
+ return error;
+
+ ct82c710_device = platform_device_alloc("ct82c710", -1);
+ if (!ct82c710_device) {
+ error = -ENOMEM;
+ goto err_unregister_driver;
+ }
+
+ error = platform_device_add_resources(ct82c710_device, &ct82c710_iores, 1);
+ if (error)
+ goto err_free_device;
+
+ error = platform_device_add(ct82c710_device);
+ if (error)
+ goto err_free_device;
+
+ return 0;
+
+ err_free_device:
+ platform_device_put(ct82c710_device);
+ err_unregister_driver:
+ platform_driver_unregister(&ct82c710_driver);
+ return error;
+}
+
+static void __exit ct82c710_exit(void)
+{
+ platform_device_unregister(ct82c710_device);
+ platform_driver_unregister(&ct82c710_driver);
+}
+
+module_init(ct82c710_init);
+module_exit(ct82c710_exit);
diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c
new file mode 100644
index 000000000..633c7de49
--- /dev/null
+++ b/drivers/input/serio/gscps2.c
@@ -0,0 +1,465 @@
+/*
+ * drivers/input/serio/gscps2.c
+ *
+ * Copyright (c) 2004-2006 Helge Deller <deller@gmx.de>
+ * Copyright (c) 2002 Laurent Canet <canetl@esiee.fr>
+ * Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org>
+ *
+ * Pieces of code based on linux-2.4's hp_mouse.c & hp_keyb.c
+ * Copyright (c) 1999 Alex deVries <alex@onefishtwo.ca>
+ * Copyright (c) 1999-2000 Philipp Rumpf <prumpf@tux.org>
+ * Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr>
+ * Copyright (c) 2000-2001 Thomas Marteau <marteaut@esiee.fr>
+ *
+ * HP GSC PS/2 port driver, found in PA/RISC Workstations
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * TODO:
+ * - Dino testing (did HP ever shipped a machine on which this port
+ * was usable/enabled ?)
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/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)
+{
+ unsigned long flags;
+ 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 */;
+
+ spin_lock_irqsave(&ps2port->lock, flags);
+ writeb(data, addr+GSC_XMTDATA);
+ spin_unlock_irqrestore(&ps2port->lock, flags);
+
+ /* this is ugly, but due to timing of the port it seems to be necessary. */
+ mdelay(6);
+
+ /* make sure any received data is returned as fast as possible */
+ /* this is important e.g. when we set the LEDs on the keyboard */
+ gscps2_interrupt(0, NULL);
+
+ return 1;
+}
+
+
+/*
+ * gscps2_enable() - enables or disables the port
+ */
+
+static void gscps2_enable(struct gscps2port *ps2port, int enable)
+{
+ unsigned long flags;
+ u8 data;
+
+ /* now enable/disable the port */
+ spin_lock_irqsave(&ps2port->lock, flags);
+ gscps2_flush(ps2port);
+ data = gscps2_readb_control(ps2port->addr);
+ if (enable)
+ data |= GSC_CTRL_ENBL;
+ else
+ data &= ~GSC_CTRL_ENBL;
+ gscps2_writeb_control(data, ps2port->addr);
+ spin_unlock_irqrestore(&ps2port->lock, flags);
+ wait_TBE(ps2port->addr);
+ gscps2_flush(ps2port);
+}
+
+/*
+ * gscps2_reset() - resets the PS/2 port
+ */
+
+static void gscps2_reset(struct gscps2port *ps2port)
+{
+ unsigned long flags;
+
+ /* reset the interface */
+ spin_lock_irqsave(&ps2port->lock, flags);
+ gscps2_flush(ps2port);
+ writeb(0xff, ps2port->addr + GSC_RESET);
+ gscps2_flush(ps2port);
+ spin_unlock_irqrestore(&ps2port->lock, flags);
+}
+
+static LIST_HEAD(ps2port_list);
+
+/**
+ * gscps2_interrupt() - Interruption service routine
+ *
+ * This function reads received PS/2 bytes and processes them on
+ * all interfaces.
+ * The problematic part here is, that the keyboard and mouse PS/2 port
+ * share the same interrupt and it's not possible to send data if any
+ * one of them holds input data. To solve this problem we try to receive
+ * the data as fast as possible and handle the reporting to the upper layer
+ * later.
+ */
+
+static irqreturn_t gscps2_interrupt(int irq, void *dev)
+{
+ struct gscps2port *ps2port;
+
+ list_for_each_entry(ps2port, &ps2port_list, node) {
+
+ unsigned long flags;
+ spin_lock_irqsave(&ps2port->lock, flags);
+
+ while ( (ps2port->buffer[ps2port->append].str =
+ gscps2_readb_status(ps2port->addr)) & GSC_STAT_RBNE ) {
+ ps2port->buffer[ps2port->append].data =
+ gscps2_readb_input(ps2port->addr);
+ ps2port->append = ((ps2port->append+1) & BUFFER_SIZE);
+ }
+
+ spin_unlock_irqrestore(&ps2port->lock, flags);
+
+ } /* list_for_each_entry */
+
+ /* all data was read from the ports - now report the data to upper layer */
+
+ list_for_each_entry(ps2port, &ps2port_list, node) {
+
+ while (ps2port->act != ps2port->append) {
+
+ unsigned int rxflags;
+ u8 data, status;
+
+ /* Did new data arrived while we read existing data ?
+ If yes, exit now and let the new irq handler start over again */
+ if (gscps2_readb_status(ps2port->addr) & GSC_STAT_CMPINTR)
+ return IRQ_HANDLED;
+
+ status = ps2port->buffer[ps2port->act].str;
+ data = ps2port->buffer[ps2port->act].data;
+
+ ps2port->act = ((ps2port->act+1) & BUFFER_SIZE);
+ rxflags = ((status & GSC_STAT_TERR) ? SERIO_TIMEOUT : 0 ) |
+ ((status & GSC_STAT_PERR) ? SERIO_PARITY : 0 );
+
+ serio_interrupt(ps2port->port, data, rxflags);
+
+ } /* while() */
+
+ } /* list_for_each_entry */
+
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * gscps2_write() - send a byte out through the aux interface.
+ */
+
+static int gscps2_write(struct serio *port, unsigned char data)
+{
+ struct gscps2port *ps2port = port->port_data;
+
+ if (!gscps2_writeb_output(ps2port, data)) {
+ printk(KERN_DEBUG PFX "sending byte %#x failed.\n", data);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * gscps2_open() is called when a port is opened by the higher layer.
+ * It resets and enables the port.
+ */
+
+static int gscps2_open(struct serio *port)
+{
+ struct gscps2port *ps2port = port->port_data;
+
+ gscps2_reset(ps2port);
+
+ /* enable it */
+ gscps2_enable(ps2port, ENABLE);
+
+ gscps2_interrupt(0, NULL);
+
+ return 0;
+}
+
+/*
+ * gscps2_close() disables the port
+ */
+
+static void gscps2_close(struct serio *port)
+{
+ struct gscps2port *ps2port = port->port_data;
+ gscps2_enable(ps2port, DISABLE);
+}
+
+/**
+ * gscps2_probe() - Probes PS2 devices
+ * @return: success/error report
+ */
+
+static int __init gscps2_probe(struct parisc_device *dev)
+{
+ struct gscps2port *ps2port;
+ struct serio *serio;
+ unsigned long hpa = dev->hpa.start;
+ int ret;
+
+ if (!dev->irq)
+ return -ENODEV;
+
+ /* Offset for DINO PS/2. Works with LASI even */
+ if (dev->id.sversion == 0x96)
+ hpa += GSC_DINO_OFFSET;
+
+ ps2port = kzalloc(sizeof(struct gscps2port), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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
+ * @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);
+
diff --git a/drivers/input/serio/hil_mlc.c b/drivers/input/serio/hil_mlc.c
new file mode 100644
index 000000000..d36e89d6f
--- /dev/null
+++ b/drivers/input/serio/hil_mlc.c
@@ -0,0 +1,1025 @@
+/*
+ * HIL MLC state machine and serio interface driver
+ *
+ * Copyright (c) 2001 Brian S. Julin
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ *
+ * References:
+ * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A
+ *
+ *
+ * Driver theory of operation:
+ *
+ * Some access methods and an ISR is defined by the sub-driver
+ * (e.g. hp_sdc_mlc.c). These methods are expected to provide a
+ * few bits of logic in addition to raw access to the HIL MLC,
+ * specifically, the ISR, which is entirely registered by the
+ * sub-driver and invoked directly, must check for record
+ * termination or packet match, at which point a semaphore must
+ * be cleared and then the hil_mlcs_tasklet must be scheduled.
+ *
+ * The hil_mlcs_tasklet processes the state machine for all MLCs
+ * each time it runs, checking each MLC's progress at the current
+ * node in the state machine, and moving the MLC to subsequent nodes
+ * in the state machine when appropriate. It will reschedule
+ * itself if output is pending. (This rescheduling should be replaced
+ * at some point with a sub-driver-specific mechanism.)
+ *
+ * A timer task prods the tasklet once per second to prevent
+ * hangups when attached devices do not return expected data
+ * and to initiate probes of the loop for new devices.
+ */
+
+#include <linux/hil_mlc.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+
+MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
+MODULE_DESCRIPTION("HIL MLC serio");
+MODULE_LICENSE("Dual BSD/GPL");
+
+EXPORT_SYMBOL(hil_mlc_register);
+EXPORT_SYMBOL(hil_mlc_unregister);
+
+#define PREFIX "HIL MLC: "
+
+static LIST_HEAD(hil_mlcs);
+static DEFINE_RWLOCK(hil_mlcs_lock);
+static struct timer_list hil_mlcs_kicker;
+static int hil_mlcs_probe, hil_mlc_stop;
+
+static void hil_mlcs_process(unsigned long unused);
+static DECLARE_TASKLET_DISABLED_OLD(hil_mlcs_tasklet, hil_mlcs_process);
+
+
+/* #define HIL_MLC_DEBUG */
+
+/********************** Device info/instance management **********************/
+
+static void hil_mlc_clear_di_map(hil_mlc *mlc, int val)
+{
+ int j;
+
+ for (j = val; j < 7 ; j++)
+ mlc->di_map[j] = -1;
+}
+
+static void hil_mlc_clear_di_scratch(hil_mlc *mlc)
+{
+ memset(&mlc->di_scratch, 0, sizeof(mlc->di_scratch));
+}
+
+static void hil_mlc_copy_di_scratch(hil_mlc *mlc, int idx)
+{
+ memcpy(&mlc->di[idx], &mlc->di_scratch, sizeof(mlc->di_scratch));
+}
+
+static int hil_mlc_match_di_scratch(hil_mlc *mlc)
+{
+ int idx;
+
+ for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
+ int j, found = 0;
+
+ /* In-use slots are not eligible. */
+ for (j = 0; j < 7 ; j++)
+ if (mlc->di_map[j] == idx)
+ found++;
+
+ if (found)
+ continue;
+
+ if (!memcmp(mlc->di + idx, &mlc->di_scratch,
+ sizeof(mlc->di_scratch)))
+ break;
+ }
+ return idx >= HIL_MLC_DEVMEM ? -1 : idx;
+}
+
+static int hil_mlc_find_free_di(hil_mlc *mlc)
+{
+ int idx;
+
+ /* TODO: Pick all-zero slots first, failing that,
+ * randomize the slot picked among those eligible.
+ */
+ for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
+ int j, found = 0;
+
+ for (j = 0; j < 7 ; j++)
+ if (mlc->di_map[j] == idx)
+ found++;
+
+ if (!found)
+ break;
+ }
+
+ return idx; /* Note: It is guaranteed at least one above will match */
+}
+
+static inline void hil_mlc_clean_serio_map(hil_mlc *mlc)
+{
+ int idx;
+
+ for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
+ int j, found = 0;
+
+ for (j = 0; j < 7 ; j++)
+ if (mlc->di_map[j] == idx)
+ found++;
+
+ if (!found)
+ mlc->serio_map[idx].di_revmap = -1;
+ }
+}
+
+static void hil_mlc_send_polls(hil_mlc *mlc)
+{
+ int did, i, cnt;
+ struct serio *serio;
+ struct serio_driver *drv;
+
+ i = cnt = 0;
+ did = (mlc->ipacket[0] & HIL_PKT_ADDR_MASK) >> 8;
+ serio = did ? mlc->serio[mlc->di_map[did - 1]] : NULL;
+ drv = (serio != NULL) ? serio->drv : NULL;
+
+ while (mlc->icount < 15 - i) {
+ hil_packet p;
+
+ p = mlc->ipacket[i];
+ if (did != (p & HIL_PKT_ADDR_MASK) >> 8) {
+ if (drv && drv->interrupt) {
+ drv->interrupt(serio, 0, 0);
+ drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
+ drv->interrupt(serio, HIL_PKT_CMD >> 8, 0);
+ drv->interrupt(serio, HIL_CMD_POL + cnt, 0);
+ }
+
+ did = (p & HIL_PKT_ADDR_MASK) >> 8;
+ serio = did ? mlc->serio[mlc->di_map[did-1]] : NULL;
+ drv = (serio != NULL) ? serio->drv : NULL;
+ cnt = 0;
+ }
+
+ cnt++;
+ i++;
+
+ if (drv && drv->interrupt) {
+ drv->interrupt(serio, (p >> 24), 0);
+ drv->interrupt(serio, (p >> 16) & 0xff, 0);
+ drv->interrupt(serio, (p >> 8) & ~HIL_PKT_ADDR_MASK, 0);
+ drv->interrupt(serio, p & 0xff, 0);
+ }
+ }
+}
+
+/*************************** State engine *********************************/
+
+#define HILSEN_SCHED 0x000100 /* Schedule the tasklet */
+#define HILSEN_BREAK 0x000200 /* Wait until next pass */
+#define HILSEN_UP 0x000400 /* relative node#, decrement */
+#define HILSEN_DOWN 0x000800 /* relative node#, increment */
+#define HILSEN_FOLLOW 0x001000 /* use retval as next node# */
+
+#define HILSEN_MASK 0x0000ff
+#define HILSEN_START 0
+#define HILSEN_RESTART 1
+#define HILSEN_DHR 9
+#define HILSEN_DHR2 10
+#define HILSEN_IFC 14
+#define HILSEN_HEAL0 16
+#define HILSEN_HEAL 18
+#define HILSEN_ACF 21
+#define HILSEN_ACF2 22
+#define HILSEN_DISC0 25
+#define HILSEN_DISC 27
+#define HILSEN_MATCH 40
+#define HILSEN_OPERATE 41
+#define HILSEN_PROBE 44
+#define HILSEN_DSR 52
+#define HILSEN_REPOLL 55
+#define HILSEN_IFCACF 58
+#define HILSEN_END 60
+
+#define HILSEN_NEXT (HILSEN_DOWN | 1)
+#define HILSEN_SAME (HILSEN_DOWN | 0)
+#define HILSEN_LAST (HILSEN_UP | 1)
+
+#define HILSEN_DOZE (HILSEN_SAME | HILSEN_SCHED | HILSEN_BREAK)
+#define HILSEN_SLEEP (HILSEN_SAME | HILSEN_BREAK)
+
+static int hilse_match(hil_mlc *mlc, int unused)
+{
+ int rc;
+
+ rc = hil_mlc_match_di_scratch(mlc);
+ if (rc == -1) {
+ rc = hil_mlc_find_free_di(mlc);
+ if (rc == -1)
+ goto err;
+
+#ifdef HIL_MLC_DEBUG
+ printk(KERN_DEBUG PREFIX "new in slot %i\n", rc);
+#endif
+ hil_mlc_copy_di_scratch(mlc, rc);
+ mlc->di_map[mlc->ddi] = rc;
+ mlc->serio_map[rc].di_revmap = mlc->ddi;
+ hil_mlc_clean_serio_map(mlc);
+ serio_rescan(mlc->serio[rc]);
+ return -1;
+ }
+
+ mlc->di_map[mlc->ddi] = rc;
+#ifdef HIL_MLC_DEBUG
+ printk(KERN_DEBUG PREFIX "same in slot %i\n", rc);
+#endif
+ mlc->serio_map[rc].di_revmap = mlc->ddi;
+ hil_mlc_clean_serio_map(mlc);
+ return 0;
+
+ err:
+ printk(KERN_ERR PREFIX "Residual device slots exhausted, close some serios!\n");
+ return 1;
+}
+
+/* An LCV used to prevent runaway loops, forces 5 second sleep when reset. */
+static int hilse_init_lcv(hil_mlc *mlc, int unused)
+{
+ time64_t now = ktime_get_seconds();
+
+ if (mlc->lcv && (now - mlc->lcv_time) < 5)
+ return -1;
+
+ mlc->lcv_time = now;
+ mlc->lcv = 0;
+
+ return 0;
+}
+
+static int hilse_inc_lcv(hil_mlc *mlc, int lim)
+{
+ return mlc->lcv++ >= lim ? -1 : 0;
+}
+
+#if 0
+static int hilse_set_lcv(hil_mlc *mlc, int val)
+{
+ mlc->lcv = val;
+
+ return 0;
+}
+#endif
+
+/* Management of the discovered device index (zero based, -1 means no devs) */
+static int hilse_set_ddi(hil_mlc *mlc, int val)
+{
+ mlc->ddi = val;
+ hil_mlc_clear_di_map(mlc, val + 1);
+
+ return 0;
+}
+
+static int hilse_dec_ddi(hil_mlc *mlc, int unused)
+{
+ mlc->ddi--;
+ if (mlc->ddi <= -1) {
+ mlc->ddi = -1;
+ hil_mlc_clear_di_map(mlc, 0);
+ return -1;
+ }
+ hil_mlc_clear_di_map(mlc, mlc->ddi + 1);
+
+ return 0;
+}
+
+static int hilse_inc_ddi(hil_mlc *mlc, int unused)
+{
+ BUG_ON(mlc->ddi >= 6);
+ mlc->ddi++;
+
+ return 0;
+}
+
+static int hilse_take_idd(hil_mlc *mlc, int unused)
+{
+ int i;
+
+ /* Help the state engine:
+ * Is this a real IDD response or just an echo?
+ *
+ * Real IDD response does not start with a command.
+ */
+ if (mlc->ipacket[0] & HIL_PKT_CMD)
+ goto bail;
+
+ /* Should have the command echoed further down. */
+ for (i = 1; i < 16; i++) {
+ if (((mlc->ipacket[i] & HIL_PKT_ADDR_MASK) ==
+ (mlc->ipacket[0] & HIL_PKT_ADDR_MASK)) &&
+ (mlc->ipacket[i] & HIL_PKT_CMD) &&
+ ((mlc->ipacket[i] & HIL_PKT_DATA_MASK) == HIL_CMD_IDD))
+ break;
+ }
+ if (i > 15)
+ goto bail;
+
+ /* And the rest of the packets should still be clear. */
+ while (++i < 16)
+ if (mlc->ipacket[i])
+ break;
+
+ if (i < 16)
+ goto bail;
+
+ for (i = 0; i < 16; i++)
+ mlc->di_scratch.idd[i] =
+ mlc->ipacket[i] & HIL_PKT_DATA_MASK;
+
+ /* Next step is to see if RSC supported */
+ if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_RSC)
+ return HILSEN_NEXT;
+
+ if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD)
+ return HILSEN_DOWN | 4;
+
+ return 0;
+
+ bail:
+ mlc->ddi--;
+
+ return -1; /* This should send us off to ACF */
+}
+
+static int hilse_take_rsc(hil_mlc *mlc, int unused)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ mlc->di_scratch.rsc[i] =
+ mlc->ipacket[i] & HIL_PKT_DATA_MASK;
+
+ /* Next step is to see if EXD supported (IDD has already been read) */
+ if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD)
+ return HILSEN_NEXT;
+
+ return 0;
+}
+
+static int hilse_take_exd(hil_mlc *mlc, int unused)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ mlc->di_scratch.exd[i] =
+ mlc->ipacket[i] & HIL_PKT_DATA_MASK;
+
+ /* Next step is to see if RNM supported. */
+ if (mlc->di_scratch.exd[0] & HIL_EXD_HEADER_RNM)
+ return HILSEN_NEXT;
+
+ return 0;
+}
+
+static int hilse_take_rnm(hil_mlc *mlc, int unused)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ mlc->di_scratch.rnm[i] =
+ mlc->ipacket[i] & HIL_PKT_DATA_MASK;
+
+ printk(KERN_INFO PREFIX "Device name gotten: %16s\n",
+ mlc->di_scratch.rnm);
+
+ return 0;
+}
+
+static int hilse_operate(hil_mlc *mlc, int repoll)
+{
+
+ if (mlc->opercnt == 0)
+ hil_mlcs_probe = 0;
+ mlc->opercnt = 1;
+
+ hil_mlc_send_polls(mlc);
+
+ if (!hil_mlcs_probe)
+ return 0;
+ hil_mlcs_probe = 0;
+ mlc->opercnt = 0;
+ return 1;
+}
+
+#define FUNC(funct, funct_arg, zero_rc, neg_rc, pos_rc) \
+{ HILSE_FUNC, { .func = funct }, funct_arg, zero_rc, neg_rc, pos_rc },
+#define OUT(pack) \
+{ HILSE_OUT, { .packet = pack }, 0, HILSEN_NEXT, HILSEN_DOZE, 0 },
+#define CTS \
+{ HILSE_CTS, { .packet = 0 }, 0, HILSEN_NEXT | HILSEN_SCHED | HILSEN_BREAK, HILSEN_DOZE, 0 },
+#define EXPECT(comp, to, got, got_wrong, timed_out) \
+{ HILSE_EXPECT, { .packet = comp }, to, got, got_wrong, timed_out },
+#define EXPECT_LAST(comp, to, got, got_wrong, timed_out) \
+{ HILSE_EXPECT_LAST, { .packet = comp }, to, got, got_wrong, timed_out },
+#define EXPECT_DISC(comp, to, got, got_wrong, timed_out) \
+{ HILSE_EXPECT_DISC, { .packet = comp }, to, got, got_wrong, timed_out },
+#define IN(to, got, got_error, timed_out) \
+{ HILSE_IN, { .packet = 0 }, to, got, got_error, timed_out },
+#define OUT_DISC(pack) \
+{ HILSE_OUT_DISC, { .packet = pack }, 0, 0, 0, 0 },
+#define OUT_LAST(pack) \
+{ HILSE_OUT_LAST, { .packet = pack }, 0, 0, 0, 0 },
+
+static const struct hilse_node hil_mlc_se[HILSEN_END] = {
+
+ /* 0 HILSEN_START */
+ FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0)
+
+ /* 1 HILSEN_RESTART */
+ FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
+ OUT(HIL_CTRL_ONLY) /* Disable APE */
+ CTS
+
+#define TEST_PACKET(x) \
+(HIL_PKT_CMD | (x << HIL_PKT_ADDR_SHIFT) | x << 4 | x)
+
+ OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0x5))
+ EXPECT(HIL_ERR_INT | TEST_PACKET(0x5),
+ 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART)
+ OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0xa))
+ EXPECT(HIL_ERR_INT | TEST_PACKET(0xa),
+ 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART)
+ OUT(HIL_CTRL_ONLY | 0) /* Disable test mode */
+
+ /* 9 HILSEN_DHR */
+ FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0)
+
+ /* 10 HILSEN_DHR2 */
+ FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
+ FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0)
+ OUT(HIL_PKT_CMD | HIL_CMD_DHR)
+ IN(300000, HILSEN_DHR2, HILSEN_DHR2, HILSEN_NEXT)
+
+ /* 14 HILSEN_IFC */
+ OUT(HIL_PKT_CMD | HIL_CMD_IFC)
+ EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT,
+ 20000, HILSEN_DISC, HILSEN_DHR2, HILSEN_NEXT )
+
+ /* If devices are there, they weren't in PUP or other loopback mode.
+ * We're more concerned at this point with restoring operation
+ * to devices than discovering new ones, so we try to salvage
+ * the loop configuration by closing off the loop.
+ */
+
+ /* 16 HILSEN_HEAL0 */
+ FUNC(hilse_dec_ddi, 0, HILSEN_NEXT, HILSEN_ACF, 0)
+ FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, 0, 0)
+
+ /* 18 HILSEN_HEAL */
+ OUT_LAST(HIL_CMD_ELB)
+ EXPECT_LAST(HIL_CMD_ELB | HIL_ERR_INT,
+ 20000, HILSEN_REPOLL, HILSEN_DSR, HILSEN_NEXT)
+ FUNC(hilse_dec_ddi, 0, HILSEN_HEAL, HILSEN_NEXT, 0)
+
+ /* 21 HILSEN_ACF */
+ FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_DOZE, 0)
+
+ /* 22 HILSEN_ACF2 */
+ FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0)
+ OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1)
+ IN(20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
+
+ /* 25 HILSEN_DISC0 */
+ OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB)
+ EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_ELB | HIL_ERR_INT,
+ 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
+
+ /* Only enter here if response just received */
+ /* 27 HILSEN_DISC */
+ OUT_DISC(HIL_PKT_CMD | HIL_CMD_IDD)
+ EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_IDD | HIL_ERR_INT,
+ 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_START)
+ FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, HILSEN_START, 0)
+ FUNC(hilse_take_idd, 0, HILSEN_MATCH, HILSEN_IFCACF, HILSEN_FOLLOW)
+ OUT_LAST(HIL_PKT_CMD | HIL_CMD_RSC)
+ EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RSC | HIL_ERR_INT,
+ 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
+ FUNC(hilse_take_rsc, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW)
+ OUT_LAST(HIL_PKT_CMD | HIL_CMD_EXD)
+ EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_EXD | HIL_ERR_INT,
+ 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
+ FUNC(hilse_take_exd, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW)
+ OUT_LAST(HIL_PKT_CMD | HIL_CMD_RNM)
+ EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RNM | HIL_ERR_INT,
+ 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR)
+ FUNC(hilse_take_rnm, 0, HILSEN_MATCH, 0, 0)
+
+ /* 40 HILSEN_MATCH */
+ FUNC(hilse_match, 0, HILSEN_NEXT, HILSEN_NEXT, /* TODO */ 0)
+
+ /* 41 HILSEN_OPERATE */
+ OUT(HIL_PKT_CMD | HIL_CMD_POL)
+ EXPECT(HIL_PKT_CMD | HIL_CMD_POL | HIL_ERR_INT,
+ 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
+ FUNC(hilse_operate, 0, HILSEN_OPERATE, HILSEN_IFC, HILSEN_NEXT)
+
+ /* 44 HILSEN_PROBE */
+ OUT_LAST(HIL_PKT_CMD | HIL_CMD_EPT)
+ IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT)
+ OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB)
+ IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT)
+ OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1)
+ IN(10000, HILSEN_DISC0, HILSEN_DSR, HILSEN_NEXT)
+ OUT_LAST(HIL_PKT_CMD | HIL_CMD_ELB)
+ IN(10000, HILSEN_OPERATE, HILSEN_DSR, HILSEN_DSR)
+
+ /* 52 HILSEN_DSR */
+ FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0)
+ OUT(HIL_PKT_CMD | HIL_CMD_DSR)
+ IN(20000, HILSEN_DHR, HILSEN_DHR, HILSEN_IFC)
+
+ /* 55 HILSEN_REPOLL */
+ OUT(HIL_PKT_CMD | HIL_CMD_RPL)
+ EXPECT(HIL_PKT_CMD | HIL_CMD_RPL | HIL_ERR_INT,
+ 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT)
+ FUNC(hilse_operate, 1, HILSEN_OPERATE, HILSEN_IFC, HILSEN_PROBE)
+
+ /* 58 HILSEN_IFCACF */
+ OUT(HIL_PKT_CMD | HIL_CMD_IFC)
+ EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT,
+ 20000, HILSEN_ACF2, HILSEN_DHR2, HILSEN_HEAL)
+
+ /* 60 HILSEN_END */
+};
+
+static inline void hilse_setup_input(hil_mlc *mlc, const struct hilse_node *node)
+{
+
+ switch (node->act) {
+ case HILSE_EXPECT_DISC:
+ mlc->imatch = node->object.packet;
+ mlc->imatch |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT);
+ break;
+ case HILSE_EXPECT_LAST:
+ mlc->imatch = node->object.packet;
+ mlc->imatch |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT);
+ break;
+ case HILSE_EXPECT:
+ mlc->imatch = node->object.packet;
+ break;
+ case HILSE_IN:
+ mlc->imatch = 0;
+ break;
+ default:
+ BUG();
+ }
+ mlc->istarted = 1;
+ mlc->intimeout = usecs_to_jiffies(node->arg);
+ mlc->instart = jiffies;
+ mlc->icount = 15;
+ memset(mlc->ipacket, 0, 16 * sizeof(hil_packet));
+ BUG_ON(down_trylock(&mlc->isem));
+}
+
+#ifdef HIL_MLC_DEBUG
+static int doze;
+static int seidx; /* For debug */
+#endif
+
+static int hilse_donode(hil_mlc *mlc)
+{
+ const struct hilse_node *node;
+ int nextidx = 0;
+ int sched_long = 0;
+ unsigned long flags;
+
+#ifdef HIL_MLC_DEBUG
+ if (mlc->seidx && mlc->seidx != seidx &&
+ mlc->seidx != 41 && mlc->seidx != 42 && mlc->seidx != 43) {
+ printk(KERN_DEBUG PREFIX "z%i \n {%i}", doze, mlc->seidx);
+ doze = 0;
+ }
+
+ seidx = mlc->seidx;
+#endif
+ node = hil_mlc_se + mlc->seidx;
+
+ switch (node->act) {
+ int rc;
+ hil_packet pack;
+
+ case HILSE_FUNC:
+ BUG_ON(node->object.func == NULL);
+ rc = node->object.func(mlc, node->arg);
+ nextidx = (rc > 0) ? node->ugly :
+ ((rc < 0) ? node->bad : node->good);
+ if (nextidx == HILSEN_FOLLOW)
+ nextidx = rc;
+ break;
+
+ case HILSE_EXPECT_LAST:
+ case HILSE_EXPECT_DISC:
+ case HILSE_EXPECT:
+ case HILSE_IN:
+ /* Already set up from previous HILSE_OUT_* */
+ write_lock_irqsave(&mlc->lock, flags);
+ rc = mlc->in(mlc, node->arg);
+ if (rc == 2) {
+ nextidx = HILSEN_DOZE;
+ sched_long = 1;
+ write_unlock_irqrestore(&mlc->lock, flags);
+ break;
+ }
+ if (rc == 1)
+ nextidx = node->ugly;
+ else if (rc == 0)
+ nextidx = node->good;
+ else
+ nextidx = node->bad;
+ mlc->istarted = 0;
+ write_unlock_irqrestore(&mlc->lock, flags);
+ break;
+
+ case HILSE_OUT_LAST:
+ write_lock_irqsave(&mlc->lock, flags);
+ pack = node->object.packet;
+ pack |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT);
+ goto out;
+
+ case HILSE_OUT_DISC:
+ write_lock_irqsave(&mlc->lock, flags);
+ pack = node->object.packet;
+ pack |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT);
+ goto out;
+
+ case HILSE_OUT:
+ write_lock_irqsave(&mlc->lock, flags);
+ pack = node->object.packet;
+ out:
+ if (!mlc->istarted) {
+ /* Prepare to receive input */
+ if ((node + 1)->act & HILSE_IN)
+ hilse_setup_input(mlc, node + 1);
+ }
+
+ write_unlock_irqrestore(&mlc->lock, flags);
+
+ if (down_trylock(&mlc->osem)) {
+ nextidx = HILSEN_DOZE;
+ break;
+ }
+ up(&mlc->osem);
+
+ write_lock_irqsave(&mlc->lock, flags);
+ if (!mlc->ostarted) {
+ mlc->ostarted = 1;
+ mlc->opacket = pack;
+ rc = mlc->out(mlc);
+ nextidx = HILSEN_DOZE;
+ write_unlock_irqrestore(&mlc->lock, flags);
+ if (rc) {
+ hil_mlc_stop = 1;
+ return 1;
+ }
+ break;
+ }
+ mlc->ostarted = 0;
+ mlc->instart = jiffies;
+ write_unlock_irqrestore(&mlc->lock, flags);
+ nextidx = HILSEN_NEXT;
+ break;
+
+ case HILSE_CTS:
+ write_lock_irqsave(&mlc->lock, flags);
+ rc = mlc->cts(mlc);
+ nextidx = rc ? node->bad : node->good;
+ write_unlock_irqrestore(&mlc->lock, flags);
+ if (rc) {
+ hil_mlc_stop = 1;
+ return 1;
+ }
+ break;
+
+ default:
+ BUG();
+ }
+
+#ifdef HIL_MLC_DEBUG
+ if (nextidx == HILSEN_DOZE)
+ doze++;
+#endif
+
+ while (nextidx & HILSEN_SCHED) {
+ unsigned long now = jiffies;
+
+ if (!sched_long)
+ goto sched;
+
+ if (time_after(now, mlc->instart + mlc->intimeout))
+ goto sched;
+ mod_timer(&hil_mlcs_kicker, mlc->instart + mlc->intimeout);
+ break;
+ sched:
+ tasklet_schedule(&hil_mlcs_tasklet);
+ break;
+ }
+
+ if (nextidx & HILSEN_DOWN)
+ mlc->seidx += nextidx & HILSEN_MASK;
+ else if (nextidx & HILSEN_UP)
+ mlc->seidx -= nextidx & HILSEN_MASK;
+ else
+ mlc->seidx = nextidx & HILSEN_MASK;
+
+ if (nextidx & HILSEN_BREAK)
+ return 1;
+
+ return 0;
+}
+
+/******************** tasklet context functions **************************/
+static void hil_mlcs_process(unsigned long unused)
+{
+ struct list_head *tmp;
+
+ read_lock(&hil_mlcs_lock);
+ list_for_each(tmp, &hil_mlcs) {
+ struct hil_mlc *mlc = list_entry(tmp, hil_mlc, list);
+ while (hilse_donode(mlc) == 0) {
+#ifdef HIL_MLC_DEBUG
+ if (mlc->seidx != 41 &&
+ mlc->seidx != 42 &&
+ mlc->seidx != 43)
+ printk(KERN_DEBUG PREFIX " + ");
+#endif
+ }
+ }
+ read_unlock(&hil_mlcs_lock);
+}
+
+/************************* Keepalive timer task *********************/
+
+static void hil_mlcs_timer(struct timer_list *unused)
+{
+ if (hil_mlc_stop) {
+ /* could not send packet - stop immediately. */
+ pr_warn(PREFIX "HIL seems stuck - Disabling HIL MLC.\n");
+ return;
+ }
+
+ hil_mlcs_probe = 1;
+ tasklet_schedule(&hil_mlcs_tasklet);
+ /* Re-insert the periodic task. */
+ if (!timer_pending(&hil_mlcs_kicker))
+ mod_timer(&hil_mlcs_kicker, jiffies + HZ);
+}
+
+/******************** user/kernel context functions **********************/
+
+static int hil_mlc_serio_write(struct serio *serio, unsigned char c)
+{
+ struct hil_mlc_serio_map *map;
+ struct hil_mlc *mlc;
+ struct serio_driver *drv;
+ uint8_t *idx, *last;
+
+ map = serio->port_data;
+ BUG_ON(map == NULL);
+
+ mlc = map->mlc;
+ BUG_ON(mlc == NULL);
+
+ mlc->serio_opacket[map->didx] |=
+ ((hil_packet)c) << (8 * (3 - mlc->serio_oidx[map->didx]));
+
+ if (mlc->serio_oidx[map->didx] >= 3) {
+ /* for now only commands */
+ if (!(mlc->serio_opacket[map->didx] & HIL_PKT_CMD))
+ return -EIO;
+ switch (mlc->serio_opacket[map->didx] & HIL_PKT_DATA_MASK) {
+ case HIL_CMD_IDD:
+ idx = mlc->di[map->didx].idd;
+ goto emu;
+ case HIL_CMD_RSC:
+ idx = mlc->di[map->didx].rsc;
+ goto emu;
+ case HIL_CMD_EXD:
+ idx = mlc->di[map->didx].exd;
+ goto emu;
+ case HIL_CMD_RNM:
+ idx = mlc->di[map->didx].rnm;
+ goto emu;
+ default:
+ break;
+ }
+ mlc->serio_oidx[map->didx] = 0;
+ mlc->serio_opacket[map->didx] = 0;
+ }
+
+ mlc->serio_oidx[map->didx]++;
+ return -EIO;
+ emu:
+ drv = serio->drv;
+ BUG_ON(drv == NULL);
+
+ last = idx + 15;
+ while ((last != idx) && (*last == 0))
+ last--;
+
+ while (idx != last) {
+ drv->interrupt(serio, 0, 0);
+ drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
+ drv->interrupt(serio, 0, 0);
+ drv->interrupt(serio, *idx, 0);
+ idx++;
+ }
+ drv->interrupt(serio, 0, 0);
+ drv->interrupt(serio, HIL_ERR_INT >> 16, 0);
+ drv->interrupt(serio, HIL_PKT_CMD >> 8, 0);
+ drv->interrupt(serio, *idx, 0);
+
+ mlc->serio_oidx[map->didx] = 0;
+ mlc->serio_opacket[map->didx] = 0;
+
+ return 0;
+}
+
+static int hil_mlc_serio_open(struct serio *serio)
+{
+ struct hil_mlc_serio_map *map;
+ struct hil_mlc *mlc;
+
+ if (serio_get_drvdata(serio) != NULL)
+ return -EBUSY;
+
+ map = serio->port_data;
+ BUG_ON(map == NULL);
+
+ mlc = map->mlc;
+ BUG_ON(mlc == NULL);
+
+ return 0;
+}
+
+static void hil_mlc_serio_close(struct serio *serio)
+{
+ struct hil_mlc_serio_map *map;
+ struct hil_mlc *mlc;
+
+ map = serio->port_data;
+ BUG_ON(map == NULL);
+
+ mlc = map->mlc;
+ BUG_ON(mlc == NULL);
+
+ serio_set_drvdata(serio, NULL);
+ serio->drv = NULL;
+ /* TODO wake up interruptable */
+}
+
+static const struct serio_device_id hil_mlc_serio_id = {
+ .type = SERIO_HIL_MLC,
+ .proto = SERIO_HIL,
+ .extra = SERIO_ANY,
+ .id = SERIO_ANY,
+};
+
+int hil_mlc_register(hil_mlc *mlc)
+{
+ int i;
+ unsigned long flags;
+
+ BUG_ON(mlc == NULL);
+
+ mlc->istarted = 0;
+ mlc->ostarted = 0;
+
+ rwlock_init(&mlc->lock);
+ sema_init(&mlc->osem, 1);
+
+ sema_init(&mlc->isem, 1);
+ mlc->icount = -1;
+ mlc->imatch = 0;
+
+ mlc->opercnt = 0;
+
+ sema_init(&(mlc->csem), 0);
+
+ hil_mlc_clear_di_scratch(mlc);
+ hil_mlc_clear_di_map(mlc, 0);
+ for (i = 0; i < HIL_MLC_DEVMEM; i++) {
+ struct serio *mlc_serio;
+ hil_mlc_copy_di_scratch(mlc, i);
+ mlc_serio = kzalloc(sizeof(*mlc_serio), GFP_KERNEL);
+ mlc->serio[i] = mlc_serio;
+ if (!mlc->serio[i]) {
+ for (; i >= 0; i--)
+ kfree(mlc->serio[i]);
+ return -ENOMEM;
+ }
+ snprintf(mlc_serio->name, sizeof(mlc_serio->name)-1, "HIL_SERIO%d", i);
+ snprintf(mlc_serio->phys, sizeof(mlc_serio->phys)-1, "HIL%d", i);
+ mlc_serio->id = hil_mlc_serio_id;
+ mlc_serio->id.id = i; /* HIL port no. */
+ mlc_serio->write = hil_mlc_serio_write;
+ mlc_serio->open = hil_mlc_serio_open;
+ mlc_serio->close = hil_mlc_serio_close;
+ mlc_serio->port_data = &(mlc->serio_map[i]);
+ mlc->serio_map[i].mlc = mlc;
+ mlc->serio_map[i].didx = i;
+ mlc->serio_map[i].di_revmap = -1;
+ mlc->serio_opacket[i] = 0;
+ mlc->serio_oidx[i] = 0;
+ serio_register_port(mlc_serio);
+ }
+
+ mlc->tasklet = &hil_mlcs_tasklet;
+
+ write_lock_irqsave(&hil_mlcs_lock, flags);
+ list_add_tail(&mlc->list, &hil_mlcs);
+ mlc->seidx = HILSEN_START;
+ write_unlock_irqrestore(&hil_mlcs_lock, flags);
+
+ tasklet_schedule(&hil_mlcs_tasklet);
+ return 0;
+}
+
+int hil_mlc_unregister(hil_mlc *mlc)
+{
+ struct list_head *tmp;
+ unsigned long flags;
+ int i;
+
+ BUG_ON(mlc == NULL);
+
+ write_lock_irqsave(&hil_mlcs_lock, flags);
+ list_for_each(tmp, &hil_mlcs)
+ if (list_entry(tmp, hil_mlc, list) == mlc)
+ goto found;
+
+ /* not found in list */
+ write_unlock_irqrestore(&hil_mlcs_lock, flags);
+ tasklet_schedule(&hil_mlcs_tasklet);
+ return -ENODEV;
+
+ found:
+ list_del(tmp);
+ write_unlock_irqrestore(&hil_mlcs_lock, flags);
+
+ for (i = 0; i < HIL_MLC_DEVMEM; i++) {
+ serio_unregister_port(mlc->serio[i]);
+ mlc->serio[i] = NULL;
+ }
+
+ tasklet_schedule(&hil_mlcs_tasklet);
+ return 0;
+}
+
+/**************************** Module interface *************************/
+
+static int __init hil_mlc_init(void)
+{
+ timer_setup(&hil_mlcs_kicker, &hil_mlcs_timer, 0);
+ mod_timer(&hil_mlcs_kicker, jiffies + HZ);
+
+ tasklet_enable(&hil_mlcs_tasklet);
+
+ return 0;
+}
+
+static void __exit hil_mlc_exit(void)
+{
+ del_timer_sync(&hil_mlcs_kicker);
+ tasklet_kill(&hil_mlcs_tasklet);
+}
+
+module_init(hil_mlc_init);
+module_exit(hil_mlc_exit);
diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c
new file mode 100644
index 000000000..13eacf6ab
--- /dev/null
+++ b/drivers/input/serio/hp_sdc.c
@@ -0,0 +1,1127 @@
+/*
+ * HP i8042-based System Device Controller driver.
+ *
+ * Copyright (c) 2001 Brian S. Julin
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ *
+ * References:
+ * System Device Controller Microprocessor Firmware Theory of Operation
+ * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2
+ * Helge Deller's original hilkbd.c port for PA-RISC.
+ *
+ *
+ * Driver theory of operation:
+ *
+ * hp_sdc_put does all writing to the SDC. ISR can run on a different
+ * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time
+ * (it cannot really benefit from SMP anyway.) A tasket fit this perfectly.
+ *
+ * All data coming back from the SDC is sent via interrupt and can be read
+ * fully in the ISR, so there are no latency/throughput problems there.
+ * The problem is with output, due to the slow clock speed of the SDC
+ * compared to the CPU. This should not be too horrible most of the time,
+ * but if used with HIL devices that support the multibyte transfer command,
+ * keeping outbound throughput flowing at the 6500KBps that the HIL is
+ * capable of is more than can be done at HZ=100.
+ *
+ * Busy polling for IBF clear wastes CPU cycles and bus cycles. hp_sdc.ibf
+ * is set to 0 when the IBF flag in the status register has cleared. ISR
+ * may do this, and may also access the parts of queued transactions related
+ * to reading data back from the SDC, but otherwise will not touch the
+ * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1.
+ *
+ * The i8042 write index and the values in the 4-byte input buffer
+ * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively,
+ * to minimize the amount of IO needed to the SDC. However these values
+ * do not need to be locked since they are only ever accessed by hp_sdc_put.
+ *
+ * A timer task schedules the tasklet once per second just to make
+ * sure it doesn't freeze up and to allow for bad reads to time out.
+ */
+
+#include <linux/hp_sdc.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/time.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/hil.h>
+#include <asm/io.h>
+
+/* Machine-specific abstraction */
+
+#if defined(__hppa__)
+# include <asm/parisc-device.h>
+# define sdc_readb(p) gsc_readb(p)
+# define sdc_writeb(v,p) gsc_writeb((v),(p))
+#elif defined(__mc68000__)
+#include <linux/uaccess.h>
+# define sdc_readb(p) in_8(p)
+# define sdc_writeb(v,p) out_8((p),(v))
+#else
+# error "HIL is not supported on this platform"
+#endif
+
+#define PREFIX "HP SDC: "
+
+MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
+MODULE_DESCRIPTION("HP i8042-based SDC Driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
+EXPORT_SYMBOL(hp_sdc_request_timer_irq);
+EXPORT_SYMBOL(hp_sdc_request_hil_irq);
+EXPORT_SYMBOL(hp_sdc_request_cooked_irq);
+
+EXPORT_SYMBOL(hp_sdc_release_timer_irq);
+EXPORT_SYMBOL(hp_sdc_release_hil_irq);
+EXPORT_SYMBOL(hp_sdc_release_cooked_irq);
+
+EXPORT_SYMBOL(__hp_sdc_enqueue_transaction);
+EXPORT_SYMBOL(hp_sdc_enqueue_transaction);
+EXPORT_SYMBOL(hp_sdc_dequeue_transaction);
+
+static bool hp_sdc_disabled;
+module_param_named(no_hpsdc, hp_sdc_disabled, bool, 0);
+MODULE_PARM_DESC(no_hpsdc, "Do not enable HP SDC driver.");
+
+static hp_i8042_sdc hp_sdc; /* All driver state is kept in here. */
+
+/*************** primitives for use in any context *********************/
+static inline uint8_t hp_sdc_status_in8(void)
+{
+ uint8_t status;
+ unsigned long flags;
+
+ write_lock_irqsave(&hp_sdc.ibf_lock, flags);
+ status = sdc_readb(hp_sdc.status_io);
+ if (!(status & HP_SDC_STATUS_IBF))
+ hp_sdc.ibf = 0;
+ write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
+
+ return status;
+}
+
+static inline uint8_t hp_sdc_data_in8(void)
+{
+ return sdc_readb(hp_sdc.data_io);
+}
+
+static inline void hp_sdc_status_out8(uint8_t val)
+{
+ unsigned long flags;
+
+ write_lock_irqsave(&hp_sdc.ibf_lock, flags);
+ hp_sdc.ibf = 1;
+ if ((val & 0xf0) == 0xe0)
+ hp_sdc.wi = 0xff;
+ sdc_writeb(val, hp_sdc.status_io);
+ write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
+}
+
+static inline void hp_sdc_data_out8(uint8_t val)
+{
+ unsigned long flags;
+
+ write_lock_irqsave(&hp_sdc.ibf_lock, flags);
+ hp_sdc.ibf = 1;
+ sdc_writeb(val, hp_sdc.data_io);
+ write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
+}
+
+/* Care must be taken to only invoke hp_sdc_spin_ibf when
+ * absolutely needed, or in rarely invoked subroutines.
+ * Not only does it waste CPU cycles, it also wastes bus cycles.
+ */
+static inline void hp_sdc_spin_ibf(void)
+{
+ unsigned long flags;
+ rwlock_t *lock;
+
+ lock = &hp_sdc.ibf_lock;
+
+ read_lock_irqsave(lock, flags);
+ if (!hp_sdc.ibf) {
+ read_unlock_irqrestore(lock, flags);
+ return;
+ }
+ read_unlock(lock);
+ write_lock(lock);
+ while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF)
+ { }
+ hp_sdc.ibf = 0;
+ write_unlock_irqrestore(lock, flags);
+}
+
+
+/************************ Interrupt context functions ************************/
+static void hp_sdc_take(int irq, void *dev_id, uint8_t status, uint8_t data)
+{
+ hp_sdc_transaction *curr;
+
+ read_lock(&hp_sdc.rtq_lock);
+ if (hp_sdc.rcurr < 0) {
+ read_unlock(&hp_sdc.rtq_lock);
+ return;
+ }
+ curr = hp_sdc.tq[hp_sdc.rcurr];
+ read_unlock(&hp_sdc.rtq_lock);
+
+ curr->seq[curr->idx++] = status;
+ curr->seq[curr->idx++] = data;
+ hp_sdc.rqty -= 2;
+ hp_sdc.rtime = ktime_get();
+
+ if (hp_sdc.rqty <= 0) {
+ /* All data has been gathered. */
+ if (curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE)
+ if (curr->act.semaphore)
+ up(curr->act.semaphore);
+
+ if (curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK)
+ if (curr->act.irqhook)
+ curr->act.irqhook(irq, dev_id, status, data);
+
+ curr->actidx = curr->idx;
+ curr->idx++;
+ /* Return control of this transaction */
+ write_lock(&hp_sdc.rtq_lock);
+ hp_sdc.rcurr = -1;
+ hp_sdc.rqty = 0;
+ write_unlock(&hp_sdc.rtq_lock);
+ tasklet_schedule(&hp_sdc.task);
+ }
+}
+
+static irqreturn_t hp_sdc_isr(int irq, void *dev_id)
+{
+ uint8_t status, data;
+
+ status = hp_sdc_status_in8();
+ /* Read data unconditionally to advance i8042. */
+ data = hp_sdc_data_in8();
+
+ /* For now we are ignoring these until we get the SDC to behave. */
+ if (((status & 0xf1) == 0x51) && data == 0x82)
+ return IRQ_HANDLED;
+
+ switch (status & HP_SDC_STATUS_IRQMASK) {
+ case 0: /* This case is not documented. */
+ break;
+
+ case HP_SDC_STATUS_USERTIMER:
+ case HP_SDC_STATUS_PERIODIC:
+ case HP_SDC_STATUS_TIMER:
+ read_lock(&hp_sdc.hook_lock);
+ if (hp_sdc.timer != NULL)
+ hp_sdc.timer(irq, dev_id, status, data);
+ read_unlock(&hp_sdc.hook_lock);
+ break;
+
+ case HP_SDC_STATUS_REG:
+ hp_sdc_take(irq, dev_id, status, data);
+ break;
+
+ case HP_SDC_STATUS_HILCMD:
+ case HP_SDC_STATUS_HILDATA:
+ read_lock(&hp_sdc.hook_lock);
+ if (hp_sdc.hil != NULL)
+ hp_sdc.hil(irq, dev_id, status, data);
+ read_unlock(&hp_sdc.hook_lock);
+ break;
+
+ case HP_SDC_STATUS_PUP:
+ read_lock(&hp_sdc.hook_lock);
+ if (hp_sdc.pup != NULL)
+ hp_sdc.pup(irq, dev_id, status, data);
+ else
+ printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n");
+ read_unlock(&hp_sdc.hook_lock);
+ break;
+
+ default:
+ read_lock(&hp_sdc.hook_lock);
+ if (hp_sdc.cooked != NULL)
+ hp_sdc.cooked(irq, dev_id, status, data);
+ read_unlock(&hp_sdc.hook_lock);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t hp_sdc_nmisr(int irq, void *dev_id)
+{
+ int status;
+
+ status = hp_sdc_status_in8();
+ printk(KERN_WARNING PREFIX "NMI !\n");
+
+#if 0
+ if (status & HP_SDC_NMISTATUS_FHS) {
+ read_lock(&hp_sdc.hook_lock);
+ if (hp_sdc.timer != NULL)
+ hp_sdc.timer(irq, dev_id, status, 0);
+ read_unlock(&hp_sdc.hook_lock);
+ } else {
+ /* TODO: pass this on to the HIL handler, or do SAK here? */
+ printk(KERN_WARNING PREFIX "HIL NMI\n");
+ }
+#endif
+
+ return IRQ_HANDLED;
+}
+
+
+/***************** Kernel (tasklet) context functions ****************/
+
+unsigned long hp_sdc_put(void);
+
+static void hp_sdc_tasklet(unsigned long foo)
+{
+ write_lock_irq(&hp_sdc.rtq_lock);
+
+ if (hp_sdc.rcurr >= 0) {
+ ktime_t now = ktime_get();
+
+ if (ktime_after(now, ktime_add_us(hp_sdc.rtime,
+ HP_SDC_MAX_REG_DELAY))) {
+ hp_sdc_transaction *curr;
+ uint8_t tmp;
+
+ curr = hp_sdc.tq[hp_sdc.rcurr];
+ /* If this turns out to be a normal failure mode
+ * we'll need to figure out a way to communicate
+ * it back to the application. and be less verbose.
+ */
+ printk(KERN_WARNING PREFIX "read timeout (%lldus)!\n",
+ ktime_us_delta(now, hp_sdc.rtime));
+ curr->idx += hp_sdc.rqty;
+ hp_sdc.rqty = 0;
+ tmp = curr->seq[curr->actidx];
+ curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD;
+ if (tmp & HP_SDC_ACT_SEMAPHORE)
+ if (curr->act.semaphore)
+ up(curr->act.semaphore);
+
+ if (tmp & HP_SDC_ACT_CALLBACK) {
+ /* Note this means that irqhooks may be called
+ * in tasklet/bh context.
+ */
+ if (curr->act.irqhook)
+ curr->act.irqhook(0, NULL, 0, 0);
+ }
+
+ curr->actidx = curr->idx;
+ curr->idx++;
+ hp_sdc.rcurr = -1;
+ }
+ }
+ write_unlock_irq(&hp_sdc.rtq_lock);
+ hp_sdc_put();
+}
+
+unsigned long hp_sdc_put(void)
+{
+ hp_sdc_transaction *curr;
+ uint8_t act;
+ int idx, curridx;
+
+ int limit = 0;
+
+ write_lock(&hp_sdc.lock);
+
+ /* If i8042 buffers are full, we cannot do anything that
+ requires output, so we skip to the administrativa. */
+ if (hp_sdc.ibf) {
+ hp_sdc_status_in8();
+ if (hp_sdc.ibf)
+ goto finish;
+ }
+
+ anew:
+ /* See if we are in the middle of a sequence. */
+ if (hp_sdc.wcurr < 0)
+ hp_sdc.wcurr = 0;
+ read_lock_irq(&hp_sdc.rtq_lock);
+ if (hp_sdc.rcurr == hp_sdc.wcurr)
+ hp_sdc.wcurr++;
+ read_unlock_irq(&hp_sdc.rtq_lock);
+ if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
+ hp_sdc.wcurr = 0;
+ curridx = hp_sdc.wcurr;
+
+ if (hp_sdc.tq[curridx] != NULL)
+ goto start;
+
+ while (++curridx != hp_sdc.wcurr) {
+ if (curridx >= HP_SDC_QUEUE_LEN) {
+ curridx = -1; /* Wrap to top */
+ continue;
+ }
+ read_lock_irq(&hp_sdc.rtq_lock);
+ if (hp_sdc.rcurr == curridx) {
+ read_unlock_irq(&hp_sdc.rtq_lock);
+ continue;
+ }
+ read_unlock_irq(&hp_sdc.rtq_lock);
+ if (hp_sdc.tq[curridx] != NULL)
+ break; /* Found one. */
+ }
+ if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */
+ curridx = -1;
+ }
+ hp_sdc.wcurr = curridx;
+
+ start:
+
+ /* Check to see if the interrupt mask needs to be set. */
+ if (hp_sdc.set_im) {
+ hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM);
+ hp_sdc.set_im = 0;
+ goto finish;
+ }
+
+ if (hp_sdc.wcurr == -1)
+ goto done;
+
+ curr = hp_sdc.tq[curridx];
+ idx = curr->actidx;
+
+ if (curr->actidx >= curr->endidx) {
+ hp_sdc.tq[curridx] = NULL;
+ /* Interleave outbound data between the transactions. */
+ hp_sdc.wcurr++;
+ if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
+ hp_sdc.wcurr = 0;
+ goto finish;
+ }
+
+ act = curr->seq[idx];
+ idx++;
+
+ if (curr->idx >= curr->endidx) {
+ if (act & HP_SDC_ACT_DEALLOC)
+ kfree(curr);
+ hp_sdc.tq[curridx] = NULL;
+ /* Interleave outbound data between the transactions. */
+ hp_sdc.wcurr++;
+ if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
+ hp_sdc.wcurr = 0;
+ goto finish;
+ }
+
+ while (act & HP_SDC_ACT_PRECMD) {
+ if (curr->idx != idx) {
+ idx++;
+ act &= ~HP_SDC_ACT_PRECMD;
+ break;
+ }
+ hp_sdc_status_out8(curr->seq[idx]);
+ curr->idx++;
+ /* act finished? */
+ if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD)
+ goto actdone;
+ /* skip quantity field if data-out sequence follows. */
+ if (act & HP_SDC_ACT_DATAOUT)
+ curr->idx++;
+ goto finish;
+ }
+ if (act & HP_SDC_ACT_DATAOUT) {
+ int qty;
+
+ qty = curr->seq[idx];
+ idx++;
+ if (curr->idx - idx < qty) {
+ hp_sdc_data_out8(curr->seq[curr->idx]);
+ curr->idx++;
+ /* act finished? */
+ if (curr->idx - idx >= qty &&
+ (act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)
+ goto actdone;
+ goto finish;
+ }
+ idx += qty;
+ act &= ~HP_SDC_ACT_DATAOUT;
+ } else
+ while (act & HP_SDC_ACT_DATAREG) {
+ int mask;
+ uint8_t w7[4];
+
+ mask = curr->seq[idx];
+ if (idx != curr->idx) {
+ idx++;
+ idx += !!(mask & 1);
+ idx += !!(mask & 2);
+ idx += !!(mask & 4);
+ idx += !!(mask & 8);
+ act &= ~HP_SDC_ACT_DATAREG;
+ break;
+ }
+
+ w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0];
+ w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1];
+ w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2];
+ w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3];
+
+ if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 ||
+ w7[hp_sdc.wi - 0x70] == hp_sdc.r7[hp_sdc.wi - 0x70]) {
+ int i = 0;
+
+ /* Need to point the write index register */
+ while (i < 4 && w7[i] == hp_sdc.r7[i])
+ i++;
+
+ if (i < 4) {
+ hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i);
+ hp_sdc.wi = 0x70 + i;
+ goto finish;
+ }
+
+ idx++;
+ if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG)
+ goto actdone;
+
+ curr->idx = idx;
+ act &= ~HP_SDC_ACT_DATAREG;
+ break;
+ }
+
+ hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]);
+ hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70];
+ hp_sdc.wi++; /* write index register autoincrements */
+ {
+ int i = 0;
+
+ while ((i < 4) && w7[i] == hp_sdc.r7[i])
+ i++;
+ if (i >= 4) {
+ curr->idx = idx + 1;
+ if ((act & HP_SDC_ACT_DURING) ==
+ HP_SDC_ACT_DATAREG)
+ goto actdone;
+ }
+ }
+ goto finish;
+ }
+ /* We don't go any further in the command if there is a pending read,
+ because we don't want interleaved results. */
+ read_lock_irq(&hp_sdc.rtq_lock);
+ if (hp_sdc.rcurr >= 0) {
+ read_unlock_irq(&hp_sdc.rtq_lock);
+ goto finish;
+ }
+ read_unlock_irq(&hp_sdc.rtq_lock);
+
+
+ if (act & HP_SDC_ACT_POSTCMD) {
+ uint8_t postcmd;
+
+ /* curr->idx should == idx at this point. */
+ postcmd = curr->seq[idx];
+ curr->idx++;
+ if (act & HP_SDC_ACT_DATAIN) {
+
+ /* Start a new read */
+ hp_sdc.rqty = curr->seq[curr->idx];
+ hp_sdc.rtime = ktime_get();
+ curr->idx++;
+ /* Still need to lock here in case of spurious irq. */
+ write_lock_irq(&hp_sdc.rtq_lock);
+ hp_sdc.rcurr = curridx;
+ write_unlock_irq(&hp_sdc.rtq_lock);
+ hp_sdc_status_out8(postcmd);
+ goto finish;
+ }
+ hp_sdc_status_out8(postcmd);
+ goto actdone;
+ }
+
+ actdone:
+ if (act & HP_SDC_ACT_SEMAPHORE)
+ up(curr->act.semaphore);
+ else if (act & HP_SDC_ACT_CALLBACK)
+ curr->act.irqhook(0,NULL,0,0);
+
+ if (curr->idx >= curr->endidx) { /* This transaction is over. */
+ if (act & HP_SDC_ACT_DEALLOC)
+ kfree(curr);
+ hp_sdc.tq[curridx] = NULL;
+ } else {
+ curr->actidx = idx + 1;
+ curr->idx = idx + 2;
+ }
+ /* Interleave outbound data between the transactions. */
+ hp_sdc.wcurr++;
+ if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
+ hp_sdc.wcurr = 0;
+
+ finish:
+ /* If by some quirk IBF has cleared and our ISR has run to
+ see that that has happened, do it all again. */
+ if (!hp_sdc.ibf && limit++ < 20)
+ goto anew;
+
+ done:
+ if (hp_sdc.wcurr >= 0)
+ tasklet_schedule(&hp_sdc.task);
+ write_unlock(&hp_sdc.lock);
+
+ return 0;
+}
+
+/******* Functions called in either user or kernel context ****/
+int __hp_sdc_enqueue_transaction(hp_sdc_transaction *this)
+{
+ int i;
+
+ if (this == NULL) {
+ BUG();
+ return -EINVAL;
+ }
+
+ /* Can't have same transaction on queue twice */
+ for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
+ if (hp_sdc.tq[i] == this)
+ goto fail;
+
+ this->actidx = 0;
+ this->idx = 1;
+
+ /* Search for empty slot */
+ for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
+ if (hp_sdc.tq[i] == NULL) {
+ hp_sdc.tq[i] = this;
+ tasklet_schedule(&hp_sdc.task);
+ return 0;
+ }
+
+ printk(KERN_WARNING PREFIX "No free slot to add transaction.\n");
+ return -EBUSY;
+
+ fail:
+ printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n");
+ return -EINVAL;
+}
+
+int hp_sdc_enqueue_transaction(hp_sdc_transaction *this) {
+ unsigned long flags;
+ int ret;
+
+ write_lock_irqsave(&hp_sdc.lock, flags);
+ ret = __hp_sdc_enqueue_transaction(this);
+ write_unlock_irqrestore(&hp_sdc.lock,flags);
+
+ return ret;
+}
+
+int hp_sdc_dequeue_transaction(hp_sdc_transaction *this)
+{
+ unsigned long flags;
+ int i;
+
+ write_lock_irqsave(&hp_sdc.lock, flags);
+
+ /* TODO: don't remove it if it's not done. */
+
+ for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
+ if (hp_sdc.tq[i] == this)
+ hp_sdc.tq[i] = NULL;
+
+ write_unlock_irqrestore(&hp_sdc.lock, flags);
+ return 0;
+}
+
+
+
+/********************** User context functions **************************/
+int hp_sdc_request_timer_irq(hp_sdc_irqhook *callback)
+{
+ if (callback == NULL || hp_sdc.dev == NULL)
+ return -EINVAL;
+
+ write_lock_irq(&hp_sdc.hook_lock);
+ if (hp_sdc.timer != NULL) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EBUSY;
+ }
+
+ hp_sdc.timer = callback;
+ /* Enable interrupts from the timers */
+ hp_sdc.im &= ~HP_SDC_IM_FH;
+ hp_sdc.im &= ~HP_SDC_IM_PT;
+ hp_sdc.im &= ~HP_SDC_IM_TIMERS;
+ hp_sdc.set_im = 1;
+ write_unlock_irq(&hp_sdc.hook_lock);
+
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+int hp_sdc_request_hil_irq(hp_sdc_irqhook *callback)
+{
+ if (callback == NULL || hp_sdc.dev == NULL)
+ return -EINVAL;
+
+ write_lock_irq(&hp_sdc.hook_lock);
+ if (hp_sdc.hil != NULL) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EBUSY;
+ }
+
+ hp_sdc.hil = callback;
+ hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
+ hp_sdc.set_im = 1;
+ write_unlock_irq(&hp_sdc.hook_lock);
+
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+int hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback)
+{
+ if (callback == NULL || hp_sdc.dev == NULL)
+ return -EINVAL;
+
+ write_lock_irq(&hp_sdc.hook_lock);
+ if (hp_sdc.cooked != NULL) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EBUSY;
+ }
+
+ /* Enable interrupts from the HIL MLC */
+ hp_sdc.cooked = callback;
+ hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
+ hp_sdc.set_im = 1;
+ write_unlock_irq(&hp_sdc.hook_lock);
+
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+int hp_sdc_release_timer_irq(hp_sdc_irqhook *callback)
+{
+ write_lock_irq(&hp_sdc.hook_lock);
+ if ((callback != hp_sdc.timer) ||
+ (hp_sdc.timer == NULL)) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EINVAL;
+ }
+
+ /* Disable interrupts from the timers */
+ hp_sdc.timer = NULL;
+ hp_sdc.im |= HP_SDC_IM_TIMERS;
+ hp_sdc.im |= HP_SDC_IM_FH;
+ hp_sdc.im |= HP_SDC_IM_PT;
+ hp_sdc.set_im = 1;
+ write_unlock_irq(&hp_sdc.hook_lock);
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+int hp_sdc_release_hil_irq(hp_sdc_irqhook *callback)
+{
+ write_lock_irq(&hp_sdc.hook_lock);
+ if ((callback != hp_sdc.hil) ||
+ (hp_sdc.hil == NULL)) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EINVAL;
+ }
+
+ hp_sdc.hil = NULL;
+ /* Disable interrupts from HIL only if there is no cooked driver. */
+ if(hp_sdc.cooked == NULL) {
+ hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
+ hp_sdc.set_im = 1;
+ }
+ write_unlock_irq(&hp_sdc.hook_lock);
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+int hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback)
+{
+ write_lock_irq(&hp_sdc.hook_lock);
+ if ((callback != hp_sdc.cooked) ||
+ (hp_sdc.cooked == NULL)) {
+ write_unlock_irq(&hp_sdc.hook_lock);
+ return -EINVAL;
+ }
+
+ hp_sdc.cooked = NULL;
+ /* Disable interrupts from HIL only if there is no raw HIL driver. */
+ if(hp_sdc.hil == NULL) {
+ hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
+ hp_sdc.set_im = 1;
+ }
+ write_unlock_irq(&hp_sdc.hook_lock);
+ tasklet_schedule(&hp_sdc.task);
+
+ return 0;
+}
+
+/************************* Keepalive timer task *********************/
+
+static void hp_sdc_kicker(struct timer_list *unused)
+{
+ tasklet_schedule(&hp_sdc.task);
+ /* Re-insert the periodic task. */
+ mod_timer(&hp_sdc.kicker, jiffies + HZ);
+}
+
+/************************** Module Initialization ***************************/
+
+#if defined(__hppa__)
+
+static const struct parisc_device_id hp_sdc_tbl[] __initconst = {
+ {
+ .hw_type = HPHW_FIO,
+ .hversion_rev = HVERSION_REV_ANY_ID,
+ .hversion = HVERSION_ANY_ID,
+ .sversion = 0x73,
+ },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(parisc, hp_sdc_tbl);
+
+static int __init hp_sdc_init_hppa(struct parisc_device *d);
+static struct delayed_work moduleloader_work;
+
+static struct parisc_driver hp_sdc_driver __refdata = {
+ .name = "hp_sdc",
+ .id_table = hp_sdc_tbl,
+ .probe = hp_sdc_init_hppa,
+};
+
+#endif /* __hppa__ */
+
+static int __init hp_sdc_init(void)
+{
+ char *errstr;
+ hp_sdc_transaction t_sync;
+ uint8_t ts_sync[6];
+ struct semaphore s_sync;
+
+ rwlock_init(&hp_sdc.lock);
+ rwlock_init(&hp_sdc.ibf_lock);
+ rwlock_init(&hp_sdc.rtq_lock);
+ rwlock_init(&hp_sdc.hook_lock);
+
+ hp_sdc.timer = NULL;
+ hp_sdc.hil = NULL;
+ hp_sdc.pup = NULL;
+ hp_sdc.cooked = NULL;
+ hp_sdc.im = HP_SDC_IM_MASK; /* Mask maskable irqs */
+ hp_sdc.set_im = 1;
+ hp_sdc.wi = 0xff;
+ hp_sdc.r7[0] = 0xff;
+ hp_sdc.r7[1] = 0xff;
+ hp_sdc.r7[2] = 0xff;
+ hp_sdc.r7[3] = 0xff;
+ hp_sdc.ibf = 1;
+
+ memset(&hp_sdc.tq, 0, sizeof(hp_sdc.tq));
+
+ hp_sdc.wcurr = -1;
+ hp_sdc.rcurr = -1;
+ hp_sdc.rqty = 0;
+
+ hp_sdc.dev_err = -ENODEV;
+
+ errstr = "IO not found for";
+ if (!hp_sdc.base_io)
+ goto err0;
+
+ errstr = "IRQ not found for";
+ if (!hp_sdc.irq)
+ goto err0;
+
+ hp_sdc.dev_err = -EBUSY;
+
+#if defined(__hppa__)
+ errstr = "IO not available for";
+ if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name))
+ goto err0;
+#endif
+
+ errstr = "IRQ not available for";
+ if (request_irq(hp_sdc.irq, &hp_sdc_isr, IRQF_SHARED,
+ "HP SDC", &hp_sdc))
+ goto err1;
+
+ errstr = "NMI not available for";
+ if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, IRQF_SHARED,
+ "HP SDC NMI", &hp_sdc))
+ goto err2;
+
+ pr_info(PREFIX "HP SDC at 0x%08lx, IRQ %d (NMI IRQ %d)\n",
+ hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
+
+ hp_sdc_status_in8();
+ hp_sdc_data_in8();
+
+ tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0);
+
+ /* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */
+ t_sync.actidx = 0;
+ t_sync.idx = 1;
+ t_sync.endidx = 6;
+ t_sync.seq = ts_sync;
+ ts_sync[0] = HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE;
+ ts_sync[1] = 0x0f;
+ ts_sync[2] = ts_sync[3] = ts_sync[4] = ts_sync[5] = 0;
+ t_sync.act.semaphore = &s_sync;
+ sema_init(&s_sync, 0);
+ hp_sdc_enqueue_transaction(&t_sync);
+ down(&s_sync); /* Wait for t_sync to complete */
+
+ /* Create the keepalive task */
+ timer_setup(&hp_sdc.kicker, hp_sdc_kicker, 0);
+ hp_sdc.kicker.expires = jiffies + HZ;
+ add_timer(&hp_sdc.kicker);
+
+ hp_sdc.dev_err = 0;
+ return 0;
+ err2:
+ free_irq(hp_sdc.irq, &hp_sdc);
+ err1:
+ release_region(hp_sdc.data_io, 2);
+ err0:
+ printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n",
+ errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
+ hp_sdc.dev = NULL;
+
+ return hp_sdc.dev_err;
+}
+
+#if defined(__hppa__)
+
+static void request_module_delayed(struct work_struct *work)
+{
+ request_module("hp_sdc_mlc");
+}
+
+static int __init hp_sdc_init_hppa(struct parisc_device *d)
+{
+ int ret;
+
+ if (!d)
+ return 1;
+ if (hp_sdc.dev != NULL)
+ return 1; /* We only expect one SDC */
+
+ hp_sdc.dev = d;
+ hp_sdc.irq = d->irq;
+ hp_sdc.nmi = d->aux_irq;
+ hp_sdc.base_io = d->hpa.start;
+ hp_sdc.data_io = d->hpa.start + 0x800;
+ hp_sdc.status_io = d->hpa.start + 0x801;
+
+ INIT_DELAYED_WORK(&moduleloader_work, request_module_delayed);
+
+ ret = hp_sdc_init();
+ /* after successful initialization give SDC some time to settle
+ * and then load the hp_sdc_mlc upper layer driver */
+ if (!ret)
+ schedule_delayed_work(&moduleloader_work,
+ msecs_to_jiffies(2000));
+
+ return ret;
+}
+
+#endif /* __hppa__ */
+
+static void hp_sdc_exit(void)
+{
+ /* do nothing if we don't have a SDC */
+ if (!hp_sdc.dev)
+ return;
+
+ write_lock_irq(&hp_sdc.lock);
+
+ /* Turn off all maskable "sub-function" irq's. */
+ hp_sdc_spin_ibf();
+ sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io);
+
+ /* Wait until we know this has been processed by the i8042 */
+ hp_sdc_spin_ibf();
+
+ free_irq(hp_sdc.nmi, &hp_sdc);
+ free_irq(hp_sdc.irq, &hp_sdc);
+ write_unlock_irq(&hp_sdc.lock);
+
+ del_timer_sync(&hp_sdc.kicker);
+
+ tasklet_kill(&hp_sdc.task);
+
+#if defined(__hppa__)
+ cancel_delayed_work_sync(&moduleloader_work);
+ if (unregister_parisc_driver(&hp_sdc_driver))
+ printk(KERN_WARNING PREFIX "Error unregistering HP SDC");
+#endif
+}
+
+static int __init hp_sdc_register(void)
+{
+ hp_sdc_transaction tq_init;
+ uint8_t tq_init_seq[5];
+ struct semaphore tq_init_sem;
+#if defined(__mc68000__)
+ unsigned char i;
+#endif
+
+ if (hp_sdc_disabled) {
+ printk(KERN_WARNING PREFIX "HP SDC driver disabled by no_hpsdc=1.\n");
+ return -ENODEV;
+ }
+
+ hp_sdc.dev = NULL;
+ hp_sdc.dev_err = 0;
+#if defined(__hppa__)
+ if (register_parisc_driver(&hp_sdc_driver)) {
+ printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n");
+ return -ENODEV;
+ }
+#elif defined(__mc68000__)
+ if (!MACH_IS_HP300)
+ return -ENODEV;
+
+ hp_sdc.irq = 1;
+ hp_sdc.nmi = 7;
+ hp_sdc.base_io = (unsigned long) 0xf0428000;
+ hp_sdc.data_io = (unsigned long) hp_sdc.base_io + 1;
+ hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3;
+ if (!copy_from_kernel_nofault(&i, (unsigned char *)hp_sdc.data_io, 1))
+ hp_sdc.dev = (void *)1;
+ hp_sdc.dev_err = hp_sdc_init();
+#endif
+ if (hp_sdc.dev == NULL) {
+ printk(KERN_WARNING PREFIX "No SDC found.\n");
+ return hp_sdc.dev_err;
+ }
+
+ sema_init(&tq_init_sem, 0);
+
+ tq_init.actidx = 0;
+ tq_init.idx = 1;
+ tq_init.endidx = 5;
+ tq_init.seq = tq_init_seq;
+ tq_init.act.semaphore = &tq_init_sem;
+
+ tq_init_seq[0] =
+ HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE;
+ tq_init_seq[1] = HP_SDC_CMD_READ_KCC;
+ tq_init_seq[2] = 1;
+ tq_init_seq[3] = 0;
+ tq_init_seq[4] = 0;
+
+ hp_sdc_enqueue_transaction(&tq_init);
+
+ down(&tq_init_sem);
+ up(&tq_init_sem);
+
+ if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
+ printk(KERN_WARNING PREFIX "Error reading config byte.\n");
+ hp_sdc_exit();
+ return -ENODEV;
+ }
+ hp_sdc.r11 = tq_init_seq[4];
+ if (hp_sdc.r11 & HP_SDC_CFG_NEW) {
+ const char *str;
+ printk(KERN_INFO PREFIX "New style SDC\n");
+ tq_init_seq[1] = HP_SDC_CMD_READ_XTD;
+ tq_init.actidx = 0;
+ tq_init.idx = 1;
+ down(&tq_init_sem);
+ hp_sdc_enqueue_transaction(&tq_init);
+ down(&tq_init_sem);
+ up(&tq_init_sem);
+ if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
+ printk(KERN_WARNING PREFIX "Error reading extended config byte.\n");
+ return -ENODEV;
+ }
+ hp_sdc.r7e = tq_init_seq[4];
+ HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str)
+ printk(KERN_INFO PREFIX "Revision: %s\n", str);
+ if (hp_sdc.r7e & HP_SDC_XTD_BEEPER)
+ printk(KERN_INFO PREFIX "TI SN76494 beeper present\n");
+ if (hp_sdc.r7e & HP_SDC_XTD_BBRTC)
+ printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n");
+ printk(KERN_INFO PREFIX "Spunking the self test register to force PUP "
+ "on next firmware reset.\n");
+ tq_init_seq[0] = HP_SDC_ACT_PRECMD |
+ HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE;
+ tq_init_seq[1] = HP_SDC_CMD_SET_STR;
+ tq_init_seq[2] = 1;
+ tq_init_seq[3] = 0;
+ tq_init.actidx = 0;
+ tq_init.idx = 1;
+ tq_init.endidx = 4;
+ down(&tq_init_sem);
+ hp_sdc_enqueue_transaction(&tq_init);
+ down(&tq_init_sem);
+ up(&tq_init_sem);
+ } else
+ printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n",
+ (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087");
+
+ return 0;
+}
+
+module_init(hp_sdc_register);
+module_exit(hp_sdc_exit);
+
+/* Timing notes: These measurements taken on my 64MHz 7100-LC (715/64)
+ * cycles cycles-adj time
+ * between two consecutive mfctl(16)'s: 4 n/a 63ns
+ * hp_sdc_spin_ibf when idle: 119 115 1.7us
+ * gsc_writeb status register: 83 79 1.2us
+ * IBF to clear after sending SET_IM: 6204 6006 93us
+ * IBF to clear after sending LOAD_RT: 4467 4352 68us
+ * IBF to clear after sending two LOAD_RTs: 18974 18859 295us
+ * READ_T1, read status/data, IRQ, call handler: 35564 n/a 556us
+ * cmd to ~IBF READ_T1 2nd time right after: 5158403 n/a 81ms
+ * between IRQ received and ~IBF for above: 2578877 n/a 40ms
+ *
+ * Performance stats after a run of this module configuring HIL and
+ * receiving a few mouse events:
+ *
+ * status in8 282508 cycles 7128 calls
+ * status out8 8404 cycles 341 calls
+ * data out8 1734 cycles 78 calls
+ * isr 174324 cycles 617 calls (includes take)
+ * take 1241 cycles 2 calls
+ * put 1411504 cycles 6937 calls
+ * task 1655209 cycles 6937 calls (includes put)
+ *
+ */
diff --git a/drivers/input/serio/hp_sdc_mlc.c b/drivers/input/serio/hp_sdc_mlc.c
new file mode 100644
index 000000000..3e85e9039
--- /dev/null
+++ b/drivers/input/serio/hp_sdc_mlc.c
@@ -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);
diff --git a/drivers/input/serio/hyperv-keyboard.c b/drivers/input/serio/hyperv-keyboard.c
new file mode 100644
index 000000000..d62aefb2e
--- /dev/null
+++ b/drivers/input/serio/hyperv-keyboard.c
@@ -0,0 +1,442 @@
+// 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;
+ unsigned long flags;
+ 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.
+ */
+ spin_lock_irqsave(&kbd_dev->lock, flags);
+ 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);
+ }
+ spin_unlock_irqrestore(&kbd_dev->lock, flags);
+
+ /*
+ * 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kbd_dev->lock, flags);
+ kbd_dev->started = true;
+ spin_unlock_irqrestore(&kbd_dev->lock, flags);
+
+ return 0;
+}
+
+static void hv_kbd_stop(struct serio *serio)
+{
+ struct hv_kbd_dev *kbd_dev = serio->port_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kbd_dev->lock, flags);
+ kbd_dev->started = false;
+ spin_unlock_irqrestore(&kbd_dev->lock, flags);
+}
+
+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(sizeof(struct hv_kbd_dev), GFP_KERNEL);
+ hv_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+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);
diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h
new file mode 100644
index 000000000..b585b1dab
--- /dev/null
+++ b/drivers/input/serio/i8042-acpipnpio.h
@@ -0,0 +1,1740 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _I8042_ACPIPNPIO_H
+#define _I8042_ACPIPNPIO_H
+
+#include <linux/acpi.h>
+
+#ifdef CONFIG_X86
+#include <asm/x86_init.h>
+#endif
+
+/*
+ * Names.
+ */
+
+#define I8042_KBD_PHYS_DESC "isa0060/serio0"
+#define I8042_AUX_PHYS_DESC "isa0060/serio1"
+#define I8042_MUX_PHYS_DESC "isa0060/serio%d"
+
+/*
+ * IRQs.
+ */
+
+#if defined(__ia64__)
+# define I8042_MAP_IRQ(x) isa_irq_to_vector((x))
+#else
+# define I8042_MAP_IRQ(x) (x)
+#endif
+
+#define I8042_KBD_IRQ i8042_kbd_irq
+#define I8042_AUX_IRQ i8042_aux_irq
+
+static int i8042_kbd_irq;
+static int i8042_aux_irq;
+
+/*
+ * Register numbers.
+ */
+
+#define I8042_COMMAND_REG i8042_command_reg
+#define I8042_STATUS_REG i8042_command_reg
+#define I8042_DATA_REG i8042_data_reg
+
+static int i8042_command_reg = 0x64;
+static int i8042_data_reg = 0x60;
+
+
+static inline int i8042_read_data(void)
+{
+ return inb(I8042_DATA_REG);
+}
+
+static inline int i8042_read_status(void)
+{
+ return inb(I8042_STATUS_REG);
+}
+
+static inline void i8042_write_data(int val)
+{
+ outb(val, I8042_DATA_REG);
+}
+
+static inline void i8042_write_command(int val)
+{
+ outb(val, I8042_COMMAND_REG);
+}
+
+#ifdef CONFIG_X86
+
+#include <linux/dmi.h>
+
+#define SERIO_QUIRK_NOKBD BIT(0)
+#define SERIO_QUIRK_NOAUX BIT(1)
+#define SERIO_QUIRK_NOMUX BIT(2)
+#define SERIO_QUIRK_FORCEMUX BIT(3)
+#define SERIO_QUIRK_UNLOCK BIT(4)
+#define SERIO_QUIRK_PROBE_DEFER BIT(5)
+#define SERIO_QUIRK_RESET_ALWAYS BIT(6)
+#define SERIO_QUIRK_RESET_NEVER BIT(7)
+#define SERIO_QUIRK_DIECT BIT(8)
+#define SERIO_QUIRK_DUMBKBD BIT(9)
+#define SERIO_QUIRK_NOLOOP BIT(10)
+#define SERIO_QUIRK_NOTIMEOUT BIT(11)
+#define SERIO_QUIRK_KBDRESET BIT(12)
+#define SERIO_QUIRK_DRITEK BIT(13)
+#define SERIO_QUIRK_NOPNP BIT(14)
+
+/* Quirk table for different mainboards. Options similar or identical to i8042
+ * module parameters.
+ * ORDERING IS IMPORTANT! The first match will be apllied and the rest ignored.
+ * This allows entries to overwrite vendor wide quirks on a per device basis.
+ * Where this is irrelevant, entries are sorted case sensitive by DMI_SYS_VENDOR
+ * and/or DMI_BOARD_VENDOR to make it easier to avoid dublicate entries.
+ */
+static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ALIENWARE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Sentia"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X750LN"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Asus X450LCP */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X450LCP"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_NEVER)
+ },
+ {
+ /* ASUS ZenBook UX425UA/QA */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX425"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_PROBE_DEFER | SERIO_QUIRK_RESET_NEVER)
+ },
+ {
+ /* ASUS ZenBook UM325UA/QA */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_PROBE_DEFER | SERIO_QUIRK_RESET_NEVER)
+ },
+ /*
+ * On some Asus laptops, just running self tests cause problems.
+ */
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_NEVER)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /* Convertible Notebook */
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_NEVER)
+ },
+ {
+ /* ASUS P65UP5 - AUX LOOP command does not raise AUX IRQ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "P/I-P65UP5"),
+ DMI_MATCH(DMI_BOARD_VERSION, "REV 2.X"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* ASUS G1S */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer Inc."),
+ DMI_MATCH(DMI_BOARD_NAME, "G1S"),
+ DMI_MATCH(DMI_BOARD_VERSION, "1.0"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1360"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Acer Aspire 5710 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5710"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Acer Aspire 7738 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 7738"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Acer Aspire 5536 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5536"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "0100"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * Acer Aspire 5738z
+ * Touchpad stops working in mux mode when dis- + re-enabled
+ * with the touchpad enable/disable toggle hotkey
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5738"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Acer Aspire One 150 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AOA150"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Acer Aspire One 532h */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AO532h"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A114-31"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A314-31"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-31"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire ES1-132"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire ES1-332"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire ES1-432"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate Spin B118-RN"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ /*
+ * Some Wistron based laptops need us to explicitly enable the 'Dritek
+ * keyboard extension' to make their extra keys start generating scancodes.
+ * Originally, this was just confined to older laptops, but a few Acer laptops
+ * have turned up in 2007 that also need this again.
+ */
+ {
+ /* Acer Aspire 5100 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 5610 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5610"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 5630 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 5650 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 5680 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 5720 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5720"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer Aspire 9110 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer TravelMate 660 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 660"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer TravelMate 2490 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer TravelMate 4280 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4280"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_DRITEK)
+ },
+ {
+ /* Acer TravelMate P459-G2-M */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate P459-G2-M"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Amoi M636/A737 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Amoi Electronics CO.,LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "M636/A737 platform"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ByteSpeed LLC"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ByteSpeed Laptop C15B"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Compal HEL80I */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "COMPAL"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HEL80I"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ProLiant"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "8500"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ProLiant"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "DL760"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Advent 4211 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "DIXONSXP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Advent 4211"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Dell Embedded Box PC 3000 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Embedded Box PC 3000"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Dell XPS M1530 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "XPS M1530"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Dell Vostro 1510 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro1510"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Dell Vostro V13 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V13"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Dell Vostro 1320 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 1320"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Dell Vostro 1520 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 1520"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Dell Vostro 1720 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 1720"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Entroware Proteus */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Entroware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Proteus"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "EL07R4"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS)
+ },
+ /*
+ * Some Fujitsu notebooks are having trouble with touchpads if
+ * active multiplexing mode is activated. Luckily they don't have
+ * external PS/2 ports so we can safely disable it.
+ * ... apparently some Toshibas don't like MUX mode either and
+ * die horrible death on reboot.
+ */
+ {
+ /* Fujitsu Lifebook P7010/P7010D */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "P7010"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook P5020D */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P Series"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook S2000 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S Series"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook S6230 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S6230"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook T725 laptop */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T725"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Fujitsu Lifebook U745 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U745"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu T70H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVLT70H"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu A544 laptop */
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1111138 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK A544"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Fujitsu AH544 laptop */
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=69731 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK AH544"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Fujitsu U574 laptop */
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=69731 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U574"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Fujitsu UH554 laptop */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK UH544"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* Fujitsu Lifebook P7010 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "0000000000"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu-Siemens Lifebook T3010 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T3010"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu-Siemens Lifebook E4010 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E4010"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu-Siemens Amilo Pro 2010 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2010"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu-Siemens Amilo Pro 2030 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO PRO V2030"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook A574/H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVA0501PZ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Fujitsu Lifebook E5411 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK E5411"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOAUX)
+ },
+ {
+ /* Gigabyte M912 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "M912"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "01"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Gigabyte Spring Peak - defines wrong chassis type */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Spring Peak"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Gigabyte T1005 - defines wrong chassis type ("Other") */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "T1005"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Gigabyte T1005M/P - defines wrong chassis type ("Other") */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "T1005M/P"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ /*
+ * Some laptops need keyboard reset before probing for the trackpad to get
+ * it detected, initialised & finally work.
+ */
+ {
+ /* Gigabyte P35 v2 - Elantech touchpad */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "P35V2"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_KBDRESET)
+ },
+ {
+ /* Aorus branded Gigabyte X3 Plus - Elantech touchpad */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X3"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_KBDRESET)
+ },
+ {
+ /* Gigabyte P34 - Elantech touchpad */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "P34"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_KBDRESET)
+ },
+ {
+ /* Gigabyte P57 - Elantech touchpad */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GIGABYTE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "P57"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_KBDRESET)
+ },
+ {
+ /* Gericom Bellagio */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Gericom"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N34AS6"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Gigabyte M1022M netbook */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co.,Ltd."),
+ DMI_MATCH(DMI_BOARD_NAME, "M1022E"),
+ DMI_MATCH(DMI_BOARD_VERSION, "1.02"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv9700"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Rev 1"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /*
+ * HP Pavilion DV4017EA -
+ * errors on MUX ports are reported without raising AUXDATA
+ * causing "spurious NAK" messages.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Pavilion dv4000 (EA032EA#ABF)"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * HP Pavilion ZT1000 -
+ * like DV4017EA does not raise AUXERR for errors on MUX ports.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion Notebook PC"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "HP Pavilion Notebook ZT1000"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * HP Pavilion DV4270ca -
+ * like DV4017EA does not raise AUXERR for errors on MUX ports.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Pavilion dv4000 (EH476UA#ABL)"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Newer HP Pavilion dv4 models */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dv4 Notebook PC"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_NOTIMEOUT)
+ },
+ {
+ /* IBM 2656 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "IBM"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "2656"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Avatar AVIU-145A6 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "IC4I"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Intel MBO Desktop D845PESV */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "D845PESV"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * Intel NUC D54250WYK - does not have i8042 controller but
+ * declares PS/2 devices in DSDT.
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "D54250WYK"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOPNP)
+ },
+ {
+ /* Lenovo 3000 n100 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "076804U"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Lenovo XiaoXin Air 12 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "80UN"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Lenovo LaVie Z */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo LaVie Z"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Lenovo Ideapad U455 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "20046"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Lenovo ThinkPad L460 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad L460"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Lenovo ThinkPad Twist S230u */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "33474HU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* LG Electronics X110 */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LG Electronics Inc."),
+ DMI_MATCH(DMI_BOARD_NAME, "X110"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Medion Akoya Mini E1210 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "E1210"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* Medion Akoya E1222 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "E122X"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ {
+ /* MSI Wind U-100 */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "MICRO-STAR INTERNATIONAL CO., LTD"),
+ DMI_MATCH(DMI_BOARD_NAME, "U-100"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * No data is coming from the touchscreen unless KBC
+ * is in legacy mode.
+ */
+ /* Panasonic CF-29 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Matsushita"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CF-29"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Medion Akoya E7225 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Medion"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Akoya E7225"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "1.0"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Microsoft Virtual Machine */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "VS2005R2"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Medion MAM 2070 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MAM 2070"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "5a"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* TUXEDO BU1406 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Notebook"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N24_25BU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* OQO Model 01 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "OQO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ZEPTO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "00"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PEGATRON CORPORATION"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "C15B"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Acer Aspire 5 A515 */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "PK"),
+ DMI_MATCH(DMI_BOARD_NAME, "Grumpy_PK"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOPNP)
+ },
+ {
+ /* ULI EV4873 - AUX LOOP does not work properly */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ULI"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "EV4873"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "5a"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /*
+ * Arima-Rioworks HDAMB -
+ * AUX LOOP command does not raise AUX IRQ
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "RIOWORKS"),
+ DMI_MATCH(DMI_BOARD_NAME, "HDAMB"),
+ DMI_MATCH(DMI_BOARD_VERSION, "Rev E"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ {
+ /* Sharp Actius MM20 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SHARP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PC-MM20 Series"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * Sony Vaio FZ-240E -
+ * reset and GET ID commands issued via KBD port are
+ * sometimes being delivered to AUX3.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FZ240E"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * Most (all?) VAIOs do not have external PS/2 ports nor
+ * they implement active multiplexing properly, and
+ * MUX discovery usually messes up keyboard/touchpad.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_BOARD_NAME, "VAIO"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /* Sony Vaio FS-115b */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-FS115B"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ /*
+ * Sony Vaio VGN-CS series require MUX or the touch sensor
+ * buttons will disturb touchpad operation
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-CS"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_FORCEMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P10"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "EQUIUM A110"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE C850D"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
+ /*
+ * A lot of modern Clevo barebones have touchpad and/or keyboard issues
+ * after suspend fixable with nomux + reset + noloop + nopnp. Luckily,
+ * none of them have an external PS/2 port so this can safely be set for
+ * all of them. These two are based on a Clevo design, but have the
+ * board_name changed.
+ */
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "TUXEDO"),
+ DMI_MATCH(DMI_BOARD_NAME, "AURA1501"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "TUXEDO"),
+ DMI_MATCH(DMI_BOARD_NAME, "EDUBOOK1502"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /* Mivvy M310 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "VIOOO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N10"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS)
+ },
+ /*
+ * Some laptops need keyboard reset before probing for the trackpad to get
+ * it detected, initialised & finally work.
+ */
+ {
+ /* Schenker XMG C504 - Elantech touchpad */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "XMG"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "C504"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_KBDRESET)
+ },
+ {
+ /* Blue FB5601 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "blue"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FB5601"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "M606"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOLOOP)
+ },
+ /*
+ * A lot of modern Clevo barebones have touchpad and/or keyboard issues
+ * after suspend fixable with nomux + reset + noloop + nopnp. Luckily,
+ * none of them have an external PS/2 port so this can safely be set for
+ * all of them.
+ * Clevo barebones come with board_vendor and/or system_vendor set to
+ * either the very generic string "Notebook" and/or a different value
+ * for each individual reseller. The only somewhat universal way to
+ * identify them is by board_name.
+ */
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "N140CU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "N141CU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * Setting SERIO_QUIRK_NOMUX or SERIO_QUIRK_RESET_ALWAYS makes
+ * the keyboard very laggy for ~5 seconds after boot and
+ * sometimes also after resume.
+ * However both are required for the keyboard to not fail
+ * completely sometimes after boot or resume.
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "N150CU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * Setting SERIO_QUIRK_NOMUX or SERIO_QUIRK_RESET_ALWAYS makes
+ * the keyboard very laggy for ~5 seconds after boot and
+ * sometimes also after resume.
+ * However both are required for the keyboard to not fail
+ * completely sometimes after boot or resume.
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NHxxRZQ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ /*
+ * At least one modern Clevo barebone has the touchpad connected both
+ * via PS/2 and i2c interface. This causes a race condition between the
+ * psmouse and i2c-hid driver. Since the full capability of the touchpad
+ * is available via the i2c interface and the device has no external
+ * PS/2 port, it is safe to just ignore all ps2 mouses here to avoid
+ * this issue. The known affected device is the
+ * TUXEDO InfinityBook S17 Gen6 / Clevo NS70MU which comes with one of
+ * the two different dmi strings below. NS50MU is not a typo!
+ */
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NS50MU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOAUX | SERIO_QUIRK_NOMUX |
+ SERIO_QUIRK_RESET_ALWAYS | SERIO_QUIRK_NOLOOP |
+ SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NS50_70MU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOAUX | SERIO_QUIRK_NOMUX |
+ SERIO_QUIRK_RESET_ALWAYS | SERIO_QUIRK_NOLOOP |
+ SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NJ50_70CU"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * This is only a partial board_name and might be followed by
+ * another letter or number. DMI_MATCH however does do partial
+ * matching.
+ */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P65xH"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /* Clevo P650RS, 650RP6, Sager NP8152-S, and others */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P65xRP"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * This is only a partial board_name and might be followed by
+ * another letter or number. DMI_MATCH however does do partial
+ * matching.
+ */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P65_P67H"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * This is only a partial board_name and might be followed by
+ * another letter or number. DMI_MATCH however does do partial
+ * matching.
+ */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P65_67RP"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * This is only a partial board_name and might be followed by
+ * another letter or number. DMI_MATCH however does do partial
+ * matching.
+ */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P65_67RS"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ /*
+ * This is only a partial board_name and might be followed by
+ * another letter or number. DMI_MATCH however does do partial
+ * matching.
+ */
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "P67xRP"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "PB50_70DFx,DDx"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "PCX0DX"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ /* See comment on TUXEDO InfinityBook S17 Gen6 / Clevo NS70MU above */
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "PD5x_7xPNP_PNR_PNN_PNT"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOAUX)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "X170SM"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "X170KM-G"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX | SERIO_QUIRK_RESET_ALWAYS |
+ SERIO_QUIRK_NOLOOP | SERIO_QUIRK_NOPNP)
+ },
+ { }
+};
+
+#ifdef CONFIG_PNP
+static const struct dmi_system_id i8042_dmi_laptop_table[] __initconst = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
+ },
+ },
+ { }
+};
+#endif
+
+#endif /* CONFIG_X86 */
+
+#ifdef CONFIG_PNP
+#include <linux/pnp.h>
+
+static bool i8042_pnp_kbd_registered;
+static unsigned int i8042_pnp_kbd_devices;
+static bool i8042_pnp_aux_registered;
+static unsigned int i8042_pnp_aux_devices;
+
+static int i8042_pnp_command_reg;
+static int i8042_pnp_data_reg;
+static int i8042_pnp_kbd_irq;
+static int i8042_pnp_aux_irq;
+
+static char i8042_pnp_kbd_name[32];
+static char i8042_pnp_aux_name[32];
+
+static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size)
+{
+ strscpy(dst, "PNP:", dst_size);
+
+ while (id) {
+ strlcat(dst, " ", dst_size);
+ strlcat(dst, id->id, dst_size);
+ id = id->next;
+ }
+}
+
+static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
+{
+ if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
+ i8042_pnp_data_reg = pnp_port_start(dev,0);
+
+ if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
+ i8042_pnp_command_reg = pnp_port_start(dev, 1);
+
+ if (pnp_irq_valid(dev,0))
+ i8042_pnp_kbd_irq = pnp_irq(dev, 0);
+
+ strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name));
+ if (strlen(pnp_dev_name(dev))) {
+ strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
+ strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
+ }
+ i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id,
+ sizeof(i8042_kbd_firmware_id));
+ i8042_kbd_fwnode = dev_fwnode(&dev->dev);
+
+ /* Keyboard ports are always supposed to be wakeup-enabled */
+ device_set_wakeup_enable(&dev->dev, true);
+
+ i8042_pnp_kbd_devices++;
+ return 0;
+}
+
+static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
+{
+ if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
+ i8042_pnp_data_reg = pnp_port_start(dev,0);
+
+ if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
+ i8042_pnp_command_reg = pnp_port_start(dev, 1);
+
+ if (pnp_irq_valid(dev, 0))
+ i8042_pnp_aux_irq = pnp_irq(dev, 0);
+
+ strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name));
+ if (strlen(pnp_dev_name(dev))) {
+ strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
+ strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
+ }
+ i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id,
+ sizeof(i8042_aux_firmware_id));
+
+ i8042_pnp_aux_devices++;
+ return 0;
+}
+
+static const struct pnp_device_id pnp_kbd_devids[] = {
+ { .id = "PNP0300", .driver_data = 0 },
+ { .id = "PNP0301", .driver_data = 0 },
+ { .id = "PNP0302", .driver_data = 0 },
+ { .id = "PNP0303", .driver_data = 0 },
+ { .id = "PNP0304", .driver_data = 0 },
+ { .id = "PNP0305", .driver_data = 0 },
+ { .id = "PNP0306", .driver_data = 0 },
+ { .id = "PNP0309", .driver_data = 0 },
+ { .id = "PNP030a", .driver_data = 0 },
+ { .id = "PNP030b", .driver_data = 0 },
+ { .id = "PNP0320", .driver_data = 0 },
+ { .id = "PNP0343", .driver_data = 0 },
+ { .id = "PNP0344", .driver_data = 0 },
+ { .id = "PNP0345", .driver_data = 0 },
+ { .id = "CPQA0D7", .driver_data = 0 },
+ { .id = "", },
+};
+MODULE_DEVICE_TABLE(pnp, pnp_kbd_devids);
+
+static struct pnp_driver i8042_pnp_kbd_driver = {
+ .name = "i8042 kbd",
+ .id_table = pnp_kbd_devids,
+ .probe = i8042_pnp_kbd_probe,
+ .driver = {
+ .probe_type = PROBE_FORCE_SYNCHRONOUS,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static const struct pnp_device_id pnp_aux_devids[] = {
+ { .id = "AUI0200", .driver_data = 0 },
+ { .id = "FJC6000", .driver_data = 0 },
+ { .id = "FJC6001", .driver_data = 0 },
+ { .id = "PNP0f03", .driver_data = 0 },
+ { .id = "PNP0f0b", .driver_data = 0 },
+ { .id = "PNP0f0e", .driver_data = 0 },
+ { .id = "PNP0f12", .driver_data = 0 },
+ { .id = "PNP0f13", .driver_data = 0 },
+ { .id = "PNP0f19", .driver_data = 0 },
+ { .id = "PNP0f1c", .driver_data = 0 },
+ { .id = "SYN0801", .driver_data = 0 },
+ { .id = "", },
+};
+MODULE_DEVICE_TABLE(pnp, pnp_aux_devids);
+
+static struct pnp_driver i8042_pnp_aux_driver = {
+ .name = "i8042 aux",
+ .id_table = pnp_aux_devids,
+ .probe = i8042_pnp_aux_probe,
+ .driver = {
+ .probe_type = PROBE_FORCE_SYNCHRONOUS,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static void i8042_pnp_exit(void)
+{
+ if (i8042_pnp_kbd_registered) {
+ i8042_pnp_kbd_registered = false;
+ pnp_unregister_driver(&i8042_pnp_kbd_driver);
+ }
+
+ if (i8042_pnp_aux_registered) {
+ i8042_pnp_aux_registered = false;
+ pnp_unregister_driver(&i8042_pnp_aux_driver);
+ }
+}
+
+static int __init i8042_pnp_init(void)
+{
+ char kbd_irq_str[4] = { 0 }, aux_irq_str[4] = { 0 };
+ bool pnp_data_busted = false;
+ int err;
+
+ if (i8042_nopnp) {
+ pr_info("PNP detection disabled\n");
+ return 0;
+ }
+
+ err = pnp_register_driver(&i8042_pnp_kbd_driver);
+ if (!err)
+ i8042_pnp_kbd_registered = true;
+
+ err = pnp_register_driver(&i8042_pnp_aux_driver);
+ if (!err)
+ i8042_pnp_aux_registered = true;
+
+ if (!i8042_pnp_kbd_devices && !i8042_pnp_aux_devices) {
+ i8042_pnp_exit();
+#if defined(__ia64__)
+ return -ENODEV;
+#else
+ pr_info("PNP: No PS/2 controller found.\n");
+#if defined(__loongarch__)
+ if (acpi_disabled == 0)
+ return -ENODEV;
+#else
+ if (x86_platform.legacy.i8042 !=
+ X86_LEGACY_I8042_EXPECTED_PRESENT)
+ return -ENODEV;
+#endif
+ pr_info("Probing ports directly.\n");
+ return 0;
+#endif
+ }
+
+ if (i8042_pnp_kbd_devices)
+ snprintf(kbd_irq_str, sizeof(kbd_irq_str),
+ "%d", i8042_pnp_kbd_irq);
+ if (i8042_pnp_aux_devices)
+ snprintf(aux_irq_str, sizeof(aux_irq_str),
+ "%d", i8042_pnp_aux_irq);
+
+ pr_info("PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %s%s%s\n",
+ i8042_pnp_kbd_name, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
+ i8042_pnp_aux_name,
+ i8042_pnp_data_reg, i8042_pnp_command_reg,
+ kbd_irq_str, (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
+ aux_irq_str);
+
+#if defined(__ia64__)
+ if (!i8042_pnp_kbd_devices)
+ i8042_nokbd = true;
+ if (!i8042_pnp_aux_devices)
+ i8042_noaux = true;
+#endif
+
+ if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) &&
+ i8042_pnp_data_reg != i8042_data_reg) ||
+ !i8042_pnp_data_reg) {
+ pr_warn("PNP: PS/2 controller has invalid data port %#x; using default %#x\n",
+ i8042_pnp_data_reg, i8042_data_reg);
+ i8042_pnp_data_reg = i8042_data_reg;
+ pnp_data_busted = true;
+ }
+
+ if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) &&
+ i8042_pnp_command_reg != i8042_command_reg) ||
+ !i8042_pnp_command_reg) {
+ pr_warn("PNP: PS/2 controller has invalid command port %#x; using default %#x\n",
+ i8042_pnp_command_reg, i8042_command_reg);
+ i8042_pnp_command_reg = i8042_command_reg;
+ pnp_data_busted = true;
+ }
+
+ if (!i8042_nokbd && !i8042_pnp_kbd_irq) {
+ pr_warn("PNP: PS/2 controller doesn't have KBD irq; using default %d\n",
+ i8042_kbd_irq);
+ i8042_pnp_kbd_irq = i8042_kbd_irq;
+ pnp_data_busted = true;
+ }
+
+ if (!i8042_noaux && !i8042_pnp_aux_irq) {
+ if (!pnp_data_busted && i8042_pnp_kbd_irq) {
+ pr_warn("PNP: PS/2 appears to have AUX port disabled, "
+ "if this is incorrect please boot with i8042.nopnp\n");
+ i8042_noaux = true;
+ } else {
+ pr_warn("PNP: PS/2 controller doesn't have AUX irq; using default %d\n",
+ i8042_aux_irq);
+ i8042_pnp_aux_irq = i8042_aux_irq;
+ }
+ }
+
+ i8042_data_reg = i8042_pnp_data_reg;
+ i8042_command_reg = i8042_pnp_command_reg;
+ i8042_kbd_irq = i8042_pnp_kbd_irq;
+ i8042_aux_irq = i8042_pnp_aux_irq;
+
+#ifdef CONFIG_X86
+ i8042_bypass_aux_irq_test = !pnp_data_busted &&
+ dmi_check_system(i8042_dmi_laptop_table);
+#endif
+
+ return 0;
+}
+
+#else /* !CONFIG_PNP */
+static inline int i8042_pnp_init(void) { return 0; }
+static inline void i8042_pnp_exit(void) { }
+#endif /* CONFIG_PNP */
+
+
+#ifdef CONFIG_X86
+static void __init i8042_check_quirks(void)
+{
+ const struct dmi_system_id *device_quirk_info;
+ uintptr_t quirks;
+
+ device_quirk_info = dmi_first_match(i8042_dmi_quirk_table);
+ if (!device_quirk_info)
+ return;
+
+ quirks = (uintptr_t)device_quirk_info->driver_data;
+
+ if (quirks & SERIO_QUIRK_NOKBD)
+ i8042_nokbd = true;
+ if (quirks & SERIO_QUIRK_NOAUX)
+ i8042_noaux = true;
+ if (quirks & SERIO_QUIRK_NOMUX)
+ i8042_nomux = true;
+ if (quirks & SERIO_QUIRK_FORCEMUX)
+ i8042_nomux = false;
+ if (quirks & SERIO_QUIRK_UNLOCK)
+ i8042_unlock = true;
+ if (quirks & SERIO_QUIRK_PROBE_DEFER)
+ i8042_probe_defer = true;
+ /* Honor module parameter when value is not default */
+ if (i8042_reset == I8042_RESET_DEFAULT) {
+ if (quirks & SERIO_QUIRK_RESET_ALWAYS)
+ i8042_reset = I8042_RESET_ALWAYS;
+ if (quirks & SERIO_QUIRK_RESET_NEVER)
+ i8042_reset = I8042_RESET_NEVER;
+ }
+ if (quirks & SERIO_QUIRK_DIECT)
+ i8042_direct = true;
+ if (quirks & SERIO_QUIRK_DUMBKBD)
+ i8042_dumbkbd = true;
+ if (quirks & SERIO_QUIRK_NOLOOP)
+ i8042_noloop = true;
+ if (quirks & SERIO_QUIRK_NOTIMEOUT)
+ i8042_notimeout = true;
+ if (quirks & SERIO_QUIRK_KBDRESET)
+ i8042_kbdreset = true;
+ if (quirks & SERIO_QUIRK_DRITEK)
+ i8042_dritek = true;
+#ifdef CONFIG_PNP
+ if (quirks & SERIO_QUIRK_NOPNP)
+ i8042_nopnp = true;
+#endif
+}
+#else
+static inline void i8042_check_quirks(void) {}
+#endif
+
+static int __init i8042_platform_init(void)
+{
+ int retval;
+
+#ifdef CONFIG_X86
+ u8 a20_on = 0xdf;
+ /* Just return if platform does not have i8042 controller */
+ if (x86_platform.legacy.i8042 == X86_LEGACY_I8042_PLATFORM_ABSENT)
+ return -ENODEV;
+#endif
+
+/*
+ * On ix86 platforms touching the i8042 data register region can do really
+ * bad things. Because of this the region is always reserved on ix86 boxes.
+ *
+ * if (!request_region(I8042_DATA_REG, 16, "i8042"))
+ * return -EBUSY;
+ */
+
+ i8042_kbd_irq = I8042_MAP_IRQ(1);
+ i8042_aux_irq = I8042_MAP_IRQ(12);
+
+#if defined(__ia64__)
+ i8042_reset = I8042_RESET_ALWAYS;
+#endif
+
+ i8042_check_quirks();
+
+ pr_debug("Active quirks (empty means none):%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+ i8042_nokbd ? " nokbd" : "",
+ i8042_noaux ? " noaux" : "",
+ i8042_nomux ? " nomux" : "",
+ i8042_unlock ? " unlock" : "",
+ i8042_probe_defer ? "probe_defer" : "",
+ i8042_reset == I8042_RESET_DEFAULT ?
+ "" : i8042_reset == I8042_RESET_ALWAYS ?
+ " reset_always" : " reset_never",
+ i8042_direct ? " direct" : "",
+ i8042_dumbkbd ? " dumbkbd" : "",
+ i8042_noloop ? " noloop" : "",
+ i8042_notimeout ? " notimeout" : "",
+ i8042_kbdreset ? " kbdreset" : "",
+#ifdef CONFIG_X86
+ i8042_dritek ? " dritek" : "",
+#else
+ "",
+#endif
+#ifdef CONFIG_PNP
+ i8042_nopnp ? " nopnp" : "");
+#else
+ "");
+#endif
+
+ retval = i8042_pnp_init();
+ if (retval)
+ return retval;
+
+#ifdef CONFIG_X86
+ /*
+ * A20 was already enabled during early kernel init. But some buggy
+ * BIOSes (in MSI Laptops) require A20 to be enabled using 8042 to
+ * resume from S3. So we do it here and hope that nothing breaks.
+ */
+ i8042_command(&a20_on, 0x10d1);
+ i8042_command(NULL, 0x00ff); /* Null command for SMM firmware */
+#endif /* CONFIG_X86 */
+
+ return retval;
+}
+
+static inline void i8042_platform_exit(void)
+{
+ i8042_pnp_exit();
+}
+
+#endif /* _I8042_ACPIPNPIO_H */
diff --git a/drivers/input/serio/i8042-io.h b/drivers/input/serio/i8042-io.h
new file mode 100644
index 000000000..64590b86e
--- /dev/null
+++ b/drivers/input/serio/i8042-io.h
@@ -0,0 +1,89 @@
+/* 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.
+ */
+
+#ifdef __alpha__
+# define I8042_KBD_IRQ 1
+# define I8042_AUX_IRQ (RTC_PORT(0) == 0x170 ? 9 : 12) /* Jensen is special */
+#elif defined(__arm__)
+/* defined in include/asm-arm/arch-xxx/irqs.h */
+#include <asm/irq.h>
+#elif defined(CONFIG_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 */
diff --git a/drivers/input/serio/i8042-ip22io.h b/drivers/input/serio/i8042-ip22io.h
new file mode 100644
index 000000000..6c7efa017
--- /dev/null
+++ b/drivers/input/serio/i8042-ip22io.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 */
diff --git a/drivers/input/serio/i8042-jazzio.h b/drivers/input/serio/i8042-jazzio.h
new file mode 100644
index 000000000..4c2a96f91
--- /dev/null
+++ b/drivers/input/serio/i8042-jazzio.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 */
diff --git a/drivers/input/serio/i8042-snirm.h b/drivers/input/serio/i8042-snirm.h
new file mode 100644
index 000000000..4b7136704
--- /dev/null
+++ b/drivers/input/serio/i8042-snirm.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 */
diff --git a/drivers/input/serio/i8042-sparcio.h b/drivers/input/serio/i8042-sparcio.h
new file mode 100644
index 000000000..c712c1fe0
--- /dev/null
+++ b/drivers/input/serio/i8042-sparcio.h
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _I8042_SPARCIO_H
+#define _I8042_SPARCIO_H
+
+#include <linux/of_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 int sparc_i8042_remove(struct platform_device *op)
+{
+ of_iounmap(kbd_res, kbd_iobase, 8);
+
+ return 0;
+}
+
+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;
+ const char *name;
+ bool is_mr_coffee;
+
+ root = of_find_node_by_path("/");
+
+ name = of_get_property(root, "name", NULL);
+ is_mr_coffee = name && !strcmp(name, "SUNW,JavaStation-1");
+
+ of_node_put(root);
+
+ return is_mr_coffee;
+}
+
+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 */
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
new file mode 100644
index 000000000..6dac7c185
--- /dev/null
+++ b/drivers/input/serio/i8042.c
@@ -0,0 +1,1671 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * i8042 keyboard and mouse controller driver for Linux
+ *
+ * Copyright (c) 1999-2004 Vojtech Pavlik
+ */
+
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/err.h>
+#include <linux/rcupdate.h>
+#include <linux/platform_device.h>
+#include <linux/i8042.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/property.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
+MODULE_DESCRIPTION("i8042 keyboard and mouse controller driver");
+MODULE_LICENSE("GPL");
+
+static bool i8042_nokbd;
+module_param_named(nokbd, i8042_nokbd, bool, 0);
+MODULE_PARM_DESC(nokbd, "Do not probe or use KBD port.");
+
+static bool i8042_noaux;
+module_param_named(noaux, i8042_noaux, bool, 0);
+MODULE_PARM_DESC(noaux, "Do not probe or use AUX (mouse) port.");
+
+static bool i8042_nomux;
+module_param_named(nomux, i8042_nomux, bool, 0);
+MODULE_PARM_DESC(nomux, "Do not check whether an active multiplexing controller is present.");
+
+static bool i8042_unlock;
+module_param_named(unlock, i8042_unlock, bool, 0);
+MODULE_PARM_DESC(unlock, "Ignore keyboard lock.");
+
+static bool i8042_probe_defer;
+module_param_named(probe_defer, i8042_probe_defer, bool, 0);
+MODULE_PARM_DESC(probe_defer, "Allow deferred probing.");
+
+enum i8042_controller_reset_mode {
+ I8042_RESET_NEVER,
+ I8042_RESET_ALWAYS,
+ I8042_RESET_ON_S2RAM,
+#define I8042_RESET_DEFAULT I8042_RESET_ON_S2RAM
+};
+static enum i8042_controller_reset_mode i8042_reset = I8042_RESET_DEFAULT;
+static int i8042_set_reset(const char *val, const struct kernel_param *kp)
+{
+ enum i8042_controller_reset_mode *arg = kp->arg;
+ int error;
+ bool reset;
+
+ if (val) {
+ error = kstrtobool(val, &reset);
+ if (error)
+ return error;
+ } else {
+ reset = true;
+ }
+
+ *arg = reset ? I8042_RESET_ALWAYS : I8042_RESET_NEVER;
+ return 0;
+}
+
+static const struct kernel_param_ops param_ops_reset_param = {
+ .flags = KERNEL_PARAM_OPS_FL_NOARG,
+ .set = i8042_set_reset,
+};
+#define param_check_reset_param(name, p) \
+ __param_check(name, p, enum i8042_controller_reset_mode)
+module_param_named(reset, i8042_reset, reset_param, 0);
+MODULE_PARM_DESC(reset, "Reset controller on resume, cleanup or both");
+
+static bool i8042_direct;
+module_param_named(direct, i8042_direct, bool, 0);
+MODULE_PARM_DESC(direct, "Put keyboard port into non-translated mode.");
+
+static bool i8042_dumbkbd;
+module_param_named(dumbkbd, i8042_dumbkbd, bool, 0);
+MODULE_PARM_DESC(dumbkbd, "Pretend that controller can only read data from keyboard");
+
+static bool i8042_noloop;
+module_param_named(noloop, i8042_noloop, bool, 0);
+MODULE_PARM_DESC(noloop, "Disable the AUX Loopback command while probing for the AUX port");
+
+static bool i8042_notimeout;
+module_param_named(notimeout, i8042_notimeout, bool, 0);
+MODULE_PARM_DESC(notimeout, "Ignore timeouts signalled by i8042");
+
+static bool i8042_kbdreset;
+module_param_named(kbdreset, i8042_kbdreset, bool, 0);
+MODULE_PARM_DESC(kbdreset, "Reset device connected to KBD port");
+
+#ifdef CONFIG_X86
+static bool i8042_dritek;
+module_param_named(dritek, i8042_dritek, bool, 0);
+MODULE_PARM_DESC(dritek, "Force enable the Dritek keyboard extension");
+#endif
+
+#ifdef CONFIG_PNP
+static bool i8042_nopnp;
+module_param_named(nopnp, i8042_nopnp, bool, 0);
+MODULE_PARM_DESC(nopnp, "Do not use PNP to detect controller settings");
+#endif
+
+#define DEBUG
+#ifdef DEBUG
+static bool i8042_debug;
+module_param_named(debug, i8042_debug, bool, 0600);
+MODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off");
+
+static bool i8042_unmask_kbd_data;
+module_param_named(unmask_kbd_data, i8042_unmask_kbd_data, bool, 0600);
+MODULE_PARM_DESC(unmask_kbd_data, "Unconditional enable (may reveal sensitive data) of normally sanitize-filtered kbd data traffic debug log [pre-condition: i8042.debug=1 enabled]");
+#endif
+
+static bool i8042_present;
+static bool i8042_bypass_aux_irq_test;
+static char i8042_kbd_firmware_id[128];
+static char i8042_aux_firmware_id[128];
+static struct fwnode_handle *i8042_kbd_fwnode;
+
+#include "i8042.h"
+
+/*
+ * i8042_lock protects serialization between i8042_command and
+ * the interrupt handler.
+ */
+static DEFINE_SPINLOCK(i8042_lock);
+
+/*
+ * Writers to AUX and KBD ports as well as users issuing i8042_command
+ * directly should acquire i8042_mutex (by means of calling
+ * i8042_lock_chip() and i8042_unlock_chip() helpers) to ensure that
+ * they do not disturb each other (unfortunately in many i8042
+ * implementations write to one of the ports will immediately abort
+ * command that is being processed by another port).
+ */
+static DEFINE_MUTEX(i8042_mutex);
+
+struct i8042_port {
+ struct serio *serio;
+ int irq;
+ bool exists;
+ bool driver_bound;
+ signed char mux;
+};
+
+#define I8042_KBD_PORT_NO 0
+#define I8042_AUX_PORT_NO 1
+#define I8042_MUX_PORT_NO 2
+#define I8042_NUM_PORTS (I8042_NUM_MUX_PORTS + 2)
+
+static struct i8042_port i8042_ports[I8042_NUM_PORTS];
+
+static unsigned char i8042_initial_ctr;
+static unsigned char i8042_ctr;
+static bool i8042_mux_present;
+static bool i8042_kbd_irq_registered;
+static bool i8042_aux_irq_registered;
+static unsigned char i8042_suppress_kbd_ack;
+static struct platform_device *i8042_platform_device;
+static struct notifier_block i8042_kbd_bind_notifier_block;
+
+static irqreturn_t i8042_interrupt(int irq, void *dev_id);
+static bool (*i8042_platform_filter)(unsigned char data, unsigned char str,
+ struct serio *serio);
+
+void i8042_lock_chip(void)
+{
+ mutex_lock(&i8042_mutex);
+}
+EXPORT_SYMBOL(i8042_lock_chip);
+
+void i8042_unlock_chip(void)
+{
+ mutex_unlock(&i8042_mutex);
+}
+EXPORT_SYMBOL(i8042_unlock_chip);
+
+int i8042_install_filter(bool (*filter)(unsigned char data, unsigned char str,
+ struct serio *serio))
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ if (i8042_platform_filter) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ i8042_platform_filter = filter;
+
+out:
+ spin_unlock_irqrestore(&i8042_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(i8042_install_filter);
+
+int i8042_remove_filter(bool (*filter)(unsigned char data, unsigned char str,
+ struct serio *port))
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ if (i8042_platform_filter != filter) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ i8042_platform_filter = NULL;
+
+out:
+ spin_unlock_irqrestore(&i8042_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(i8042_remove_filter);
+
+/*
+ * The i8042_wait_read() and i8042_wait_write functions wait for the i8042 to
+ * be ready for reading values from it / writing values to it.
+ * Called always with i8042_lock held.
+ */
+
+static int i8042_wait_read(void)
+{
+ int i = 0;
+
+ while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) {
+ udelay(50);
+ i++;
+ }
+ return -(i == I8042_CTL_TIMEOUT);
+}
+
+static int i8042_wait_write(void)
+{
+ int i = 0;
+
+ while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) {
+ udelay(50);
+ i++;
+ }
+ return -(i == I8042_CTL_TIMEOUT);
+}
+
+/*
+ * i8042_flush() flushes all data that may be in the keyboard and mouse buffers
+ * of the i8042 down the toilet.
+ */
+
+static int i8042_flush(void)
+{
+ unsigned long flags;
+ unsigned char data, str;
+ int count = 0;
+ int retval = 0;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ while ((str = i8042_read_status()) & I8042_STR_OBF) {
+ if (count++ < I8042_BUFFER_SIZE) {
+ udelay(50);
+ data = i8042_read_data();
+ dbg("%02x <- i8042 (flush, %s)\n",
+ data, str & I8042_STR_AUXDATA ? "aux" : "kbd");
+ } else {
+ retval = -EIO;
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ return retval;
+}
+
+/*
+ * i8042_command() executes a command on the i8042. It also sends the input
+ * parameter(s) of the commands to it, and receives the output value(s). The
+ * parameters are to be stored in the param array, and the output is placed
+ * into the same array. The number of the parameters and output values is
+ * encoded in bits 8-11 of the command number.
+ */
+
+static int __i8042_command(unsigned char *param, int command)
+{
+ int i, error;
+
+ if (i8042_noloop && command == I8042_CMD_AUX_LOOP)
+ return -1;
+
+ error = i8042_wait_write();
+ if (error)
+ return error;
+
+ dbg("%02x -> i8042 (command)\n", command & 0xff);
+ i8042_write_command(command & 0xff);
+
+ for (i = 0; i < ((command >> 12) & 0xf); i++) {
+ error = i8042_wait_write();
+ if (error) {
+ dbg(" -- i8042 (wait write timeout)\n");
+ return error;
+ }
+ dbg("%02x -> i8042 (parameter)\n", param[i]);
+ i8042_write_data(param[i]);
+ }
+
+ for (i = 0; i < ((command >> 8) & 0xf); i++) {
+ error = i8042_wait_read();
+ if (error) {
+ dbg(" -- i8042 (wait read timeout)\n");
+ return error;
+ }
+
+ if (command == I8042_CMD_AUX_LOOP &&
+ !(i8042_read_status() & I8042_STR_AUXDATA)) {
+ dbg(" -- i8042 (auxerr)\n");
+ return -1;
+ }
+
+ param[i] = i8042_read_data();
+ dbg("%02x <- i8042 (return)\n", param[i]);
+ }
+
+ return 0;
+}
+
+int i8042_command(unsigned char *param, int command)
+{
+ unsigned long flags;
+ int retval;
+
+ if (!i8042_present)
+ return -1;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+ retval = __i8042_command(param, command);
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ return retval;
+}
+EXPORT_SYMBOL(i8042_command);
+
+/*
+ * i8042_kbd_write() sends a byte out through the keyboard interface.
+ */
+
+static int i8042_kbd_write(struct serio *port, unsigned char c)
+{
+ unsigned long flags;
+ int retval = 0;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ if (!(retval = i8042_wait_write())) {
+ dbg("%02x -> i8042 (kbd-data)\n", c);
+ i8042_write_data(c);
+ }
+
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ return retval;
+}
+
+/*
+ * i8042_aux_write() sends a byte out through the aux interface.
+ */
+
+static int i8042_aux_write(struct serio *serio, unsigned char c)
+{
+ struct i8042_port *port = serio->port_data;
+
+ return i8042_command(&c, port->mux == -1 ?
+ I8042_CMD_AUX_SEND :
+ I8042_CMD_MUX_SEND + port->mux);
+}
+
+
+/*
+ * i8042_port_close attempts to clear AUX or KBD port state by disabling
+ * and then re-enabling it.
+ */
+
+static void i8042_port_close(struct serio *serio)
+{
+ int irq_bit;
+ int disable_bit;
+ const char *port_name;
+
+ if (serio == i8042_ports[I8042_AUX_PORT_NO].serio) {
+ irq_bit = I8042_CTR_AUXINT;
+ disable_bit = I8042_CTR_AUXDIS;
+ port_name = "AUX";
+ } else {
+ irq_bit = I8042_CTR_KBDINT;
+ disable_bit = I8042_CTR_KBDDIS;
+ port_name = "KBD";
+ }
+
+ i8042_ctr &= ~irq_bit;
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
+ pr_warn("Can't write CTR while closing %s port\n", port_name);
+
+ udelay(50);
+
+ i8042_ctr &= ~disable_bit;
+ i8042_ctr |= irq_bit;
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
+ pr_err("Can't reactivate %s port\n", port_name);
+
+ /*
+ * See if there is any data appeared while we were messing with
+ * port state.
+ */
+ i8042_interrupt(0, NULL);
+}
+
+/*
+ * i8042_start() is called by serio core when port is about to finish
+ * registering. It will mark port as existing so i8042_interrupt can
+ * start sending data through it.
+ */
+static int i8042_start(struct serio *serio)
+{
+ struct i8042_port *port = serio->port_data;
+
+ device_set_wakeup_capable(&serio->dev, true);
+
+ /*
+ * On platforms using suspend-to-idle, allow the keyboard to
+ * wake up the system from sleep by enabling keyboard wakeups
+ * by default. This is consistent with keyboard wakeup
+ * behavior on many platforms using suspend-to-RAM (ACPI S3)
+ * by default.
+ */
+ if (pm_suspend_default_s2idle() &&
+ serio == i8042_ports[I8042_KBD_PORT_NO].serio) {
+ device_set_wakeup_enable(&serio->dev, true);
+ }
+
+ spin_lock_irq(&i8042_lock);
+ port->exists = true;
+ spin_unlock_irq(&i8042_lock);
+
+ return 0;
+}
+
+/*
+ * i8042_stop() marks serio port as non-existing so i8042_interrupt
+ * will not try to send data to the port that is about to go away.
+ * The function is called by serio core as part of unregister procedure.
+ */
+static void i8042_stop(struct serio *serio)
+{
+ struct i8042_port *port = serio->port_data;
+
+ spin_lock_irq(&i8042_lock);
+ port->exists = false;
+ port->serio = NULL;
+ spin_unlock_irq(&i8042_lock);
+
+ /*
+ * We need to make sure that interrupt handler finishes using
+ * our serio port before we return from this function.
+ * We synchronize with both AUX and KBD IRQs because there is
+ * a (very unlikely) chance that AUX IRQ is raised for KBD port
+ * and vice versa.
+ */
+ synchronize_irq(I8042_AUX_IRQ);
+ synchronize_irq(I8042_KBD_IRQ);
+}
+
+/*
+ * i8042_filter() filters out unwanted bytes from the input data stream.
+ * It is called from i8042_interrupt and thus is running with interrupts
+ * off and i8042_lock held.
+ */
+static bool i8042_filter(unsigned char data, unsigned char str,
+ struct serio *serio)
+{
+ if (unlikely(i8042_suppress_kbd_ack)) {
+ if ((~str & I8042_STR_AUXDATA) &&
+ (data == 0xfa || data == 0xfe)) {
+ i8042_suppress_kbd_ack--;
+ dbg("Extra keyboard ACK - filtered out\n");
+ return true;
+ }
+ }
+
+ if (i8042_platform_filter && i8042_platform_filter(data, str, serio)) {
+ dbg("Filtered out by platform filter\n");
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * i8042_interrupt() is the most important function in this driver -
+ * it handles the interrupts from the i8042, and sends incoming bytes
+ * to the upper layers.
+ */
+
+static irqreturn_t i8042_interrupt(int irq, void *dev_id)
+{
+ struct i8042_port *port;
+ struct serio *serio;
+ unsigned long flags;
+ unsigned char str, data;
+ unsigned int dfl;
+ unsigned int port_no;
+ bool filtered;
+ int ret = 1;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ str = i8042_read_status();
+ if (unlikely(~str & I8042_STR_OBF)) {
+ spin_unlock_irqrestore(&i8042_lock, flags);
+ if (irq)
+ dbg("Interrupt %d, without any data\n", irq);
+ ret = 0;
+ goto out;
+ }
+
+ data = i8042_read_data();
+
+ if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
+ static unsigned long last_transmit;
+ static unsigned char last_str;
+
+ dfl = 0;
+ if (str & I8042_STR_MUXERR) {
+ dbg("MUX error, status is %02x, data is %02x\n",
+ str, data);
+/*
+ * When MUXERR condition is signalled the data register can only contain
+ * 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately
+ * it is not always the case. Some KBCs also report 0xfc when there is
+ * nothing connected to the port while others sometimes get confused which
+ * port the data came from and signal error leaving the data intact. They
+ * _do not_ revert to legacy mode (actually I've never seen KBC reverting
+ * to legacy mode yet, when we see one we'll add proper handling).
+ * Anyway, we process 0xfc, 0xfd, 0xfe and 0xff as timeouts, and for the
+ * rest assume that the data came from the same serio last byte
+ * was transmitted (if transmission happened not too long ago).
+ */
+
+ switch (data) {
+ default:
+ if (time_before(jiffies, last_transmit + HZ/10)) {
+ str = last_str;
+ break;
+ }
+ fallthrough; /* report timeout */
+ case 0xfc:
+ case 0xfd:
+ case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break;
+ case 0xff: dfl = SERIO_PARITY; data = 0xfe; break;
+ }
+ }
+
+ port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3);
+ last_str = str;
+ last_transmit = jiffies;
+ } else {
+
+ dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
+ ((str & I8042_STR_TIMEOUT && !i8042_notimeout) ? SERIO_TIMEOUT : 0);
+
+ port_no = (str & I8042_STR_AUXDATA) ?
+ I8042_AUX_PORT_NO : I8042_KBD_PORT_NO;
+ }
+
+ port = &i8042_ports[port_no];
+ serio = port->exists ? port->serio : NULL;
+
+ filter_dbg(port->driver_bound, data, "<- i8042 (interrupt, %d, %d%s%s)\n",
+ port_no, irq,
+ dfl & SERIO_PARITY ? ", bad parity" : "",
+ dfl & SERIO_TIMEOUT ? ", timeout" : "");
+
+ filtered = i8042_filter(data, str, serio);
+
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ if (likely(serio && !filtered))
+ serio_interrupt(serio, data, dfl);
+
+ out:
+ return IRQ_RETVAL(ret);
+}
+
+/*
+ * i8042_enable_kbd_port enables keyboard port on chip
+ */
+
+static int i8042_enable_kbd_port(void)
+{
+ i8042_ctr &= ~I8042_CTR_KBDDIS;
+ i8042_ctr |= I8042_CTR_KBDINT;
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ i8042_ctr &= ~I8042_CTR_KBDINT;
+ i8042_ctr |= I8042_CTR_KBDDIS;
+ pr_err("Failed to enable KBD port\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * i8042_enable_aux_port enables AUX (mouse) port on chip
+ */
+
+static int i8042_enable_aux_port(void)
+{
+ i8042_ctr &= ~I8042_CTR_AUXDIS;
+ i8042_ctr |= I8042_CTR_AUXINT;
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ i8042_ctr &= ~I8042_CTR_AUXINT;
+ i8042_ctr |= I8042_CTR_AUXDIS;
+ pr_err("Failed to enable AUX port\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * i8042_enable_mux_ports enables 4 individual AUX ports after
+ * the controller has been switched into Multiplexed mode
+ */
+
+static int i8042_enable_mux_ports(void)
+{
+ unsigned char param;
+ int i;
+
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++) {
+ i8042_command(&param, I8042_CMD_MUX_PFX + i);
+ i8042_command(&param, I8042_CMD_AUX_ENABLE);
+ }
+
+ return i8042_enable_aux_port();
+}
+
+/*
+ * i8042_set_mux_mode checks whether the controller has an
+ * active multiplexor and puts the chip into Multiplexed (true)
+ * or Legacy (false) mode.
+ */
+
+static int i8042_set_mux_mode(bool multiplex, unsigned char *mux_version)
+{
+
+ unsigned char param, val;
+/*
+ * Get rid of bytes in the queue.
+ */
+
+ i8042_flush();
+
+/*
+ * Internal loopback test - send three bytes, they should come back from the
+ * mouse interface, the last should be version.
+ */
+
+ param = val = 0xf0;
+ if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != val)
+ return -1;
+ param = val = multiplex ? 0x56 : 0xf6;
+ if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != val)
+ return -1;
+ param = val = multiplex ? 0xa4 : 0xa5;
+ if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param == val)
+ return -1;
+
+/*
+ * Workaround for interference with USB Legacy emulation
+ * that causes a v10.12 MUX to be found.
+ */
+ if (param == 0xac)
+ return -1;
+
+ if (mux_version)
+ *mux_version = param;
+
+ return 0;
+}
+
+/*
+ * i8042_check_mux() checks whether the controller supports the PS/2 Active
+ * Multiplexing specification by Synaptics, Phoenix, Insyde and
+ * LCS/Telegraphics.
+ */
+
+static int i8042_check_mux(void)
+{
+ unsigned char mux_version;
+
+ if (i8042_set_mux_mode(true, &mux_version))
+ return -1;
+
+ pr_info("Detected active multiplexing controller, rev %d.%d\n",
+ (mux_version >> 4) & 0xf, mux_version & 0xf);
+
+/*
+ * Disable all muxed ports by disabling AUX.
+ */
+ i8042_ctr |= I8042_CTR_AUXDIS;
+ i8042_ctr &= ~I8042_CTR_AUXINT;
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ pr_err("Failed to disable AUX port, can't use MUX\n");
+ return -EIO;
+ }
+
+ i8042_mux_present = true;
+
+ return 0;
+}
+
+/*
+ * The following is used to test AUX IRQ delivery.
+ */
+static struct completion i8042_aux_irq_delivered;
+static bool i8042_irq_being_tested;
+
+static irqreturn_t i8042_aux_test_irq(int irq, void *dev_id)
+{
+ unsigned long flags;
+ unsigned char str, data;
+ int ret = 0;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+ str = i8042_read_status();
+ if (str & I8042_STR_OBF) {
+ data = i8042_read_data();
+ dbg("%02x <- i8042 (aux_test_irq, %s)\n",
+ data, str & I8042_STR_AUXDATA ? "aux" : "kbd");
+ if (i8042_irq_being_tested &&
+ data == 0xa5 && (str & I8042_STR_AUXDATA))
+ complete(&i8042_aux_irq_delivered);
+ ret = 1;
+ }
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ return IRQ_RETVAL(ret);
+}
+
+/*
+ * i8042_toggle_aux - enables or disables AUX port on i8042 via command and
+ * verifies success by readinng CTR. Used when testing for presence of AUX
+ * port.
+ */
+static int i8042_toggle_aux(bool on)
+{
+ unsigned char param;
+ int i;
+
+ if (i8042_command(&param,
+ on ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE))
+ return -1;
+
+ /* some chips need some time to set the I8042_CTR_AUXDIS bit */
+ for (i = 0; i < 100; i++) {
+ udelay(50);
+
+ if (i8042_command(&param, I8042_CMD_CTL_RCTR))
+ return -1;
+
+ if (!(param & I8042_CTR_AUXDIS) == on)
+ return 0;
+ }
+
+ return -1;
+}
+
+/*
+ * i8042_check_aux() applies as much paranoia as it can at detecting
+ * the presence of an AUX interface.
+ */
+
+static int i8042_check_aux(void)
+{
+ int retval = -1;
+ bool irq_registered = false;
+ bool aux_loop_broken = false;
+ unsigned long flags;
+ unsigned char param;
+
+/*
+ * Get rid of bytes in the queue.
+ */
+
+ i8042_flush();
+
+/*
+ * Internal loopback test - filters out AT-type i8042's. Unfortunately
+ * SiS screwed up and their 5597 doesn't support the LOOP command even
+ * though it has an AUX port.
+ */
+
+ param = 0x5a;
+ retval = i8042_command(&param, I8042_CMD_AUX_LOOP);
+ if (retval || param != 0x5a) {
+
+/*
+ * External connection test - filters out AT-soldered PS/2 i8042's
+ * 0x00 - no error, 0x01-0x03 - clock/data stuck, 0xff - general error
+ * 0xfa - no error on some notebooks which ignore the spec
+ * Because it's common for chipsets to return error on perfectly functioning
+ * AUX ports, we test for this only when the LOOP command failed.
+ */
+
+ if (i8042_command(&param, I8042_CMD_AUX_TEST) ||
+ (param && param != 0xfa && param != 0xff))
+ return -1;
+
+/*
+ * If AUX_LOOP completed without error but returned unexpected data
+ * mark it as broken
+ */
+ if (!retval)
+ aux_loop_broken = true;
+ }
+
+/*
+ * Bit assignment test - filters out PS/2 i8042's in AT mode
+ */
+
+ if (i8042_toggle_aux(false)) {
+ pr_warn("Failed to disable AUX port, but continuing anyway... Is this a SiS?\n");
+ pr_warn("If AUX port is really absent please use the 'i8042.noaux' option\n");
+ }
+
+ if (i8042_toggle_aux(true))
+ return -1;
+
+/*
+ * Reset keyboard (needed on some laptops to successfully detect
+ * touchpad, e.g., some Gigabyte laptop models with Elantech
+ * touchpads).
+ */
+ if (i8042_kbdreset) {
+ pr_warn("Attempting to reset device connected to KBD port\n");
+ i8042_kbd_write(NULL, (unsigned char) 0xff);
+ }
+
+/*
+ * Test AUX IRQ delivery to make sure BIOS did not grab the IRQ and
+ * used it for a PCI card or somethig else.
+ */
+
+ if (i8042_noloop || i8042_bypass_aux_irq_test || aux_loop_broken) {
+/*
+ * Without LOOP command we can't test AUX IRQ delivery. Assume the port
+ * is working and hope we are right.
+ */
+ retval = 0;
+ goto out;
+ }
+
+ if (request_irq(I8042_AUX_IRQ, i8042_aux_test_irq, IRQF_SHARED,
+ "i8042", i8042_platform_device))
+ goto out;
+
+ irq_registered = true;
+
+ if (i8042_enable_aux_port())
+ goto out;
+
+ spin_lock_irqsave(&i8042_lock, flags);
+
+ init_completion(&i8042_aux_irq_delivered);
+ i8042_irq_being_tested = true;
+
+ param = 0xa5;
+ retval = __i8042_command(&param, I8042_CMD_AUX_LOOP & 0xf0ff);
+
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+ if (retval)
+ goto out;
+
+ if (wait_for_completion_timeout(&i8042_aux_irq_delivered,
+ msecs_to_jiffies(250)) == 0) {
+/*
+ * AUX IRQ was never delivered so we need to flush the controller to
+ * get rid of the byte we put there; otherwise keyboard may not work.
+ */
+ dbg(" -- i8042 (aux irq test timeout)\n");
+ i8042_flush();
+ retval = -1;
+ }
+
+ out:
+
+/*
+ * Disable the interface.
+ */
+
+ i8042_ctr |= I8042_CTR_AUXDIS;
+ i8042_ctr &= ~I8042_CTR_AUXINT;
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
+ retval = -1;
+
+ if (irq_registered)
+ free_irq(I8042_AUX_IRQ, i8042_platform_device);
+
+ return retval;
+}
+
+static int i8042_controller_check(void)
+{
+ if (i8042_flush()) {
+ pr_info("No controller found\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int i8042_controller_selftest(void)
+{
+ unsigned char param;
+ int i = 0;
+
+ /*
+ * We try this 5 times; on some really fragile systems this does not
+ * take the first time...
+ */
+ do {
+
+ if (i8042_command(&param, I8042_CMD_CTL_TEST)) {
+ pr_err("i8042 controller selftest timeout\n");
+ return -ENODEV;
+ }
+
+ if (param == I8042_RET_CTL_TEST)
+ return 0;
+
+ dbg("i8042 controller selftest: %#x != %#x\n",
+ param, I8042_RET_CTL_TEST);
+ msleep(50);
+ } while (i++ < 5);
+
+#ifdef CONFIG_X86
+ /*
+ * On x86, we don't fail entire i8042 initialization if controller
+ * reset fails in hopes that keyboard port will still be functional
+ * and user will still get a working keyboard. This is especially
+ * important on netbooks. On other arches we trust hardware more.
+ */
+ pr_info("giving up on controller selftest, continuing anyway...\n");
+ return 0;
+#else
+ pr_err("i8042 controller selftest failed\n");
+ return -EIO;
+#endif
+}
+
+/*
+ * i8042_controller_init initializes the i8042 controller, and,
+ * most importantly, sets it into non-xlated mode if that's
+ * desired.
+ */
+
+static int i8042_controller_init(void)
+{
+ unsigned long flags;
+ int n = 0;
+ unsigned char ctr[2];
+
+/*
+ * Save the CTR for restore on unload / reboot.
+ */
+
+ do {
+ if (n >= 10) {
+ pr_err("Unable to get stable CTR read\n");
+ return -EIO;
+ }
+
+ if (n != 0)
+ udelay(50);
+
+ if (i8042_command(&ctr[n++ % 2], I8042_CMD_CTL_RCTR)) {
+ pr_err("Can't read CTR while initializing i8042\n");
+ return i8042_probe_defer ? -EPROBE_DEFER : -EIO;
+ }
+
+ } while (n < 2 || ctr[0] != ctr[1]);
+
+ i8042_initial_ctr = i8042_ctr = ctr[0];
+
+/*
+ * Disable the keyboard interface and interrupt.
+ */
+
+ i8042_ctr |= I8042_CTR_KBDDIS;
+ i8042_ctr &= ~I8042_CTR_KBDINT;
+
+/*
+ * Handle keylock.
+ */
+
+ spin_lock_irqsave(&i8042_lock, flags);
+ if (~i8042_read_status() & I8042_STR_KEYLOCK) {
+ if (i8042_unlock)
+ i8042_ctr |= I8042_CTR_IGNKEYLOCK;
+ else
+ pr_warn("Warning: Keylock active\n");
+ }
+ spin_unlock_irqrestore(&i8042_lock, flags);
+
+/*
+ * If the chip is configured into nontranslated mode by the BIOS, don't
+ * bother enabling translating and be happy.
+ */
+
+ if (~i8042_ctr & I8042_CTR_XLATE)
+ i8042_direct = true;
+
+/*
+ * Set nontranslated mode for the kbd interface if requested by an option.
+ * After this the kbd interface becomes a simple serial in/out, like the aux
+ * interface is. We don't do this by default, since it can confuse notebook
+ * BIOSes.
+ */
+
+ if (i8042_direct)
+ i8042_ctr &= ~I8042_CTR_XLATE;
+
+/*
+ * Write CTR back.
+ */
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ pr_err("Can't write CTR while initializing i8042\n");
+ return -EIO;
+ }
+
+/*
+ * Flush whatever accumulated while we were disabling keyboard port.
+ */
+
+ i8042_flush();
+
+ return 0;
+}
+
+
+/*
+ * Reset the controller and reset CRT to the original value set by BIOS.
+ */
+
+static void i8042_controller_reset(bool s2r_wants_reset)
+{
+ i8042_flush();
+
+/*
+ * Disable both KBD and AUX interfaces so they don't get in the way
+ */
+
+ i8042_ctr |= I8042_CTR_KBDDIS | I8042_CTR_AUXDIS;
+ i8042_ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT);
+
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
+ pr_warn("Can't write CTR while resetting\n");
+
+/*
+ * Disable MUX mode if present.
+ */
+
+ if (i8042_mux_present)
+ i8042_set_mux_mode(false, NULL);
+
+/*
+ * Reset the controller if requested.
+ */
+
+ if (i8042_reset == I8042_RESET_ALWAYS ||
+ (i8042_reset == I8042_RESET_ON_S2RAM && s2r_wants_reset)) {
+ i8042_controller_selftest();
+ }
+
+/*
+ * Restore the original control register setting.
+ */
+
+ if (i8042_command(&i8042_initial_ctr, I8042_CMD_CTL_WCTR))
+ pr_warn("Can't restore CTR\n");
+}
+
+
+/*
+ * i8042_panic_blink() will turn the keyboard LEDs on or off and is called
+ * when kernel panics. Flashing LEDs is useful for users running X who may
+ * not see the console and will help distinguishing panics from "real"
+ * lockups.
+ *
+ * Note that DELAY has a limit of 10ms so we will not get stuck here
+ * waiting for KBC to free up even if KBD interrupt is off
+ */
+
+#define DELAY do { mdelay(1); if (++delay > 10) return delay; } while(0)
+
+static long i8042_panic_blink(int state)
+{
+ long delay = 0;
+ char led;
+
+ led = (state) ? 0x01 | 0x04 : 0;
+ while (i8042_read_status() & I8042_STR_IBF)
+ DELAY;
+ dbg("%02x -> i8042 (panic blink)\n", 0xed);
+ i8042_suppress_kbd_ack = 2;
+ i8042_write_data(0xed); /* set leds */
+ DELAY;
+ while (i8042_read_status() & I8042_STR_IBF)
+ DELAY;
+ DELAY;
+ dbg("%02x -> i8042 (panic blink)\n", led);
+ i8042_write_data(led);
+ DELAY;
+ return delay;
+}
+
+#undef DELAY
+
+#ifdef CONFIG_X86
+static void i8042_dritek_enable(void)
+{
+ unsigned char param = 0x90;
+ int error;
+
+ error = i8042_command(&param, 0x1059);
+ if (error)
+ pr_warn("Failed to enable DRITEK extension: %d\n", error);
+}
+#endif
+
+#ifdef CONFIG_PM
+
+/*
+ * Here we try to reset everything back to a state we had
+ * before suspending.
+ */
+
+static int i8042_controller_resume(bool s2r_wants_reset)
+{
+ int error;
+
+ error = i8042_controller_check();
+ if (error)
+ return error;
+
+ if (i8042_reset == I8042_RESET_ALWAYS ||
+ (i8042_reset == I8042_RESET_ON_S2RAM && s2r_wants_reset)) {
+ error = i8042_controller_selftest();
+ if (error)
+ return error;
+ }
+
+/*
+ * Restore original CTR value and disable all ports
+ */
+
+ i8042_ctr = i8042_initial_ctr;
+ if (i8042_direct)
+ i8042_ctr &= ~I8042_CTR_XLATE;
+ i8042_ctr |= I8042_CTR_AUXDIS | I8042_CTR_KBDDIS;
+ i8042_ctr &= ~(I8042_CTR_AUXINT | I8042_CTR_KBDINT);
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ pr_warn("Can't write CTR to resume, retrying...\n");
+ msleep(50);
+ if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+ pr_err("CTR write retry failed\n");
+ return -EIO;
+ }
+ }
+
+
+#ifdef CONFIG_X86
+ if (i8042_dritek)
+ i8042_dritek_enable();
+#endif
+
+ if (i8042_mux_present) {
+ if (i8042_set_mux_mode(true, NULL) || i8042_enable_mux_ports())
+ pr_warn("failed to resume active multiplexor, mouse won't work\n");
+ } else if (i8042_ports[I8042_AUX_PORT_NO].serio)
+ i8042_enable_aux_port();
+
+ if (i8042_ports[I8042_KBD_PORT_NO].serio)
+ i8042_enable_kbd_port();
+
+ i8042_interrupt(0, NULL);
+
+ return 0;
+}
+
+/*
+ * Here we try to restore the original BIOS settings to avoid
+ * upsetting it.
+ */
+
+static int i8042_pm_suspend(struct device *dev)
+{
+ int i;
+
+ if (pm_suspend_via_firmware())
+ i8042_controller_reset(true);
+
+ /* Set up serio interrupts for system wakeup. */
+ for (i = 0; i < I8042_NUM_PORTS; i++) {
+ struct serio *serio = i8042_ports[i].serio;
+
+ if (serio && device_may_wakeup(&serio->dev))
+ enable_irq_wake(i8042_ports[i].irq);
+ }
+
+ return 0;
+}
+
+static int i8042_pm_resume_noirq(struct device *dev)
+{
+ if (!pm_resume_via_firmware())
+ i8042_interrupt(0, NULL);
+
+ return 0;
+}
+
+static int i8042_pm_resume(struct device *dev)
+{
+ bool want_reset;
+ int i;
+
+ for (i = 0; i < I8042_NUM_PORTS; i++) {
+ struct serio *serio = i8042_ports[i].serio;
+
+ if (serio && device_may_wakeup(&serio->dev))
+ disable_irq_wake(i8042_ports[i].irq);
+ }
+
+ /*
+ * If platform firmware was not going to be involved in suspend, we did
+ * not restore the controller state to whatever it had been at boot
+ * time, so we do not need to do anything.
+ */
+ if (!pm_suspend_via_firmware())
+ return 0;
+
+ /*
+ * We only need to reset the controller if we are resuming after handing
+ * off control to the platform firmware, otherwise we can simply restore
+ * the mode.
+ */
+ want_reset = pm_resume_via_firmware();
+
+ return i8042_controller_resume(want_reset);
+}
+
+static int i8042_pm_thaw(struct device *dev)
+{
+ i8042_interrupt(0, NULL);
+
+ return 0;
+}
+
+static int i8042_pm_reset(struct device *dev)
+{
+ i8042_controller_reset(false);
+
+ return 0;
+}
+
+static int i8042_pm_restore(struct device *dev)
+{
+ return i8042_controller_resume(false);
+}
+
+static const struct dev_pm_ops i8042_pm_ops = {
+ .suspend = i8042_pm_suspend,
+ .resume_noirq = i8042_pm_resume_noirq,
+ .resume = i8042_pm_resume,
+ .thaw = i8042_pm_thaw,
+ .poweroff = i8042_pm_reset,
+ .restore = i8042_pm_restore,
+};
+
+#endif /* CONFIG_PM */
+
+/*
+ * We need to reset the 8042 back to original mode on system shutdown,
+ * because otherwise BIOSes will be confused.
+ */
+
+static void i8042_shutdown(struct platform_device *dev)
+{
+ i8042_controller_reset(false);
+}
+
+static int i8042_create_kbd_port(void)
+{
+ struct serio *serio;
+ struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO];
+
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!serio)
+ return -ENOMEM;
+
+ serio->id.type = i8042_direct ? SERIO_8042 : SERIO_8042_XL;
+ serio->write = i8042_dumbkbd ? NULL : i8042_kbd_write;
+ serio->start = i8042_start;
+ serio->stop = i8042_stop;
+ serio->close = i8042_port_close;
+ serio->ps2_cmd_mutex = &i8042_mutex;
+ serio->port_data = port;
+ serio->dev.parent = &i8042_platform_device->dev;
+ strscpy(serio->name, "i8042 KBD port", sizeof(serio->name));
+ strscpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys));
+ strscpy(serio->firmware_id, i8042_kbd_firmware_id,
+ sizeof(serio->firmware_id));
+ set_primary_fwnode(&serio->dev, i8042_kbd_fwnode);
+
+ port->serio = serio;
+ port->irq = I8042_KBD_IRQ;
+
+ return 0;
+}
+
+static int i8042_create_aux_port(int idx)
+{
+ struct serio *serio;
+ int port_no = idx < 0 ? I8042_AUX_PORT_NO : I8042_MUX_PORT_NO + idx;
+ struct i8042_port *port = &i8042_ports[port_no];
+
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!serio)
+ return -ENOMEM;
+
+ serio->id.type = SERIO_8042;
+ serio->write = i8042_aux_write;
+ serio->start = i8042_start;
+ serio->stop = i8042_stop;
+ serio->ps2_cmd_mutex = &i8042_mutex;
+ serio->port_data = port;
+ serio->dev.parent = &i8042_platform_device->dev;
+ if (idx < 0) {
+ strscpy(serio->name, "i8042 AUX port", sizeof(serio->name));
+ strscpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys));
+ strscpy(serio->firmware_id, i8042_aux_firmware_id,
+ sizeof(serio->firmware_id));
+ serio->close = i8042_port_close;
+ } else {
+ snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx);
+ snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, idx + 1);
+ strscpy(serio->firmware_id, i8042_aux_firmware_id,
+ sizeof(serio->firmware_id));
+ }
+
+ port->serio = serio;
+ port->mux = idx;
+ port->irq = I8042_AUX_IRQ;
+
+ return 0;
+}
+
+static void i8042_free_kbd_port(void)
+{
+ kfree(i8042_ports[I8042_KBD_PORT_NO].serio);
+ i8042_ports[I8042_KBD_PORT_NO].serio = NULL;
+}
+
+static void i8042_free_aux_ports(void)
+{
+ int i;
+
+ for (i = I8042_AUX_PORT_NO; i < I8042_NUM_PORTS; i++) {
+ kfree(i8042_ports[i].serio);
+ i8042_ports[i].serio = NULL;
+ }
+}
+
+static void i8042_register_ports(void)
+{
+ int i;
+
+ for (i = 0; i < I8042_NUM_PORTS; i++) {
+ struct serio *serio = i8042_ports[i].serio;
+
+ if (!serio)
+ continue;
+
+ printk(KERN_INFO "serio: %s at %#lx,%#lx irq %d\n",
+ serio->name,
+ (unsigned long) I8042_DATA_REG,
+ (unsigned long) I8042_COMMAND_REG,
+ i8042_ports[i].irq);
+ serio_register_port(serio);
+ }
+}
+
+static void i8042_unregister_ports(void)
+{
+ int i;
+
+ for (i = 0; i < I8042_NUM_PORTS; i++) {
+ if (i8042_ports[i].serio) {
+ serio_unregister_port(i8042_ports[i].serio);
+ i8042_ports[i].serio = NULL;
+ }
+ }
+}
+
+static void i8042_free_irqs(void)
+{
+ if (i8042_aux_irq_registered)
+ free_irq(I8042_AUX_IRQ, i8042_platform_device);
+ if (i8042_kbd_irq_registered)
+ free_irq(I8042_KBD_IRQ, i8042_platform_device);
+
+ i8042_aux_irq_registered = i8042_kbd_irq_registered = false;
+}
+
+static int i8042_setup_aux(void)
+{
+ int (*aux_enable)(void);
+ int error;
+ int i;
+
+ if (i8042_check_aux())
+ return -ENODEV;
+
+ if (i8042_nomux || i8042_check_mux()) {
+ error = i8042_create_aux_port(-1);
+ if (error)
+ goto err_free_ports;
+ aux_enable = i8042_enable_aux_port;
+ } else {
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++) {
+ error = i8042_create_aux_port(i);
+ if (error)
+ goto err_free_ports;
+ }
+ aux_enable = i8042_enable_mux_ports;
+ }
+
+ error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED,
+ "i8042", i8042_platform_device);
+ if (error)
+ goto err_free_ports;
+
+ error = aux_enable();
+ if (error)
+ goto err_free_irq;
+
+ i8042_aux_irq_registered = true;
+ return 0;
+
+ err_free_irq:
+ free_irq(I8042_AUX_IRQ, i8042_platform_device);
+ err_free_ports:
+ i8042_free_aux_ports();
+ return error;
+}
+
+static int i8042_setup_kbd(void)
+{
+ int error;
+
+ error = i8042_create_kbd_port();
+ if (error)
+ return error;
+
+ error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
+ "i8042", i8042_platform_device);
+ if (error)
+ goto err_free_port;
+
+ error = i8042_enable_kbd_port();
+ if (error)
+ goto err_free_irq;
+
+ i8042_kbd_irq_registered = true;
+ return 0;
+
+ err_free_irq:
+ free_irq(I8042_KBD_IRQ, i8042_platform_device);
+ err_free_port:
+ i8042_free_kbd_port();
+ return error;
+}
+
+static int i8042_kbd_bind_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct device *dev = data;
+ struct serio *serio = to_serio_port(dev);
+ struct i8042_port *port = serio->port_data;
+
+ if (serio != i8042_ports[I8042_KBD_PORT_NO].serio)
+ return 0;
+
+ switch (action) {
+ case BUS_NOTIFY_BOUND_DRIVER:
+ port->driver_bound = true;
+ break;
+
+ case BUS_NOTIFY_UNBIND_DRIVER:
+ port->driver_bound = false;
+ break;
+ }
+
+ return 0;
+}
+
+static int i8042_probe(struct platform_device *dev)
+{
+ int error;
+
+ if (i8042_reset == I8042_RESET_ALWAYS) {
+ error = i8042_controller_selftest();
+ if (error)
+ return error;
+ }
+
+ error = i8042_controller_init();
+ if (error)
+ return error;
+
+#ifdef CONFIG_X86
+ if (i8042_dritek)
+ i8042_dritek_enable();
+#endif
+
+ if (!i8042_noaux) {
+ error = i8042_setup_aux();
+ if (error && error != -ENODEV && error != -EBUSY)
+ goto out_fail;
+ }
+
+ if (!i8042_nokbd) {
+ error = i8042_setup_kbd();
+ if (error)
+ goto out_fail;
+ }
+/*
+ * Ok, everything is ready, let's register all serio ports
+ */
+ i8042_register_ports();
+
+ return 0;
+
+ out_fail:
+ i8042_free_aux_ports(); /* in case KBD failed but AUX not */
+ i8042_free_irqs();
+ i8042_controller_reset(false);
+
+ return error;
+}
+
+static int i8042_remove(struct platform_device *dev)
+{
+ i8042_unregister_ports();
+ i8042_free_irqs();
+ i8042_controller_reset(false);
+
+ return 0;
+}
+
+static struct platform_driver i8042_driver = {
+ .driver = {
+ .name = "i8042",
+#ifdef CONFIG_PM
+ .pm = &i8042_pm_ops,
+#endif
+ },
+ .probe = i8042_probe,
+ .remove = i8042_remove,
+ .shutdown = i8042_shutdown,
+};
+
+static struct notifier_block i8042_kbd_bind_notifier_block = {
+ .notifier_call = i8042_kbd_bind_notifier,
+};
+
+static int __init i8042_init(void)
+{
+ int err;
+
+ dbg_init();
+
+ err = i8042_platform_init();
+ if (err)
+ return (err == -ENODEV) ? 0 : err;
+
+ err = i8042_controller_check();
+ if (err)
+ goto err_platform_exit;
+
+ /* Set this before creating the dev to allow i8042_command to work right away */
+ i8042_present = true;
+
+ err = platform_driver_register(&i8042_driver);
+ if (err)
+ goto err_platform_exit;
+
+ i8042_platform_device = platform_device_alloc("i8042", -1);
+ if (!i8042_platform_device) {
+ err = -ENOMEM;
+ goto err_unregister_driver;
+ }
+
+ err = platform_device_add(i8042_platform_device);
+ if (err)
+ goto err_free_device;
+
+ bus_register_notifier(&serio_bus, &i8042_kbd_bind_notifier_block);
+ panic_blink = i8042_panic_blink;
+
+ return 0;
+
+err_free_device:
+ platform_device_put(i8042_platform_device);
+err_unregister_driver:
+ platform_driver_unregister(&i8042_driver);
+ err_platform_exit:
+ i8042_platform_exit();
+ return err;
+}
+
+static void __exit i8042_exit(void)
+{
+ if (!i8042_present)
+ return;
+
+ platform_device_unregister(i8042_platform_device);
+ platform_driver_unregister(&i8042_driver);
+ i8042_platform_exit();
+
+ bus_unregister_notifier(&serio_bus, &i8042_kbd_bind_notifier_block);
+ panic_blink = NULL;
+}
+
+module_init(i8042_init);
+module_exit(i8042_exit);
diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h
new file mode 100644
index 000000000..adb517337
--- /dev/null
+++ b/drivers/input/serio/i8042.h
@@ -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_IA64) || 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 */
diff --git a/drivers/input/serio/ioc3kbd.c b/drivers/input/serio/ioc3kbd.c
new file mode 100644
index 000000000..d51bfe912
--- /dev/null
+++ b/drivers/input/serio/ioc3kbd.c
@@ -0,0 +1,216 @@
+// 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(sizeof(*sk), GFP_KERNEL);
+ if (!sk)
+ return -ENOMEM;
+
+ sa = kzalloc(sizeof(*sa), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+static struct platform_driver ioc3kbd_driver = {
+ .probe = ioc3kbd_probe,
+ .remove = ioc3kbd_remove,
+ .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");
diff --git a/drivers/input/serio/libps2.c b/drivers/input/serio/libps2.c
new file mode 100644
index 000000000..3e19344ed
--- /dev/null
+++ b/drivers/input/serio/libps2.c
@@ -0,0 +1,494 @@
+// 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/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"
+
+MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
+MODULE_DESCRIPTION("PS/2 driver library");
+MODULE_LICENSE("GPL");
+
+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 waits for acknowledge.
+ * It 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;
+
+ serio_pause_rx(ps2dev->serio);
+
+ retval = ps2_do_sendbyte(ps2dev, byte, timeout, 1);
+ dev_dbg(&ps2dev->serio->dev, "%02x - %x\n", byte, ps2dev->nak);
+
+ serio_continue_rx(ps2dev->serio);
+
+ return retval;
+}
+EXPORT_SYMBOL(ps2_sendbyte);
+
+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);
+
+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.
+ */
+
+void ps2_drain(struct ps2dev *ps2dev, size_t maxbytes, unsigned int timeout)
+{
+ if (maxbytes > sizeof(ps2dev->cmdbuf)) {
+ WARN_ON(1);
+ maxbytes = sizeof(ps2dev->cmdbuf);
+ }
+
+ ps2_begin_command(ps2dev);
+
+ serio_pause_rx(ps2dev->serio);
+ ps2dev->flags = PS2_FLAG_CMD;
+ ps2dev->cmdcnt = maxbytes;
+ serio_continue_rx(ps2dev->serio);
+
+ wait_event_timeout(ps2dev->wait,
+ !(ps2dev->flags & PS2_FLAG_CMD),
+ msecs_to_jiffies(timeout));
+
+ ps2_end_command(ps2dev);
+}
+EXPORT_SYMBOL(ps2_drain);
+
+/*
+ * ps2_is_keyboard_id() checks received ID byte against the list of
+ * known keyboard IDs.
+ */
+
+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) {
+ serio_pause_rx(ps2dev->serio);
+ ps2dev->flags = 0;
+ serio_continue_rx(ps2dev->serio);
+ timeout = 0;
+ }
+
+ /*
+ * If device behind the port is not a keyboard there
+ * won't be 2nd byte of ID response.
+ */
+ if (!ps2_is_keyboard_id(ps2dev->cmdbuf[1])) {
+ serio_pause_rx(ps2dev->serio);
+ ps2dev->flags = ps2dev->cmdcnt = 0;
+ serio_continue_rx(ps2dev->serio);
+ timeout = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return timeout;
+}
+
+/*
+ * ps2_command() sends a command and its parameters to the mouse,
+ * then waits for the response and puts it in the param array.
+ *
+ * ps2_command() can only be called from a process context
+ */
+
+int __ps2_command(struct ps2dev *ps2dev, 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 (receive > sizeof(ps2dev->cmdbuf)) {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (send && !param) {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ memcpy(send_param, param, send);
+
+ serio_pause_rx(ps2dev->serio);
+
+ ps2dev->flags = command == PS2_CMD_GETID ? PS2_FLAG_WAITID : 0;
+ ps2dev->cmdcnt = receive;
+ if (receive && param)
+ for (i = 0; i < receive; i++)
+ ps2dev->cmdbuf[(receive - 1) - i] = param[i];
+
+ /* Signal that we are sending the command byte */
+ ps2dev->flags |= PS2_FLAG_ACK_CMD;
+
+ /*
+ * Some devices (Synaptics) peform 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;
+
+ /* Now we are sending command parameters, if any */
+ ps2dev->flags &= ~PS2_FLAG_ACK_CMD;
+
+ 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);
+
+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 the mouse
+ * 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
+ */
+
+void ps2_init(struct ps2dev *ps2dev, struct serio *serio)
+{
+ mutex_init(&ps2dev->cmd_mutex);
+ lockdep_set_subclass(&ps2dev->cmd_mutex, serio->depth);
+ init_waitqueue_head(&ps2dev->wait);
+ ps2dev->serio = serio;
+}
+EXPORT_SYMBOL(ps2_init);
+
+/*
+ * ps2_handle_ack() is supposed to be used in interrupt handler
+ * to properly process ACK/NAK of a command from a PS/2 device.
+ */
+
+bool 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.
+ * 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);
+ ps2dev->flags &= ~PS2_FLAG_WAITID;
+ return ps2dev->flags & PS2_FLAG_ACK_CMD;
+ }
+
+ if (!ps2dev->nak) {
+ ps2dev->flags &= ~PS2_FLAG_NAK;
+ if (ps2dev->cmdcnt)
+ ps2dev->flags |= PS2_FLAG_CMD | PS2_FLAG_CMD1;
+ }
+
+ ps2dev->flags &= ~PS2_FLAG_ACK;
+ wake_up(&ps2dev->wait);
+
+ if (data != PS2_RET_ACK)
+ ps2_handle_response(ps2dev, data);
+
+ return true;
+}
+EXPORT_SYMBOL(ps2_handle_ack);
+
+/*
+ * ps2_handle_response() is supposed to be used in interrupt handler
+ * to properly store device's response to a command and notify process
+ * waiting for completion of the command.
+ */
+
+bool 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);
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(ps2_handle_response);
+
+void ps2_cmd_aborted(struct ps2dev *ps2dev)
+{
+ if (ps2dev->flags & PS2_FLAG_ACK)
+ ps2dev->nak = 1;
+
+ if (ps2dev->flags & (PS2_FLAG_ACK | PS2_FLAG_CMD))
+ wake_up(&ps2dev->wait);
+
+ /* reset all flags except last nack */
+ ps2dev->flags &= PS2_FLAG_NAK;
+}
+EXPORT_SYMBOL(ps2_cmd_aborted);
diff --git a/drivers/input/serio/maceps2.c b/drivers/input/serio/maceps2.c
new file mode 100644
index 000000000..629e15089
--- /dev/null
+++ b/drivers/input/serio/maceps2.c
@@ -0,0 +1,206 @@
+// 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(sizeof(struct serio), GFP_KERNEL);
+ if (serio) {
+ serio->id.type = SERIO_8042;
+ serio->write = maceps2_write;
+ serio->open = maceps2_open;
+ serio->close = maceps2_close;
+ snprintf(serio->name, sizeof(serio->name), "MACE PS/2 port%d", idx);
+ snprintf(serio->phys, sizeof(serio->phys), "mace/serio%d", idx);
+ serio->port_data = &port_data[idx];
+ serio->dev.parent = &maceps2_device->dev;
+ }
+
+ return serio;
+}
+
+static int maceps2_probe(struct platform_device *dev)
+{
+ maceps2_port[0] = maceps2_allocate_port(0);
+ maceps2_port[1] = maceps2_allocate_port(1);
+ if (!maceps2_port[0] || !maceps2_port[1]) {
+ kfree(maceps2_port[0]);
+ kfree(maceps2_port[1]);
+ return -ENOMEM;
+ }
+
+ serio_register_port(maceps2_port[0]);
+ serio_register_port(maceps2_port[1]);
+
+ return 0;
+}
+
+static int maceps2_remove(struct platform_device *dev)
+{
+ serio_unregister_port(maceps2_port[0]);
+ serio_unregister_port(maceps2_port[1]);
+
+ return 0;
+}
+
+static struct platform_driver maceps2_driver = {
+ .driver = {
+ .name = "maceps2",
+ },
+ .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);
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c
new file mode 100644
index 000000000..04d2db982
--- /dev/null
+++ b/drivers/input/serio/olpc_apsp.c
@@ -0,0 +1,272 @@
+// 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;
+ struct resource *res;
+ int error;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct olpc_apsp), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ 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(sizeof(struct serio), GFP_KERNEL);
+ 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(sizeof(struct serio), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+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);
diff --git a/drivers/input/serio/parkbd.c b/drivers/input/serio/parkbd.c
new file mode 100644
index 000000000..0d5489542
--- /dev/null
+++ b/drivers/input/serio/parkbd.c
@@ -0,0 +1,223 @@
+// 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(sizeof(struct serio), GFP_KERNEL);
+ 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,
+ .devmodel = true,
+};
+module_parport_driver(parkbd_parport_driver);
diff --git a/drivers/input/serio/pcips2.c b/drivers/input/serio/pcips2.c
new file mode 100644
index 000000000..05878750f
--- /dev/null
+++ b/drivers/input/serio/pcips2.c
@@ -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(sizeof(struct pcips2_data), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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");
diff --git a/drivers/input/serio/ps2-gpio.c b/drivers/input/serio/ps2-gpio.c
new file mode 100644
index 000000000..bc1dc4843
--- /dev/null
+++ b/drivers/input/serio/ps2-gpio.c
@@ -0,0 +1,507 @@
+// 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 samples 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()) {
+ mutex_lock(&drvdata->tx.mutex);
+ __ps2_gpio_write(serio, val);
+ if (!wait_for_completion_timeout(&drvdata->tx.complete,
+ msecs_to_jiffies(10000)))
+ ret = SERIO_TIMEOUT;
+ mutex_unlock(&drvdata->tx.mutex);
+ } 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(struct ps2_gpio_data), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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, DRIVER_NAME, drvdata);
+ if (error) {
+ dev_err(dev, "failed to request irq %d: %d\n",
+ drvdata->irq, error);
+ goto err_free_serio;
+ }
+
+ /* Keep irq disabled until serio->open is called. */
+ disable_irq(drvdata->irq);
+
+ 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 int ps2_gpio_remove(struct platform_device *pdev)
+{
+ struct ps2_gpio_data *drvdata = platform_get_drvdata(pdev);
+
+ serio_unregister_port(drvdata->serio);
+ return 0;
+}
+
+#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");
diff --git a/drivers/input/serio/ps2mult.c b/drivers/input/serio/ps2mult.c
new file mode 100644
index 000000000..902e81826
--- /dev/null
+++ b/drivers/input/serio/ps2mult.c
@@ -0,0 +1,304 @@
+// 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&psm->lock, flags);
+
+ 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);
+
+ spin_unlock_irqrestore(&psm->lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&psm->lock, flags);
+ port->registered = true;
+ spin_unlock_irqrestore(&psm->lock, flags);
+
+ 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&psm->lock, flags);
+ port->registered = false;
+ spin_unlock_irqrestore(&psm->lock, flags);
+}
+
+static int ps2mult_create_port(struct ps2mult *psm, int i)
+{
+ struct serio *mx_serio = psm->mx_serio;
+ struct serio *serio;
+
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&psm->lock, flags);
+
+ 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]);
+
+ spin_unlock_irqrestore(&psm->lock, flags);
+}
+
+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(sizeof(*psm), GFP_KERNEL);
+ 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;
+ unsigned long flags;
+
+ dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
+
+ spin_lock_irqsave(&psm->lock, flags);
+
+ 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:
+ spin_unlock_irqrestore(&psm->lock, flags);
+ 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);
diff --git a/drivers/input/serio/q40kbd.c b/drivers/input/serio/q40kbd.c
new file mode 100644
index 000000000..ba04058fc
--- /dev/null
+++ b/drivers/input/serio/q40kbd.c
@@ -0,0 +1,174 @@
+// 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&q40kbd->lock, flags);
+
+ if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))
+ serio_interrupt(q40kbd->port, master_inb(KEYCODE_REG), 0);
+
+ master_outb(-1, KEYBOARD_UNLOCK_REG);
+
+ spin_unlock_irqrestore(&q40kbd->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * q40kbd_flush() flushes all data that may be in the keyboard buffers
+ */
+
+static void q40kbd_flush(struct q40kbd *q40kbd)
+{
+ int maxread = 100;
+ unsigned long flags;
+
+ spin_lock_irqsave(&q40kbd->lock, flags);
+
+ while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)))
+ master_inb(KEYCODE_REG);
+
+ spin_unlock_irqrestore(&q40kbd->lock, flags);
+}
+
+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(sizeof(struct q40kbd), GFP_KERNEL);
+ port = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+static struct platform_driver q40kbd_driver = {
+ .driver = {
+ .name = "q40kbd",
+ },
+ .remove = q40kbd_remove,
+};
+
+module_platform_driver_probe(q40kbd_driver, q40kbd_probe);
diff --git a/drivers/input/serio/rpckbd.c b/drivers/input/serio/rpckbd.c
new file mode 100644
index 000000000..ce420eb1f
--- /dev/null
+++ b/drivers/input/serio/rpckbd.c
@@ -0,0 +1,154 @@
+// 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 < 0 ? rx_irq : -ENXIO;
+
+ tx_irq = platform_get_irq(dev, 1);
+ if (tx_irq <= 0)
+ return tx_irq < 0 ? tx_irq : -ENXIO;
+
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ rpckbd = kzalloc(sizeof(*rpckbd), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+static struct platform_driver rpckbd_driver = {
+ .probe = rpckbd_probe,
+ .remove = rpckbd_remove,
+ .driver = {
+ .name = "kart",
+ },
+};
+module_platform_driver(rpckbd_driver);
diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c
new file mode 100644
index 000000000..2724c3aa5
--- /dev/null
+++ b/drivers/input/serio/sa1111ps2.c
@@ -0,0 +1,386 @@
+// 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;
+
+ spin_lock(&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);
+ }
+ spin_unlock(&ps2if->lock);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Write a byte to the PS2 port. We have to wait for the
+ * port to indicate that the transmitter is empty.
+ */
+static int ps2_write(struct serio *io, unsigned char val)
+{
+ struct ps2if *ps2if = io->port_data;
+ unsigned long flags;
+ unsigned int head;
+
+ spin_lock_irqsave(&ps2if->lock, flags);
+
+ /*
+ * If the TX register is empty, we can go straight out.
+ */
+ if (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;
+ }
+ }
+
+ spin_unlock_irqrestore(&ps2if->lock, flags);
+ 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(sizeof(struct ps2if), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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");
diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c
new file mode 100644
index 000000000..15ce32023
--- /dev/null
+++ b/drivers/input/serio/serio.c
@@ -0,0 +1,1049 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The Serio abstraction module
+ *
+ * Copyright (c) 1999-2004 Vojtech Pavlik
+ * Copyright (c) 2004 Dmitry Torokhov
+ * Copyright (c) 2003 Daniele Bellucci
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/stddef.h>
+#include <linux/module.h>
+#include <linux/serio.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("Serio abstraction core");
+MODULE_LICENSE("GPL");
+
+/*
+ * serio_mutex protects entire serio subsystem and is taken every time
+ * serio port or driver registered or unregistered.
+ */
+static DEFINE_MUTEX(serio_mutex);
+
+static LIST_HEAD(serio_list);
+
+static void serio_add_port(struct serio *serio);
+static int serio_reconnect_port(struct serio *serio);
+static void serio_disconnect_port(struct serio *serio);
+static void serio_reconnect_subtree(struct serio *serio);
+static void serio_attach_driver(struct serio_driver *drv);
+
+static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
+{
+ int retval;
+
+ mutex_lock(&serio->drv_mutex);
+ retval = drv->connect(serio, drv);
+ mutex_unlock(&serio->drv_mutex);
+
+ return retval;
+}
+
+static int serio_reconnect_driver(struct serio *serio)
+{
+ int retval = -1;
+
+ mutex_lock(&serio->drv_mutex);
+ if (serio->drv && serio->drv->reconnect)
+ retval = serio->drv->reconnect(serio);
+ mutex_unlock(&serio->drv_mutex);
+
+ return retval;
+}
+
+static void serio_disconnect_driver(struct serio *serio)
+{
+ mutex_lock(&serio->drv_mutex);
+ if (serio->drv)
+ serio->drv->disconnect(serio);
+ mutex_unlock(&serio->drv_mutex);
+}
+
+static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
+{
+ while (ids->type || ids->proto) {
+ if ((ids->type == SERIO_ANY || ids->type == serio->id.type) &&
+ (ids->proto == SERIO_ANY || ids->proto == serio->id.proto) &&
+ (ids->extra == SERIO_ANY || ids->extra == serio->id.extra) &&
+ (ids->id == SERIO_ANY || ids->id == serio->id.id))
+ return 1;
+ ids++;
+ }
+ return 0;
+}
+
+/*
+ * Basic serio -> driver core mappings
+ */
+
+static int serio_bind_driver(struct serio *serio, struct serio_driver *drv)
+{
+ int error;
+
+ if (serio_match_port(drv->id_table, serio)) {
+
+ serio->dev.driver = &drv->driver;
+ if (serio_connect_driver(serio, drv)) {
+ serio->dev.driver = NULL;
+ return -ENODEV;
+ }
+
+ error = device_bind_driver(&serio->dev);
+ if (error) {
+ dev_warn(&serio->dev,
+ "device_bind_driver() failed for %s (%s) and %s, error: %d\n",
+ serio->phys, serio->name,
+ drv->description, error);
+ serio_disconnect_driver(serio);
+ serio->dev.driver = NULL;
+ return error;
+ }
+ }
+ return 0;
+}
+
+static void serio_find_driver(struct serio *serio)
+{
+ int error;
+
+ error = device_attach(&serio->dev);
+ if (error < 0 && error != -EPROBE_DEFER)
+ dev_warn(&serio->dev,
+ "device_attach() failed for %s (%s), error: %d\n",
+ serio->phys, serio->name, error);
+}
+
+
+/*
+ * Serio event processing.
+ */
+
+enum serio_event_type {
+ SERIO_RESCAN_PORT,
+ SERIO_RECONNECT_PORT,
+ SERIO_RECONNECT_SUBTREE,
+ SERIO_REGISTER_PORT,
+ SERIO_ATTACH_DRIVER,
+};
+
+struct serio_event {
+ enum serio_event_type type;
+ void *object;
+ struct module *owner;
+ struct list_head node;
+};
+
+static DEFINE_SPINLOCK(serio_event_lock); /* protects serio_event_list */
+static LIST_HEAD(serio_event_list);
+
+static struct serio_event *serio_get_event(void)
+{
+ struct serio_event *event = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serio_event_lock, flags);
+
+ if (!list_empty(&serio_event_list)) {
+ event = list_first_entry(&serio_event_list,
+ struct serio_event, node);
+ list_del_init(&event->node);
+ }
+
+ spin_unlock_irqrestore(&serio_event_lock, flags);
+ return event;
+}
+
+static void serio_free_event(struct serio_event *event)
+{
+ module_put(event->owner);
+ kfree(event);
+}
+
+static void serio_remove_duplicate_events(void *object,
+ enum serio_event_type type)
+{
+ struct serio_event *e, *next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serio_event_lock, flags);
+
+ list_for_each_entry_safe(e, next, &serio_event_list, node) {
+ if (object == e->object) {
+ /*
+ * If this event is of different type we should not
+ * look further - we only suppress duplicate events
+ * that were sent back-to-back.
+ */
+ if (type != e->type)
+ break;
+
+ list_del_init(&e->node);
+ serio_free_event(e);
+ }
+ }
+
+ spin_unlock_irqrestore(&serio_event_lock, flags);
+}
+
+static void serio_handle_event(struct work_struct *work)
+{
+ struct serio_event *event;
+
+ mutex_lock(&serio_mutex);
+
+ while ((event = serio_get_event())) {
+
+ switch (event->type) {
+
+ case SERIO_REGISTER_PORT:
+ serio_add_port(event->object);
+ break;
+
+ case SERIO_RECONNECT_PORT:
+ serio_reconnect_port(event->object);
+ break;
+
+ case SERIO_RESCAN_PORT:
+ serio_disconnect_port(event->object);
+ serio_find_driver(event->object);
+ break;
+
+ case SERIO_RECONNECT_SUBTREE:
+ serio_reconnect_subtree(event->object);
+ break;
+
+ case SERIO_ATTACH_DRIVER:
+ serio_attach_driver(event->object);
+ break;
+ }
+
+ serio_remove_duplicate_events(event->object, event->type);
+ serio_free_event(event);
+ }
+
+ mutex_unlock(&serio_mutex);
+}
+
+static DECLARE_WORK(serio_event_work, serio_handle_event);
+
+static int serio_queue_event(void *object, struct module *owner,
+ enum serio_event_type event_type)
+{
+ unsigned long flags;
+ struct serio_event *event;
+ int retval = 0;
+
+ spin_lock_irqsave(&serio_event_lock, flags);
+
+ /*
+ * Scan event list for the other events for the same serio port,
+ * starting with the most recent one. If event is the same we
+ * do not need add new one. If event is of different type we
+ * need to add this event and should not look further because
+ * we need to preseve sequence of distinct events.
+ */
+ list_for_each_entry_reverse(event, &serio_event_list, node) {
+ if (event->object == object) {
+ if (event->type == event_type)
+ goto out;
+ break;
+ }
+ }
+
+ event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC);
+ if (!event) {
+ pr_err("Not enough memory to queue event %d\n", event_type);
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ if (!try_module_get(owner)) {
+ pr_warn("Can't get module reference, dropping event %d\n",
+ event_type);
+ kfree(event);
+ retval = -EINVAL;
+ goto out;
+ }
+
+ event->type = event_type;
+ event->object = object;
+ event->owner = owner;
+
+ list_add_tail(&event->node, &serio_event_list);
+ queue_work(system_long_wq, &serio_event_work);
+
+out:
+ spin_unlock_irqrestore(&serio_event_lock, flags);
+ return retval;
+}
+
+/*
+ * Remove all events that have been submitted for a given
+ * object, be it serio port or driver.
+ */
+static void serio_remove_pending_events(void *object)
+{
+ struct serio_event *event, *next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serio_event_lock, flags);
+
+ list_for_each_entry_safe(event, next, &serio_event_list, node) {
+ if (event->object == object) {
+ list_del_init(&event->node);
+ serio_free_event(event);
+ }
+ }
+
+ spin_unlock_irqrestore(&serio_event_lock, flags);
+}
+
+/*
+ * Locate child serio port (if any) that has not been fully registered yet.
+ *
+ * Children are registered by driver's connect() handler so there can't be a
+ * grandchild pending registration together with a child.
+ */
+static struct serio *serio_get_pending_child(struct serio *parent)
+{
+ struct serio_event *event;
+ struct serio *serio, *child = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serio_event_lock, flags);
+
+ list_for_each_entry(event, &serio_event_list, node) {
+ if (event->type == SERIO_REGISTER_PORT) {
+ serio = event->object;
+ if (serio->parent == parent) {
+ child = serio;
+ break;
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&serio_event_lock, flags);
+ return child;
+}
+
+/*
+ * Serio port operations
+ */
+
+static ssize_t serio_show_description(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%s\n", serio->name);
+}
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ return sprintf(buf, "serio:ty%02Xpr%02Xid%02Xex%02X\n",
+ serio->id.type, serio->id.proto, serio->id.id, serio->id.extra);
+}
+
+static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%02x\n", serio->id.type);
+}
+
+static ssize_t proto_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%02x\n", serio->id.proto);
+}
+
+static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%02x\n", serio->id.id);
+}
+
+static ssize_t extra_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%02x\n", serio->id.extra);
+}
+
+static ssize_t drvctl_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct serio *serio = to_serio_port(dev);
+ struct device_driver *drv;
+ int error;
+
+ error = mutex_lock_interruptible(&serio_mutex);
+ if (error)
+ return error;
+
+ if (!strncmp(buf, "none", count)) {
+ serio_disconnect_port(serio);
+ } else if (!strncmp(buf, "reconnect", count)) {
+ serio_reconnect_subtree(serio);
+ } else if (!strncmp(buf, "rescan", count)) {
+ serio_disconnect_port(serio);
+ serio_find_driver(serio);
+ serio_remove_duplicate_events(serio, SERIO_RESCAN_PORT);
+ } else if ((drv = driver_find(buf, &serio_bus)) != NULL) {
+ serio_disconnect_port(serio);
+ error = serio_bind_driver(serio, to_serio_driver(drv));
+ serio_remove_duplicate_events(serio, SERIO_RESCAN_PORT);
+ } else {
+ error = -EINVAL;
+ }
+
+ mutex_unlock(&serio_mutex);
+
+ return error ? error : count;
+}
+
+static ssize_t serio_show_bind_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+ return sprintf(buf, "%s\n", serio->manual_bind ? "manual" : "auto");
+}
+
+static ssize_t serio_set_bind_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct serio *serio = to_serio_port(dev);
+ int retval;
+
+ retval = count;
+ if (!strncmp(buf, "manual", count)) {
+ serio->manual_bind = true;
+ } else if (!strncmp(buf, "auto", count)) {
+ serio->manual_bind = false;
+ } else {
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+static ssize_t firmware_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ return sprintf(buf, "%s\n", serio->firmware_id);
+}
+
+static DEVICE_ATTR_RO(type);
+static DEVICE_ATTR_RO(proto);
+static DEVICE_ATTR_RO(id);
+static DEVICE_ATTR_RO(extra);
+
+static struct attribute *serio_device_id_attrs[] = {
+ &dev_attr_type.attr,
+ &dev_attr_proto.attr,
+ &dev_attr_id.attr,
+ &dev_attr_extra.attr,
+ NULL
+};
+
+static const struct attribute_group serio_id_attr_group = {
+ .name = "id",
+ .attrs = serio_device_id_attrs,
+};
+
+static DEVICE_ATTR_RO(modalias);
+static DEVICE_ATTR_WO(drvctl);
+static DEVICE_ATTR(description, S_IRUGO, serio_show_description, NULL);
+static DEVICE_ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode);
+static DEVICE_ATTR_RO(firmware_id);
+
+static struct attribute *serio_device_attrs[] = {
+ &dev_attr_modalias.attr,
+ &dev_attr_description.attr,
+ &dev_attr_drvctl.attr,
+ &dev_attr_bind_mode.attr,
+ &dev_attr_firmware_id.attr,
+ NULL
+};
+
+static const struct attribute_group serio_device_attr_group = {
+ .attrs = serio_device_attrs,
+};
+
+static const struct attribute_group *serio_device_attr_groups[] = {
+ &serio_id_attr_group,
+ &serio_device_attr_group,
+ NULL
+};
+
+static void serio_release_port(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ kfree(serio);
+ module_put(THIS_MODULE);
+}
+
+/*
+ * Prepare serio port for registration.
+ */
+static void serio_init_port(struct serio *serio)
+{
+ static atomic_t serio_no = ATOMIC_INIT(-1);
+
+ __module_get(THIS_MODULE);
+
+ INIT_LIST_HEAD(&serio->node);
+ INIT_LIST_HEAD(&serio->child_node);
+ INIT_LIST_HEAD(&serio->children);
+ spin_lock_init(&serio->lock);
+ mutex_init(&serio->drv_mutex);
+ device_initialize(&serio->dev);
+ dev_set_name(&serio->dev, "serio%lu",
+ (unsigned long)atomic_inc_return(&serio_no));
+ serio->dev.bus = &serio_bus;
+ serio->dev.release = serio_release_port;
+ serio->dev.groups = serio_device_attr_groups;
+ if (serio->parent) {
+ serio->dev.parent = &serio->parent->dev;
+ serio->depth = serio->parent->depth + 1;
+ } else
+ serio->depth = 0;
+ lockdep_set_subclass(&serio->lock, serio->depth);
+}
+
+/*
+ * Complete serio port registration.
+ * Driver core will attempt to find appropriate driver for the port.
+ */
+static void serio_add_port(struct serio *serio)
+{
+ struct serio *parent = serio->parent;
+ int error;
+
+ if (parent) {
+ serio_pause_rx(parent);
+ list_add_tail(&serio->child_node, &parent->children);
+ serio_continue_rx(parent);
+ }
+
+ list_add_tail(&serio->node, &serio_list);
+
+ if (serio->start)
+ serio->start(serio);
+
+ error = device_add(&serio->dev);
+ if (error)
+ dev_err(&serio->dev,
+ "device_add() failed for %s (%s), error: %d\n",
+ serio->phys, serio->name, error);
+}
+
+/*
+ * serio_destroy_port() completes unregistration process and removes
+ * port from the system
+ */
+static void serio_destroy_port(struct serio *serio)
+{
+ struct serio *child;
+
+ while ((child = serio_get_pending_child(serio)) != NULL) {
+ serio_remove_pending_events(child);
+ put_device(&child->dev);
+ }
+
+ if (serio->stop)
+ serio->stop(serio);
+
+ if (serio->parent) {
+ serio_pause_rx(serio->parent);
+ list_del_init(&serio->child_node);
+ serio_continue_rx(serio->parent);
+ serio->parent = NULL;
+ }
+
+ if (device_is_registered(&serio->dev))
+ device_del(&serio->dev);
+
+ list_del_init(&serio->node);
+ serio_remove_pending_events(serio);
+ put_device(&serio->dev);
+}
+
+/*
+ * Reconnect serio port (re-initialize attached device).
+ * If reconnect fails (old device is no longer attached or
+ * there was no device to begin with) we do full rescan in
+ * hope of finding a driver for the port.
+ */
+static int serio_reconnect_port(struct serio *serio)
+{
+ int error = serio_reconnect_driver(serio);
+
+ if (error) {
+ serio_disconnect_port(serio);
+ serio_find_driver(serio);
+ }
+
+ return error;
+}
+
+/*
+ * Reconnect serio port and all its children (re-initialize attached
+ * devices).
+ */
+static void serio_reconnect_subtree(struct serio *root)
+{
+ struct serio *s = root;
+ int error;
+
+ do {
+ error = serio_reconnect_port(s);
+ if (!error) {
+ /*
+ * Reconnect was successful, move on to do the
+ * first child.
+ */
+ if (!list_empty(&s->children)) {
+ s = list_first_entry(&s->children,
+ struct serio, child_node);
+ continue;
+ }
+ }
+
+ /*
+ * Either it was a leaf node or reconnect failed and it
+ * became a leaf node. Continue reconnecting starting with
+ * the next sibling of the parent node.
+ */
+ while (s != root) {
+ struct serio *parent = s->parent;
+
+ if (!list_is_last(&s->child_node, &parent->children)) {
+ s = list_entry(s->child_node.next,
+ struct serio, child_node);
+ break;
+ }
+
+ s = parent;
+ }
+ } while (s != root);
+}
+
+/*
+ * serio_disconnect_port() unbinds a port from its driver. As a side effect
+ * all children ports are unbound and destroyed.
+ */
+static void serio_disconnect_port(struct serio *serio)
+{
+ struct serio *s = serio;
+
+ /*
+ * Children ports should be disconnected and destroyed
+ * first; we travel the tree in depth-first order.
+ */
+ while (!list_empty(&serio->children)) {
+
+ /* Locate a leaf */
+ while (!list_empty(&s->children))
+ s = list_first_entry(&s->children,
+ struct serio, child_node);
+
+ /*
+ * Prune this leaf node unless it is the one we
+ * started with.
+ */
+ if (s != serio) {
+ struct serio *parent = s->parent;
+
+ device_release_driver(&s->dev);
+ serio_destroy_port(s);
+
+ s = parent;
+ }
+ }
+
+ /*
+ * OK, no children left, now disconnect this port.
+ */
+ device_release_driver(&serio->dev);
+}
+
+void serio_rescan(struct serio *serio)
+{
+ serio_queue_event(serio, NULL, SERIO_RESCAN_PORT);
+}
+EXPORT_SYMBOL(serio_rescan);
+
+void serio_reconnect(struct serio *serio)
+{
+ serio_queue_event(serio, NULL, SERIO_RECONNECT_SUBTREE);
+}
+EXPORT_SYMBOL(serio_reconnect);
+
+/*
+ * Submits register request to kseriod for subsequent execution.
+ * Note that port registration is always asynchronous.
+ */
+void __serio_register_port(struct serio *serio, struct module *owner)
+{
+ serio_init_port(serio);
+ serio_queue_event(serio, owner, SERIO_REGISTER_PORT);
+}
+EXPORT_SYMBOL(__serio_register_port);
+
+/*
+ * Synchronously unregisters serio port.
+ */
+void serio_unregister_port(struct serio *serio)
+{
+ mutex_lock(&serio_mutex);
+ serio_disconnect_port(serio);
+ serio_destroy_port(serio);
+ mutex_unlock(&serio_mutex);
+}
+EXPORT_SYMBOL(serio_unregister_port);
+
+/*
+ * Safely unregisters children ports if they are present.
+ */
+void serio_unregister_child_port(struct serio *serio)
+{
+ struct serio *s, *next;
+
+ mutex_lock(&serio_mutex);
+ list_for_each_entry_safe(s, next, &serio->children, child_node) {
+ serio_disconnect_port(s);
+ serio_destroy_port(s);
+ }
+ mutex_unlock(&serio_mutex);
+}
+EXPORT_SYMBOL(serio_unregister_child_port);
+
+
+/*
+ * Serio driver operations
+ */
+
+static ssize_t description_show(struct device_driver *drv, char *buf)
+{
+ struct serio_driver *driver = to_serio_driver(drv);
+ return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)");
+}
+static DRIVER_ATTR_RO(description);
+
+static ssize_t bind_mode_show(struct device_driver *drv, char *buf)
+{
+ struct serio_driver *serio_drv = to_serio_driver(drv);
+ return sprintf(buf, "%s\n", serio_drv->manual_bind ? "manual" : "auto");
+}
+
+static ssize_t bind_mode_store(struct device_driver *drv, const char *buf, size_t count)
+{
+ struct serio_driver *serio_drv = to_serio_driver(drv);
+ int retval;
+
+ retval = count;
+ if (!strncmp(buf, "manual", count)) {
+ serio_drv->manual_bind = true;
+ } else if (!strncmp(buf, "auto", count)) {
+ serio_drv->manual_bind = false;
+ } else {
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+static DRIVER_ATTR_RW(bind_mode);
+
+static struct attribute *serio_driver_attrs[] = {
+ &driver_attr_description.attr,
+ &driver_attr_bind_mode.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(serio_driver);
+
+static int serio_driver_probe(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+ struct serio_driver *drv = to_serio_driver(dev->driver);
+
+ return serio_connect_driver(serio, drv);
+}
+
+static void serio_driver_remove(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ serio_disconnect_driver(serio);
+}
+
+static void serio_cleanup(struct serio *serio)
+{
+ mutex_lock(&serio->drv_mutex);
+ if (serio->drv && serio->drv->cleanup)
+ serio->drv->cleanup(serio);
+ mutex_unlock(&serio->drv_mutex);
+}
+
+static void serio_shutdown(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ serio_cleanup(serio);
+}
+
+static void serio_attach_driver(struct serio_driver *drv)
+{
+ int error;
+
+ error = driver_attach(&drv->driver);
+ if (error)
+ pr_warn("driver_attach() failed for %s with error %d\n",
+ drv->driver.name, error);
+}
+
+int __serio_register_driver(struct serio_driver *drv, struct module *owner, const char *mod_name)
+{
+ bool manual_bind = drv->manual_bind;
+ int error;
+
+ drv->driver.bus = &serio_bus;
+ drv->driver.owner = owner;
+ drv->driver.mod_name = mod_name;
+
+ /*
+ * Temporarily disable automatic binding because probing
+ * takes long time and we are better off doing it in kseriod
+ */
+ drv->manual_bind = true;
+
+ error = driver_register(&drv->driver);
+ if (error) {
+ pr_err("driver_register() failed for %s, error: %d\n",
+ drv->driver.name, error);
+ return error;
+ }
+
+ /*
+ * Restore original bind mode and let kseriod bind the
+ * driver to free ports
+ */
+ if (!manual_bind) {
+ drv->manual_bind = false;
+ error = serio_queue_event(drv, NULL, SERIO_ATTACH_DRIVER);
+ if (error) {
+ driver_unregister(&drv->driver);
+ return error;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(__serio_register_driver);
+
+void serio_unregister_driver(struct serio_driver *drv)
+{
+ struct serio *serio;
+
+ mutex_lock(&serio_mutex);
+
+ drv->manual_bind = true; /* so serio_find_driver ignores it */
+ serio_remove_pending_events(drv);
+
+start_over:
+ list_for_each_entry(serio, &serio_list, node) {
+ if (serio->drv == drv) {
+ serio_disconnect_port(serio);
+ serio_find_driver(serio);
+ /* we could've deleted some ports, restart */
+ goto start_over;
+ }
+ }
+
+ driver_unregister(&drv->driver);
+ mutex_unlock(&serio_mutex);
+}
+EXPORT_SYMBOL(serio_unregister_driver);
+
+static void serio_set_drv(struct serio *serio, struct serio_driver *drv)
+{
+ serio_pause_rx(serio);
+ serio->drv = drv;
+ serio_continue_rx(serio);
+}
+
+static int serio_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct serio *serio = to_serio_port(dev);
+ struct serio_driver *serio_drv = to_serio_driver(drv);
+
+ if (serio->manual_bind || serio_drv->manual_bind)
+ return 0;
+
+ return serio_match_port(serio_drv->id_table, serio);
+}
+
+#define SERIO_ADD_UEVENT_VAR(fmt, val...) \
+ do { \
+ int err = add_uevent_var(env, fmt, val); \
+ if (err) \
+ return err; \
+ } while (0)
+
+static int serio_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct serio *serio;
+
+ if (!dev)
+ return -ENODEV;
+
+ serio = to_serio_port(dev);
+
+ SERIO_ADD_UEVENT_VAR("SERIO_TYPE=%02x", serio->id.type);
+ SERIO_ADD_UEVENT_VAR("SERIO_PROTO=%02x", serio->id.proto);
+ SERIO_ADD_UEVENT_VAR("SERIO_ID=%02x", serio->id.id);
+ SERIO_ADD_UEVENT_VAR("SERIO_EXTRA=%02x", serio->id.extra);
+
+ SERIO_ADD_UEVENT_VAR("MODALIAS=serio:ty%02Xpr%02Xid%02Xex%02X",
+ serio->id.type, serio->id.proto, serio->id.id, serio->id.extra);
+
+ if (serio->firmware_id[0])
+ SERIO_ADD_UEVENT_VAR("SERIO_FIRMWARE_ID=%s",
+ serio->firmware_id);
+
+ return 0;
+}
+#undef SERIO_ADD_UEVENT_VAR
+
+#ifdef CONFIG_PM
+static int serio_suspend(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+
+ serio_cleanup(serio);
+
+ return 0;
+}
+
+static int serio_resume(struct device *dev)
+{
+ struct serio *serio = to_serio_port(dev);
+ int error = -ENOENT;
+
+ mutex_lock(&serio->drv_mutex);
+ if (serio->drv && serio->drv->fast_reconnect) {
+ error = serio->drv->fast_reconnect(serio);
+ if (error && error != -ENOENT)
+ dev_warn(dev, "fast reconnect failed with error %d\n",
+ error);
+ }
+ mutex_unlock(&serio->drv_mutex);
+
+ if (error) {
+ /*
+ * Driver reconnect can take a while, so better let
+ * kseriod deal with it.
+ */
+ serio_queue_event(serio, NULL, SERIO_RECONNECT_PORT);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops serio_pm_ops = {
+ .suspend = serio_suspend,
+ .resume = serio_resume,
+ .poweroff = serio_suspend,
+ .restore = serio_resume,
+};
+#endif /* CONFIG_PM */
+
+/* called from serio_driver->connect/disconnect methods under serio_mutex */
+int serio_open(struct serio *serio, struct serio_driver *drv)
+{
+ serio_set_drv(serio, drv);
+
+ if (serio->open && serio->open(serio)) {
+ serio_set_drv(serio, NULL);
+ return -1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(serio_open);
+
+/* called from serio_driver->connect/disconnect methods under serio_mutex */
+void serio_close(struct serio *serio)
+{
+ if (serio->close)
+ serio->close(serio);
+
+ serio_set_drv(serio, NULL);
+}
+EXPORT_SYMBOL(serio_close);
+
+irqreturn_t serio_interrupt(struct serio *serio,
+ unsigned char data, unsigned int dfl)
+{
+ unsigned long flags;
+ irqreturn_t ret = IRQ_NONE;
+
+ spin_lock_irqsave(&serio->lock, flags);
+
+ if (likely(serio->drv)) {
+ ret = serio->drv->interrupt(serio, data, dfl);
+ } else if (!dfl && device_is_registered(&serio->dev)) {
+ serio_rescan(serio);
+ ret = IRQ_HANDLED;
+ }
+
+ spin_unlock_irqrestore(&serio->lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(serio_interrupt);
+
+struct bus_type serio_bus = {
+ .name = "serio",
+ .drv_groups = serio_driver_groups,
+ .match = serio_bus_match,
+ .uevent = serio_uevent,
+ .probe = serio_driver_probe,
+ .remove = serio_driver_remove,
+ .shutdown = serio_shutdown,
+#ifdef CONFIG_PM
+ .pm = &serio_pm_ops,
+#endif
+};
+EXPORT_SYMBOL(serio_bus);
+
+static int __init serio_init(void)
+{
+ int error;
+
+ error = bus_register(&serio_bus);
+ if (error) {
+ pr_err("Failed to register serio bus, error: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void __exit serio_exit(void)
+{
+ bus_unregister(&serio_bus);
+
+ /*
+ * There should not be any outstanding events but work may
+ * still be scheduled so simply cancel it.
+ */
+ cancel_work_sync(&serio_event_work);
+}
+
+subsys_initcall(serio_init);
+module_exit(serio_exit);
diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c
new file mode 100644
index 000000000..1e4770094
--- /dev/null
+++ b/drivers/input/serio/serio_raw.c
@@ -0,0 +1,441 @@
+// 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[16];
+ 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;
+ int retval;
+
+ retval = mutex_lock_interruptible(&serio_raw_mutex);
+ if (retval)
+ return retval;
+
+ serio_raw = serio_raw_locate(iminor(inode));
+ if (!serio_raw) {
+ retval = -ENODEV;
+ goto out;
+ }
+
+ if (serio_raw->dead) {
+ retval = -ENODEV;
+ goto out;
+ }
+
+ client = kzalloc(sizeof(struct serio_raw_client), GFP_KERNEL);
+ if (!client) {
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ client->serio_raw = serio_raw;
+ file->private_data = client;
+
+ kref_get(&serio_raw->kref);
+
+ serio_pause_rx(serio_raw->serio);
+ list_add_tail(&client->node, &serio_raw->client_list);
+ serio_continue_rx(serio_raw->serio);
+
+out:
+ mutex_unlock(&serio_raw_mutex);
+ return retval;
+}
+
+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;
+
+ serio_pause_rx(serio_raw->serio);
+ list_del(&client->node);
+ serio_continue_rx(serio_raw->serio);
+
+ 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)
+{
+ bool empty;
+
+ serio_pause_rx(serio_raw->serio);
+
+ empty = serio_raw->head == serio_raw->tail;
+ if (!empty) {
+ *c = serio_raw->queue[serio_raw->tail];
+ serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN;
+ }
+
+ serio_continue_rx(serio_raw->serio);
+
+ return !empty;
+}
+
+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 retval = 0;
+ unsigned char c;
+
+ retval = mutex_lock_interruptible(&serio_raw_mutex);
+ if (retval)
+ return retval;
+
+ if (serio_raw->dead) {
+ retval = -ENODEV;
+ goto out;
+ }
+
+ if (count > 32)
+ count = 32;
+
+ while (count--) {
+ if (get_user(c, buffer++)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ if (serio_write(serio_raw->serio, c)) {
+ /* Either signal error or partial write */
+ if (retval == 0)
+ retval = -EIO;
+ goto out;
+ }
+
+ retval++;
+ }
+
+out:
+ mutex_unlock(&serio_raw_mutex);
+ return retval;
+}
+
+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(sizeof(struct serio_raw), GFP_KERNEL);
+ 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%ld", (long)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;
+
+ serio_pause_rx(serio_raw->serio);
+ list_for_each_entry(client, &serio_raw->client_list, node)
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+ serio_continue_rx(serio_raw->serio);
+
+ 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);
+
+ mutex_lock(&serio_raw_mutex);
+ serio_raw->dead = true;
+ list_del_init(&serio_raw->node);
+ mutex_unlock(&serio_raw_mutex);
+
+ 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);
diff --git a/drivers/input/serio/serport.c b/drivers/input/serio/serport.c
new file mode 100644
index 000000000..7f7ef0e3a
--- /dev/null
+++ b/drivers/input/serio/serport.c
@@ -0,0 +1,309 @@
+// 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serport->lock, flags);
+ set_bit(SERPORT_ACTIVE, &serport->flags);
+ spin_unlock_irqrestore(&serport->lock, flags);
+
+ return 0;
+}
+
+
+static void serport_serio_close(struct serio *serio)
+{
+ struct serport *serport = serio->port_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serport->lock, flags);
+ clear_bit(SERPORT_ACTIVE, &serport->flags);
+ spin_unlock_irqrestore(&serport->lock, 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(sizeof(struct serport), GFP_KERNEL);
+ if (!serport)
+ return -ENOMEM;
+
+ serport->tty = tty;
+ spin_lock_init(&serport->lock);
+ init_waitqueue_head(&serport->wait);
+
+ tty->disc_data = serport;
+ tty->receive_room = 256;
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+
+ return 0;
+}
+
+/*
+ * serport_ldisc_close() is the opposite of serport_ldisc_open()
+ */
+
+static void serport_ldisc_close(struct tty_struct *tty)
+{
+ struct serport *serport = (struct serport *) tty->disc_data;
+
+ kfree(serport);
+}
+
+/*
+ * serport_ldisc_receive() is called by the low level tty driver when characters
+ * are ready for us. We forward the characters and flags, one by one to the
+ * 'interrupt' routine.
+ */
+
+static void serport_ldisc_receive(struct tty_struct *tty,
+ const unsigned char *cp, const char *fp, int count)
+{
+ struct serport *serport = (struct serport*) tty->disc_data;
+ unsigned long flags;
+ unsigned int ch_flags = 0;
+ int i;
+
+ spin_lock_irqsave(&serport->lock, flags);
+
+ if (!test_bit(SERPORT_ACTIVE, &serport->flags))
+ goto out;
+
+ 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);
+ }
+
+out:
+ spin_unlock_irqrestore(&serport->lock, flags);
+}
+
+/*
+ * serport_ldisc_read() just waits indefinitely if everything goes well.
+ * However, when the serio driver closes the serio port, it finishes,
+ * returning 0 characters.
+ */
+
+static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file,
+ unsigned char *kbuf, size_t nr,
+ void **cookie, unsigned long offset)
+{
+ struct serport *serport = (struct serport*) tty->disc_data;
+ struct serio *serio;
+
+ if (test_and_set_bit(SERPORT_BUSY, &serport->flags))
+ return -EBUSY;
+
+ serport->serio = serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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 = (struct serport *) tty->disc_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serport->lock, flags);
+ set_bit(SERPORT_DEAD, &serport->flags);
+ spin_unlock_irqrestore(&serport->lock, flags);
+
+ wake_up_interruptible(&serport->wait);
+}
+
+static void serport_ldisc_write_wakeup(struct tty_struct * tty)
+{
+ struct serport *serport = (struct serport *) tty->disc_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&serport->lock, flags);
+ if (test_bit(SERPORT_ACTIVE, &serport->flags))
+ serio_drv_write_wakeup(serport->serio);
+ spin_unlock_irqrestore(&serport->lock, flags);
+}
+
+/*
+ * The line discipline structure.
+ */
+
+static struct tty_ldisc_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);
diff --git a/drivers/input/serio/sun4i-ps2.c b/drivers/input/serio/sun4i-ps2.c
new file mode 100644
index 000000000..eb2626401
--- /dev/null
+++ b/drivers/input/serio/sun4i-ps2.c
@@ -0,0 +1,338 @@
+// 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/mod_devicetable.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;
+
+ spin_lock(&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);
+
+ spin_unlock(&drvdata->lock);
+
+ 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;
+ unsigned long flags;
+
+ /* 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;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ writel(rval, drvdata->reg_base + PS2_REG_GCTL);
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ 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(sizeof(struct sun4i_ps2data), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ 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 int 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);
+
+ return 0;
+}
+
+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");
diff --git a/drivers/input/serio/userio.c b/drivers/input/serio/userio.c
new file mode 100644
index 000000000..9ab5c45c3
--- /dev/null
+++ b/drivers/input/serio/userio.c
@@ -0,0 +1,285 @@
+/*
+ * userio kernel serio device emulation module
+ * Copyright (C) 2015 Red Hat
+ * Copyright (C) 2015 Stephen Chandler 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;
+ unsigned long flags;
+
+ spin_lock_irqsave(&userio->buf_lock, flags);
+
+ 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");
+
+ spin_unlock_irqrestore(&userio->buf_lock, flags);
+
+ wake_up_interruptible(&userio->waitq);
+
+ return 0;
+}
+
+static int userio_char_open(struct inode *inode, struct file *file)
+{
+ struct userio_device *userio;
+
+ userio = kzalloc(sizeof(struct userio_device), GFP_KERNEL);
+ if (!userio)
+ return -ENOMEM;
+
+ mutex_init(&userio->mutex);
+ spin_lock_init(&userio->buf_lock);
+ init_waitqueue_head(&userio->waitq);
+
+ userio->serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!userio->serio) {
+ kfree(userio);
+ return -ENOMEM;
+ }
+
+ userio->serio->write = userio_device_write;
+ userio->serio->port_data = userio;
+
+ file->private_data = 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 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 nonwrap_len, copylen;
+ unsigned char buf[USERIO_BUFSIZE];
+ unsigned long flags;
+
+ /*
+ * 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 (;;) {
+ spin_lock_irqsave(&userio->buf_lock, flags);
+
+ nonwrap_len = CIRC_CNT_TO_END(userio->head,
+ userio->tail,
+ USERIO_BUFSIZE);
+ copylen = min(nonwrap_len, count);
+ if (copylen) {
+ memcpy(buf, &userio->buf[userio->tail], copylen);
+ userio->tail = (userio->tail + copylen) %
+ USERIO_BUFSIZE;
+ }
+
+ spin_unlock_irqrestore(&userio->buf_lock, flags);
+
+ if (nonwrap_len)
+ 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 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;
+
+ error = mutex_lock_interruptible(&userio->mutex);
+ if (error)
+ return error;
+
+ 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");
+
+ error = -EINVAL;
+ goto out;
+ }
+
+ if (userio->running) {
+ dev_warn(userio_misc.this_device,
+ "Begin command sent, but we're already running\n");
+ error = -EBUSY;
+ goto out;
+ }
+
+ 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");
+ error = -EBUSY;
+ goto out;
+ }
+
+ userio->serio->id.type = 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");
+ error = -ENODEV;
+ goto out;
+ }
+
+ serio_interrupt(userio->serio, cmd.data, 0);
+ break;
+
+ default:
+ error = -EOPNOTSUPP;
+ goto out;
+ }
+
+out:
+ mutex_unlock(&userio->mutex);
+ return error ?: 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,
+ .llseek = no_llseek,
+};
+
+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("Stephen Chandler Paul <thatslyude@gmail.com>");
+MODULE_DESCRIPTION("Virtual Serio Device Support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/serio/xilinx_ps2.c b/drivers/input/serio/xilinx_ps2.c
new file mode 100644
index 000000000..960d7601f
--- /dev/null
+++ b/drivers/input/serio/xilinx_ps2.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Xilinx XPS PS/2 device driver
+ *
+ * (c) 2005 MontaVista Software, Inc.
+ * (c) 2008 Xilinx, Inc.
+ */
+
+
+#include <linux/module.h>
+#include <linux/serio.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+#define DRIVER_NAME "xilinx_ps2"
+
+/* Register offsets for the xps2 device */
+#define XPS2_SRST_OFFSET 0x00000000 /* Software Reset register */
+#define XPS2_STATUS_OFFSET 0x00000004 /* Status register */
+#define XPS2_RX_DATA_OFFSET 0x00000008 /* Receive Data register */
+#define XPS2_TX_DATA_OFFSET 0x0000000C /* Transmit Data register */
+#define XPS2_GIER_OFFSET 0x0000002C /* Global Interrupt Enable reg */
+#define XPS2_IPISR_OFFSET 0x00000030 /* Interrupt Status register */
+#define XPS2_IPIER_OFFSET 0x00000038 /* Interrupt Enable register */
+
+/* Reset Register Bit Definitions */
+#define XPS2_SRST_RESET 0x0000000A /* Software Reset */
+
+/* Status Register Bit Positions */
+#define XPS2_STATUS_RX_FULL 0x00000001 /* Receive Full */
+#define XPS2_STATUS_TX_FULL 0x00000002 /* Transmit Full */
+
+/*
+ * Bit definitions for ISR/IER registers. Both the registers have the same bit
+ * definitions and are only defined once.
+ */
+#define XPS2_IPIXR_WDT_TOUT 0x00000001 /* Watchdog Timeout Interrupt */
+#define XPS2_IPIXR_TX_NOACK 0x00000002 /* Transmit No ACK Interrupt */
+#define XPS2_IPIXR_TX_ACK 0x00000004 /* Transmit ACK (Data) Interrupt */
+#define XPS2_IPIXR_RX_OVF 0x00000008 /* Receive Overflow Interrupt */
+#define XPS2_IPIXR_RX_ERR 0x00000010 /* Receive Error Interrupt */
+#define XPS2_IPIXR_RX_FULL 0x00000020 /* Receive Data Interrupt */
+
+/* Mask for all the Transmit Interrupts */
+#define XPS2_IPIXR_TX_ALL (XPS2_IPIXR_TX_NOACK | XPS2_IPIXR_TX_ACK)
+
+/* Mask for all the Receive Interrupts */
+#define XPS2_IPIXR_RX_ALL (XPS2_IPIXR_RX_OVF | XPS2_IPIXR_RX_ERR | \
+ XPS2_IPIXR_RX_FULL)
+
+/* Mask for all the Interrupts */
+#define XPS2_IPIXR_ALL (XPS2_IPIXR_TX_ALL | XPS2_IPIXR_RX_ALL | \
+ XPS2_IPIXR_WDT_TOUT)
+
+/* Global Interrupt Enable mask */
+#define XPS2_GIER_GIE_MASK 0x80000000
+
+struct xps2data {
+ int irq;
+ spinlock_t lock;
+ void __iomem *base_address; /* virt. address of control registers */
+ unsigned int flags;
+ struct serio *serio; /* serio */
+ struct device *dev;
+};
+
+/************************************/
+/* XPS PS/2 data transmission calls */
+/************************************/
+
+/**
+ * xps2_recv() - attempts to receive a byte from the PS/2 port.
+ * @drvdata: pointer to ps2 device private data structure
+ * @byte: address where the read data will be copied
+ *
+ * If there is any data available in the PS/2 receiver, this functions reads
+ * the data, otherwise it returns error.
+ */
+static int xps2_recv(struct xps2data *drvdata, u8 *byte)
+{
+ u32 sr;
+ int status = -1;
+
+ /* If there is data available in the PS/2 receiver, read it */
+ sr = in_be32(drvdata->base_address + XPS2_STATUS_OFFSET);
+ if (sr & XPS2_STATUS_RX_FULL) {
+ *byte = in_be32(drvdata->base_address + XPS2_RX_DATA_OFFSET);
+ status = 0;
+ }
+
+ return status;
+}
+
+/*********************/
+/* Interrupt handler */
+/*********************/
+static irqreturn_t xps2_interrupt(int irq, void *dev_id)
+{
+ struct xps2data *drvdata = dev_id;
+ u32 intr_sr;
+ u8 c;
+ int status;
+
+ /* Get the PS/2 interrupts and clear them */
+ intr_sr = in_be32(drvdata->base_address + XPS2_IPISR_OFFSET);
+ out_be32(drvdata->base_address + XPS2_IPISR_OFFSET, intr_sr);
+
+ /* Check which interrupt is active */
+ if (intr_sr & XPS2_IPIXR_RX_OVF)
+ dev_warn(drvdata->dev, "receive overrun error\n");
+
+ if (intr_sr & XPS2_IPIXR_RX_ERR)
+ drvdata->flags |= SERIO_PARITY;
+
+ if (intr_sr & (XPS2_IPIXR_TX_NOACK | XPS2_IPIXR_WDT_TOUT))
+ drvdata->flags |= SERIO_TIMEOUT;
+
+ if (intr_sr & XPS2_IPIXR_RX_FULL) {
+ status = xps2_recv(drvdata, &c);
+
+ /* Error, if a byte is not received */
+ if (status) {
+ dev_err(drvdata->dev,
+ "wrong rcvd byte count (%d)\n", status);
+ } else {
+ serio_interrupt(drvdata->serio, c, drvdata->flags);
+ drvdata->flags = 0;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*******************/
+/* serio callbacks */
+/*******************/
+
+/**
+ * sxps2_write() - sends a byte out through the PS/2 port.
+ * @pserio: pointer to the serio structure of the PS/2 port
+ * @c: data that needs to be written to the PS/2 port
+ *
+ * This function checks if the PS/2 transmitter is empty and sends a byte.
+ * Otherwise it returns error. Transmission fails only when nothing is connected
+ * to the PS/2 port. Thats why, we do not try to resend the data in case of a
+ * failure.
+ */
+static int sxps2_write(struct serio *pserio, unsigned char c)
+{
+ struct xps2data *drvdata = pserio->port_data;
+ unsigned long flags;
+ u32 sr;
+ int status = -1;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ /* If the PS/2 transmitter is empty send a byte of data */
+ sr = in_be32(drvdata->base_address + XPS2_STATUS_OFFSET);
+ if (!(sr & XPS2_STATUS_TX_FULL)) {
+ out_be32(drvdata->base_address + XPS2_TX_DATA_OFFSET, c);
+ status = 0;
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ return status;
+}
+
+/**
+ * sxps2_open() - called when a port is opened by the higher layer.
+ * @pserio: pointer to the serio structure of the PS/2 device
+ *
+ * This function requests irq and enables interrupts for the PS/2 device.
+ */
+static int sxps2_open(struct serio *pserio)
+{
+ struct xps2data *drvdata = pserio->port_data;
+ int error;
+ u8 c;
+
+ error = request_irq(drvdata->irq, &xps2_interrupt, 0,
+ DRIVER_NAME, drvdata);
+ if (error) {
+ dev_err(drvdata->dev,
+ "Couldn't allocate interrupt %d\n", drvdata->irq);
+ return error;
+ }
+
+ /* start reception by enabling the interrupts */
+ out_be32(drvdata->base_address + XPS2_GIER_OFFSET, XPS2_GIER_GIE_MASK);
+ out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, XPS2_IPIXR_RX_ALL);
+ (void)xps2_recv(drvdata, &c);
+
+ return 0; /* success */
+}
+
+/**
+ * sxps2_close() - frees the interrupt.
+ * @pserio: pointer to the serio structure of the PS/2 device
+ *
+ * This function frees the irq and disables interrupts for the PS/2 device.
+ */
+static void sxps2_close(struct serio *pserio)
+{
+ struct xps2data *drvdata = pserio->port_data;
+
+ /* Disable the PS2 interrupts */
+ out_be32(drvdata->base_address + XPS2_GIER_OFFSET, 0x00);
+ out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, 0x00);
+ free_irq(drvdata->irq, drvdata);
+}
+
+/**
+ * xps2_of_probe - probe method for the PS/2 device.
+ * @of_dev: pointer to OF device structure
+ * @match: pointer to the structure used for matching a device
+ *
+ * This function probes the PS/2 device in the device tree.
+ * It initializes the driver data structure and the hardware.
+ * It returns 0, if the driver is bound to the PS/2 device, or a negative
+ * value if there is an error.
+ */
+static int xps2_of_probe(struct platform_device *ofdev)
+{
+ struct resource r_mem; /* IO mem resources */
+ struct xps2data *drvdata;
+ struct serio *serio;
+ struct device *dev = &ofdev->dev;
+ resource_size_t remap_size, phys_addr;
+ unsigned int irq;
+ int error;
+
+ dev_info(dev, "Device Tree Probing \'%pOFn\'\n", dev->of_node);
+
+ /* Get iospace for the device */
+ error = of_address_to_resource(dev->of_node, 0, &r_mem);
+ if (error) {
+ dev_err(dev, "invalid address\n");
+ return error;
+ }
+
+ /* Get IRQ for the device */
+ irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (!irq) {
+ dev_err(dev, "no IRQ found\n");
+ return -ENODEV;
+ }
+
+ drvdata = kzalloc(sizeof(struct xps2data), GFP_KERNEL);
+ serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!drvdata || !serio) {
+ error = -ENOMEM;
+ goto failed1;
+ }
+
+ spin_lock_init(&drvdata->lock);
+ drvdata->irq = irq;
+ drvdata->serio = serio;
+ drvdata->dev = dev;
+
+ phys_addr = r_mem.start;
+ remap_size = resource_size(&r_mem);
+ if (!request_mem_region(phys_addr, remap_size, DRIVER_NAME)) {
+ dev_err(dev, "Couldn't lock memory region at 0x%08llX\n",
+ (unsigned long long)phys_addr);
+ error = -EBUSY;
+ goto failed1;
+ }
+
+ /* Fill in configuration data and add them to the list */
+ drvdata->base_address = ioremap(phys_addr, remap_size);
+ if (drvdata->base_address == NULL) {
+ dev_err(dev, "Couldn't ioremap memory at 0x%08llX\n",
+ (unsigned long long)phys_addr);
+ error = -EFAULT;
+ goto failed2;
+ }
+
+ /* Disable all the interrupts, just in case */
+ out_be32(drvdata->base_address + XPS2_IPIER_OFFSET, 0);
+
+ /*
+ * Reset the PS2 device and abort any current transaction,
+ * to make sure we have the PS2 in a good state.
+ */
+ out_be32(drvdata->base_address + XPS2_SRST_OFFSET, XPS2_SRST_RESET);
+
+ dev_info(dev, "Xilinx PS2 at 0x%08llX mapped to 0x%p, irq=%d\n",
+ (unsigned long long)phys_addr, drvdata->base_address,
+ drvdata->irq);
+
+ serio->id.type = SERIO_8042;
+ serio->write = sxps2_write;
+ serio->open = sxps2_open;
+ serio->close = sxps2_close;
+ serio->port_data = drvdata;
+ serio->dev.parent = dev;
+ snprintf(serio->name, sizeof(serio->name),
+ "Xilinx XPS PS/2 at %08llX", (unsigned long long)phys_addr);
+ snprintf(serio->phys, sizeof(serio->phys),
+ "xilinxps2/serio at %08llX", (unsigned long long)phys_addr);
+
+ serio_register_port(serio);
+
+ platform_set_drvdata(ofdev, drvdata);
+ return 0; /* success */
+
+failed2:
+ release_mem_region(phys_addr, remap_size);
+failed1:
+ kfree(serio);
+ kfree(drvdata);
+
+ return error;
+}
+
+/**
+ * xps2_of_remove - unbinds the driver from the PS/2 device.
+ * @of_dev: pointer to OF device structure
+ *
+ * This function is called if a device is physically removed from the system or
+ * if the driver module is being unloaded. It frees any resources allocated to
+ * the device.
+ */
+static int xps2_of_remove(struct platform_device *of_dev)
+{
+ struct xps2data *drvdata = platform_get_drvdata(of_dev);
+ struct resource r_mem; /* IO mem resources */
+
+ serio_unregister_port(drvdata->serio);
+ iounmap(drvdata->base_address);
+
+ /* Get iospace of the device */
+ if (of_address_to_resource(of_dev->dev.of_node, 0, &r_mem))
+ dev_err(drvdata->dev, "invalid address\n");
+ else
+ release_mem_region(r_mem.start, resource_size(&r_mem));
+
+ kfree(drvdata);
+
+ return 0;
+}
+
+/* Match table for of_platform binding */
+static const struct of_device_id xps2_of_match[] = {
+ { .compatible = "xlnx,xps-ps2-1.00.a", },
+ { /* end of list */ },
+};
+MODULE_DEVICE_TABLE(of, xps2_of_match);
+
+static struct platform_driver xps2_of_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = xps2_of_match,
+ },
+ .probe = xps2_of_probe,
+ .remove = xps2_of_remove,
+};
+module_platform_driver(xps2_of_driver);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Xilinx XPS PS/2 driver");
+MODULE_LICENSE("GPL");
+