18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  i8042 keyboard and mouse controller driver for Linux
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (c) 1999-2004 Vojtech Pavlik
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/types.h>
128c2ecf20Sopenharmony_ci#include <linux/delay.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/ioport.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/serio.h>
188c2ecf20Sopenharmony_ci#include <linux/err.h>
198c2ecf20Sopenharmony_ci#include <linux/rcupdate.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <linux/i8042.h>
228c2ecf20Sopenharmony_ci#include <linux/slab.h>
238c2ecf20Sopenharmony_ci#include <linux/suspend.h>
248c2ecf20Sopenharmony_ci#include <linux/property.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#include <asm/io.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
298c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("i8042 keyboard and mouse controller driver");
308c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic bool i8042_nokbd;
338c2ecf20Sopenharmony_cimodule_param_named(nokbd, i8042_nokbd, bool, 0);
348c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nokbd, "Do not probe or use KBD port.");
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic bool i8042_noaux;
378c2ecf20Sopenharmony_cimodule_param_named(noaux, i8042_noaux, bool, 0);
388c2ecf20Sopenharmony_ciMODULE_PARM_DESC(noaux, "Do not probe or use AUX (mouse) port.");
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic bool i8042_nomux;
418c2ecf20Sopenharmony_cimodule_param_named(nomux, i8042_nomux, bool, 0);
428c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nomux, "Do not check whether an active multiplexing controller is present.");
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic bool i8042_unlock;
458c2ecf20Sopenharmony_cimodule_param_named(unlock, i8042_unlock, bool, 0);
468c2ecf20Sopenharmony_ciMODULE_PARM_DESC(unlock, "Ignore keyboard lock.");
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic bool i8042_probe_defer;
498c2ecf20Sopenharmony_cimodule_param_named(probe_defer, i8042_probe_defer, bool, 0);
508c2ecf20Sopenharmony_ciMODULE_PARM_DESC(probe_defer, "Allow deferred probing.");
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cienum i8042_controller_reset_mode {
538c2ecf20Sopenharmony_ci	I8042_RESET_NEVER,
548c2ecf20Sopenharmony_ci	I8042_RESET_ALWAYS,
558c2ecf20Sopenharmony_ci	I8042_RESET_ON_S2RAM,
568c2ecf20Sopenharmony_ci#define I8042_RESET_DEFAULT	I8042_RESET_ON_S2RAM
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_cistatic enum i8042_controller_reset_mode i8042_reset = I8042_RESET_DEFAULT;
598c2ecf20Sopenharmony_cistatic int i8042_set_reset(const char *val, const struct kernel_param *kp)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	enum i8042_controller_reset_mode *arg = kp->arg;
628c2ecf20Sopenharmony_ci	int error;
638c2ecf20Sopenharmony_ci	bool reset;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (val) {
668c2ecf20Sopenharmony_ci		error = kstrtobool(val, &reset);
678c2ecf20Sopenharmony_ci		if (error)
688c2ecf20Sopenharmony_ci			return error;
698c2ecf20Sopenharmony_ci	} else {
708c2ecf20Sopenharmony_ci		reset = true;
718c2ecf20Sopenharmony_ci	}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	*arg = reset ? I8042_RESET_ALWAYS : I8042_RESET_NEVER;
748c2ecf20Sopenharmony_ci	return 0;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic const struct kernel_param_ops param_ops_reset_param = {
788c2ecf20Sopenharmony_ci	.flags = KERNEL_PARAM_OPS_FL_NOARG,
798c2ecf20Sopenharmony_ci	.set = i8042_set_reset,
808c2ecf20Sopenharmony_ci};
818c2ecf20Sopenharmony_ci#define param_check_reset_param(name, p)	\
828c2ecf20Sopenharmony_ci	__param_check(name, p, enum i8042_controller_reset_mode)
838c2ecf20Sopenharmony_cimodule_param_named(reset, i8042_reset, reset_param, 0);
848c2ecf20Sopenharmony_ciMODULE_PARM_DESC(reset, "Reset controller on resume, cleanup or both");
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic bool i8042_direct;
878c2ecf20Sopenharmony_cimodule_param_named(direct, i8042_direct, bool, 0);
888c2ecf20Sopenharmony_ciMODULE_PARM_DESC(direct, "Put keyboard port into non-translated mode.");
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic bool i8042_dumbkbd;
918c2ecf20Sopenharmony_cimodule_param_named(dumbkbd, i8042_dumbkbd, bool, 0);
928c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dumbkbd, "Pretend that controller can only read data from keyboard");
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic bool i8042_noloop;
958c2ecf20Sopenharmony_cimodule_param_named(noloop, i8042_noloop, bool, 0);
968c2ecf20Sopenharmony_ciMODULE_PARM_DESC(noloop, "Disable the AUX Loopback command while probing for the AUX port");
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic bool i8042_notimeout;
998c2ecf20Sopenharmony_cimodule_param_named(notimeout, i8042_notimeout, bool, 0);
1008c2ecf20Sopenharmony_ciMODULE_PARM_DESC(notimeout, "Ignore timeouts signalled by i8042");
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic bool i8042_kbdreset;
1038c2ecf20Sopenharmony_cimodule_param_named(kbdreset, i8042_kbdreset, bool, 0);
1048c2ecf20Sopenharmony_ciMODULE_PARM_DESC(kbdreset, "Reset device connected to KBD port");
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci#ifdef CONFIG_X86
1078c2ecf20Sopenharmony_cistatic bool i8042_dritek;
1088c2ecf20Sopenharmony_cimodule_param_named(dritek, i8042_dritek, bool, 0);
1098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dritek, "Force enable the Dritek keyboard extension");
1108c2ecf20Sopenharmony_ci#endif
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
1138c2ecf20Sopenharmony_cistatic bool i8042_nopnp;
1148c2ecf20Sopenharmony_cimodule_param_named(nopnp, i8042_nopnp, bool, 0);
1158c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nopnp, "Do not use PNP to detect controller settings");
1168c2ecf20Sopenharmony_ci#endif
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci#define DEBUG
1198c2ecf20Sopenharmony_ci#ifdef DEBUG
1208c2ecf20Sopenharmony_cistatic bool i8042_debug;
1218c2ecf20Sopenharmony_cimodule_param_named(debug, i8042_debug, bool, 0600);
1228c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off");
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic bool i8042_unmask_kbd_data;
1258c2ecf20Sopenharmony_cimodule_param_named(unmask_kbd_data, i8042_unmask_kbd_data, bool, 0600);
1268c2ecf20Sopenharmony_ciMODULE_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]");
1278c2ecf20Sopenharmony_ci#endif
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic bool i8042_present;
1308c2ecf20Sopenharmony_cistatic bool i8042_bypass_aux_irq_test;
1318c2ecf20Sopenharmony_cistatic char i8042_kbd_firmware_id[128];
1328c2ecf20Sopenharmony_cistatic char i8042_aux_firmware_id[128];
1338c2ecf20Sopenharmony_cistatic struct fwnode_handle *i8042_kbd_fwnode;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci#include "i8042.h"
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci/*
1388c2ecf20Sopenharmony_ci * i8042_lock protects serialization between i8042_command and
1398c2ecf20Sopenharmony_ci * the interrupt handler.
1408c2ecf20Sopenharmony_ci */
1418c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(i8042_lock);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/*
1448c2ecf20Sopenharmony_ci * Writers to AUX and KBD ports as well as users issuing i8042_command
1458c2ecf20Sopenharmony_ci * directly should acquire i8042_mutex (by means of calling
1468c2ecf20Sopenharmony_ci * i8042_lock_chip() and i8042_unlock_ship() helpers) to ensure that
1478c2ecf20Sopenharmony_ci * they do not disturb each other (unfortunately in many i8042
1488c2ecf20Sopenharmony_ci * implementations write to one of the ports will immediately abort
1498c2ecf20Sopenharmony_ci * command that is being processed by another port).
1508c2ecf20Sopenharmony_ci */
1518c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(i8042_mutex);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistruct i8042_port {
1548c2ecf20Sopenharmony_ci	struct serio *serio;
1558c2ecf20Sopenharmony_ci	int irq;
1568c2ecf20Sopenharmony_ci	bool exists;
1578c2ecf20Sopenharmony_ci	bool driver_bound;
1588c2ecf20Sopenharmony_ci	signed char mux;
1598c2ecf20Sopenharmony_ci};
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci#define I8042_KBD_PORT_NO	0
1628c2ecf20Sopenharmony_ci#define I8042_AUX_PORT_NO	1
1638c2ecf20Sopenharmony_ci#define I8042_MUX_PORT_NO	2
1648c2ecf20Sopenharmony_ci#define I8042_NUM_PORTS		(I8042_NUM_MUX_PORTS + 2)
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic struct i8042_port i8042_ports[I8042_NUM_PORTS];
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic unsigned char i8042_initial_ctr;
1698c2ecf20Sopenharmony_cistatic unsigned char i8042_ctr;
1708c2ecf20Sopenharmony_cistatic bool i8042_mux_present;
1718c2ecf20Sopenharmony_cistatic bool i8042_kbd_irq_registered;
1728c2ecf20Sopenharmony_cistatic bool i8042_aux_irq_registered;
1738c2ecf20Sopenharmony_cistatic unsigned char i8042_suppress_kbd_ack;
1748c2ecf20Sopenharmony_cistatic struct platform_device *i8042_platform_device;
1758c2ecf20Sopenharmony_cistatic struct notifier_block i8042_kbd_bind_notifier_block;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic irqreturn_t i8042_interrupt(int irq, void *dev_id);
1788c2ecf20Sopenharmony_cistatic bool (*i8042_platform_filter)(unsigned char data, unsigned char str,
1798c2ecf20Sopenharmony_ci				     struct serio *serio);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_civoid i8042_lock_chip(void)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	mutex_lock(&i8042_mutex);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ciEXPORT_SYMBOL(i8042_lock_chip);
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_civoid i8042_unlock_chip(void)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	mutex_unlock(&i8042_mutex);
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ciEXPORT_SYMBOL(i8042_unlock_chip);
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ciint i8042_install_filter(bool (*filter)(unsigned char data, unsigned char str,
1948c2ecf20Sopenharmony_ci					struct serio *serio))
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	unsigned long flags;
1978c2ecf20Sopenharmony_ci	int ret = 0;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	if (i8042_platform_filter) {
2028c2ecf20Sopenharmony_ci		ret = -EBUSY;
2038c2ecf20Sopenharmony_ci		goto out;
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	i8042_platform_filter = filter;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciout:
2098c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
2108c2ecf20Sopenharmony_ci	return ret;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(i8042_install_filter);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ciint i8042_remove_filter(bool (*filter)(unsigned char data, unsigned char str,
2158c2ecf20Sopenharmony_ci				       struct serio *port))
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	unsigned long flags;
2188c2ecf20Sopenharmony_ci	int ret = 0;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	if (i8042_platform_filter != filter) {
2238c2ecf20Sopenharmony_ci		ret = -EINVAL;
2248c2ecf20Sopenharmony_ci		goto out;
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	i8042_platform_filter = NULL;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ciout:
2308c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
2318c2ecf20Sopenharmony_ci	return ret;
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ciEXPORT_SYMBOL(i8042_remove_filter);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci/*
2368c2ecf20Sopenharmony_ci * The i8042_wait_read() and i8042_wait_write functions wait for the i8042 to
2378c2ecf20Sopenharmony_ci * be ready for reading values from it / writing values to it.
2388c2ecf20Sopenharmony_ci * Called always with i8042_lock held.
2398c2ecf20Sopenharmony_ci */
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic int i8042_wait_read(void)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	int i = 0;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) {
2468c2ecf20Sopenharmony_ci		udelay(50);
2478c2ecf20Sopenharmony_ci		i++;
2488c2ecf20Sopenharmony_ci	}
2498c2ecf20Sopenharmony_ci	return -(i == I8042_CTL_TIMEOUT);
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic int i8042_wait_write(void)
2538c2ecf20Sopenharmony_ci{
2548c2ecf20Sopenharmony_ci	int i = 0;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) {
2578c2ecf20Sopenharmony_ci		udelay(50);
2588c2ecf20Sopenharmony_ci		i++;
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci	return -(i == I8042_CTL_TIMEOUT);
2618c2ecf20Sopenharmony_ci}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci/*
2648c2ecf20Sopenharmony_ci * i8042_flush() flushes all data that may be in the keyboard and mouse buffers
2658c2ecf20Sopenharmony_ci * of the i8042 down the toilet.
2668c2ecf20Sopenharmony_ci */
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic int i8042_flush(void)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	unsigned long flags;
2718c2ecf20Sopenharmony_ci	unsigned char data, str;
2728c2ecf20Sopenharmony_ci	int count = 0;
2738c2ecf20Sopenharmony_ci	int retval = 0;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	while ((str = i8042_read_status()) & I8042_STR_OBF) {
2788c2ecf20Sopenharmony_ci		if (count++ < I8042_BUFFER_SIZE) {
2798c2ecf20Sopenharmony_ci			udelay(50);
2808c2ecf20Sopenharmony_ci			data = i8042_read_data();
2818c2ecf20Sopenharmony_ci			dbg("%02x <- i8042 (flush, %s)\n",
2828c2ecf20Sopenharmony_ci			    data, str & I8042_STR_AUXDATA ? "aux" : "kbd");
2838c2ecf20Sopenharmony_ci		} else {
2848c2ecf20Sopenharmony_ci			retval = -EIO;
2858c2ecf20Sopenharmony_ci			break;
2868c2ecf20Sopenharmony_ci		}
2878c2ecf20Sopenharmony_ci	}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	return retval;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci/*
2958c2ecf20Sopenharmony_ci * i8042_command() executes a command on the i8042. It also sends the input
2968c2ecf20Sopenharmony_ci * parameter(s) of the commands to it, and receives the output value(s). The
2978c2ecf20Sopenharmony_ci * parameters are to be stored in the param array, and the output is placed
2988c2ecf20Sopenharmony_ci * into the same array. The number of the parameters and output values is
2998c2ecf20Sopenharmony_ci * encoded in bits 8-11 of the command number.
3008c2ecf20Sopenharmony_ci */
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_cistatic int __i8042_command(unsigned char *param, int command)
3038c2ecf20Sopenharmony_ci{
3048c2ecf20Sopenharmony_ci	int i, error;
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	if (i8042_noloop && command == I8042_CMD_AUX_LOOP)
3078c2ecf20Sopenharmony_ci		return -1;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	error = i8042_wait_write();
3108c2ecf20Sopenharmony_ci	if (error)
3118c2ecf20Sopenharmony_ci		return error;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	dbg("%02x -> i8042 (command)\n", command & 0xff);
3148c2ecf20Sopenharmony_ci	i8042_write_command(command & 0xff);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	for (i = 0; i < ((command >> 12) & 0xf); i++) {
3178c2ecf20Sopenharmony_ci		error = i8042_wait_write();
3188c2ecf20Sopenharmony_ci		if (error) {
3198c2ecf20Sopenharmony_ci			dbg("     -- i8042 (wait write timeout)\n");
3208c2ecf20Sopenharmony_ci			return error;
3218c2ecf20Sopenharmony_ci		}
3228c2ecf20Sopenharmony_ci		dbg("%02x -> i8042 (parameter)\n", param[i]);
3238c2ecf20Sopenharmony_ci		i8042_write_data(param[i]);
3248c2ecf20Sopenharmony_ci	}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	for (i = 0; i < ((command >> 8) & 0xf); i++) {
3278c2ecf20Sopenharmony_ci		error = i8042_wait_read();
3288c2ecf20Sopenharmony_ci		if (error) {
3298c2ecf20Sopenharmony_ci			dbg("     -- i8042 (wait read timeout)\n");
3308c2ecf20Sopenharmony_ci			return error;
3318c2ecf20Sopenharmony_ci		}
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci		if (command == I8042_CMD_AUX_LOOP &&
3348c2ecf20Sopenharmony_ci		    !(i8042_read_status() & I8042_STR_AUXDATA)) {
3358c2ecf20Sopenharmony_ci			dbg("     -- i8042 (auxerr)\n");
3368c2ecf20Sopenharmony_ci			return -1;
3378c2ecf20Sopenharmony_ci		}
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci		param[i] = i8042_read_data();
3408c2ecf20Sopenharmony_ci		dbg("%02x <- i8042 (return)\n", param[i]);
3418c2ecf20Sopenharmony_ci	}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	return 0;
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ciint i8042_command(unsigned char *param, int command)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	unsigned long flags;
3498c2ecf20Sopenharmony_ci	int retval;
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	if (!i8042_present)
3528c2ecf20Sopenharmony_ci		return -1;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
3558c2ecf20Sopenharmony_ci	retval = __i8042_command(param, command);
3568c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	return retval;
3598c2ecf20Sopenharmony_ci}
3608c2ecf20Sopenharmony_ciEXPORT_SYMBOL(i8042_command);
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci/*
3638c2ecf20Sopenharmony_ci * i8042_kbd_write() sends a byte out through the keyboard interface.
3648c2ecf20Sopenharmony_ci */
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_cistatic int i8042_kbd_write(struct serio *port, unsigned char c)
3678c2ecf20Sopenharmony_ci{
3688c2ecf20Sopenharmony_ci	unsigned long flags;
3698c2ecf20Sopenharmony_ci	int retval = 0;
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	if (!(retval = i8042_wait_write())) {
3748c2ecf20Sopenharmony_ci		dbg("%02x -> i8042 (kbd-data)\n", c);
3758c2ecf20Sopenharmony_ci		i8042_write_data(c);
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	return retval;
3818c2ecf20Sopenharmony_ci}
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci/*
3848c2ecf20Sopenharmony_ci * i8042_aux_write() sends a byte out through the aux interface.
3858c2ecf20Sopenharmony_ci */
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cistatic int i8042_aux_write(struct serio *serio, unsigned char c)
3888c2ecf20Sopenharmony_ci{
3898c2ecf20Sopenharmony_ci	struct i8042_port *port = serio->port_data;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	return i8042_command(&c, port->mux == -1 ?
3928c2ecf20Sopenharmony_ci					I8042_CMD_AUX_SEND :
3938c2ecf20Sopenharmony_ci					I8042_CMD_MUX_SEND + port->mux);
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci/*
3988c2ecf20Sopenharmony_ci * i8042_port_close attempts to clear AUX or KBD port state by disabling
3998c2ecf20Sopenharmony_ci * and then re-enabling it.
4008c2ecf20Sopenharmony_ci */
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_cistatic void i8042_port_close(struct serio *serio)
4038c2ecf20Sopenharmony_ci{
4048c2ecf20Sopenharmony_ci	int irq_bit;
4058c2ecf20Sopenharmony_ci	int disable_bit;
4068c2ecf20Sopenharmony_ci	const char *port_name;
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	if (serio == i8042_ports[I8042_AUX_PORT_NO].serio) {
4098c2ecf20Sopenharmony_ci		irq_bit = I8042_CTR_AUXINT;
4108c2ecf20Sopenharmony_ci		disable_bit = I8042_CTR_AUXDIS;
4118c2ecf20Sopenharmony_ci		port_name = "AUX";
4128c2ecf20Sopenharmony_ci	} else {
4138c2ecf20Sopenharmony_ci		irq_bit = I8042_CTR_KBDINT;
4148c2ecf20Sopenharmony_ci		disable_bit = I8042_CTR_KBDDIS;
4158c2ecf20Sopenharmony_ci		port_name = "KBD";
4168c2ecf20Sopenharmony_ci	}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	i8042_ctr &= ~irq_bit;
4198c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
4208c2ecf20Sopenharmony_ci		pr_warn("Can't write CTR while closing %s port\n", port_name);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	udelay(50);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	i8042_ctr &= ~disable_bit;
4258c2ecf20Sopenharmony_ci	i8042_ctr |= irq_bit;
4268c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
4278c2ecf20Sopenharmony_ci		pr_err("Can't reactivate %s port\n", port_name);
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	/*
4308c2ecf20Sopenharmony_ci	 * See if there is any data appeared while we were messing with
4318c2ecf20Sopenharmony_ci	 * port state.
4328c2ecf20Sopenharmony_ci	 */
4338c2ecf20Sopenharmony_ci	i8042_interrupt(0, NULL);
4348c2ecf20Sopenharmony_ci}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci/*
4378c2ecf20Sopenharmony_ci * i8042_start() is called by serio core when port is about to finish
4388c2ecf20Sopenharmony_ci * registering. It will mark port as existing so i8042_interrupt can
4398c2ecf20Sopenharmony_ci * start sending data through it.
4408c2ecf20Sopenharmony_ci */
4418c2ecf20Sopenharmony_cistatic int i8042_start(struct serio *serio)
4428c2ecf20Sopenharmony_ci{
4438c2ecf20Sopenharmony_ci	struct i8042_port *port = serio->port_data;
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci	device_set_wakeup_capable(&serio->dev, true);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	/*
4488c2ecf20Sopenharmony_ci	 * On platforms using suspend-to-idle, allow the keyboard to
4498c2ecf20Sopenharmony_ci	 * wake up the system from sleep by enabling keyboard wakeups
4508c2ecf20Sopenharmony_ci	 * by default.  This is consistent with keyboard wakeup
4518c2ecf20Sopenharmony_ci	 * behavior on many platforms using suspend-to-RAM (ACPI S3)
4528c2ecf20Sopenharmony_ci	 * by default.
4538c2ecf20Sopenharmony_ci	 */
4548c2ecf20Sopenharmony_ci	if (pm_suspend_default_s2idle() &&
4558c2ecf20Sopenharmony_ci	    serio == i8042_ports[I8042_KBD_PORT_NO].serio) {
4568c2ecf20Sopenharmony_ci		device_set_wakeup_enable(&serio->dev, true);
4578c2ecf20Sopenharmony_ci	}
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	spin_lock_irq(&i8042_lock);
4608c2ecf20Sopenharmony_ci	port->exists = true;
4618c2ecf20Sopenharmony_ci	spin_unlock_irq(&i8042_lock);
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	return 0;
4648c2ecf20Sopenharmony_ci}
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci/*
4678c2ecf20Sopenharmony_ci * i8042_stop() marks serio port as non-existing so i8042_interrupt
4688c2ecf20Sopenharmony_ci * will not try to send data to the port that is about to go away.
4698c2ecf20Sopenharmony_ci * The function is called by serio core as part of unregister procedure.
4708c2ecf20Sopenharmony_ci */
4718c2ecf20Sopenharmony_cistatic void i8042_stop(struct serio *serio)
4728c2ecf20Sopenharmony_ci{
4738c2ecf20Sopenharmony_ci	struct i8042_port *port = serio->port_data;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	spin_lock_irq(&i8042_lock);
4768c2ecf20Sopenharmony_ci	port->exists = false;
4778c2ecf20Sopenharmony_ci	port->serio = NULL;
4788c2ecf20Sopenharmony_ci	spin_unlock_irq(&i8042_lock);
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	/*
4818c2ecf20Sopenharmony_ci	 * We need to make sure that interrupt handler finishes using
4828c2ecf20Sopenharmony_ci	 * our serio port before we return from this function.
4838c2ecf20Sopenharmony_ci	 * We synchronize with both AUX and KBD IRQs because there is
4848c2ecf20Sopenharmony_ci	 * a (very unlikely) chance that AUX IRQ is raised for KBD port
4858c2ecf20Sopenharmony_ci	 * and vice versa.
4868c2ecf20Sopenharmony_ci	 */
4878c2ecf20Sopenharmony_ci	synchronize_irq(I8042_AUX_IRQ);
4888c2ecf20Sopenharmony_ci	synchronize_irq(I8042_KBD_IRQ);
4898c2ecf20Sopenharmony_ci}
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci/*
4928c2ecf20Sopenharmony_ci * i8042_filter() filters out unwanted bytes from the input data stream.
4938c2ecf20Sopenharmony_ci * It is called from i8042_interrupt and thus is running with interrupts
4948c2ecf20Sopenharmony_ci * off and i8042_lock held.
4958c2ecf20Sopenharmony_ci */
4968c2ecf20Sopenharmony_cistatic bool i8042_filter(unsigned char data, unsigned char str,
4978c2ecf20Sopenharmony_ci			 struct serio *serio)
4988c2ecf20Sopenharmony_ci{
4998c2ecf20Sopenharmony_ci	if (unlikely(i8042_suppress_kbd_ack)) {
5008c2ecf20Sopenharmony_ci		if ((~str & I8042_STR_AUXDATA) &&
5018c2ecf20Sopenharmony_ci		    (data == 0xfa || data == 0xfe)) {
5028c2ecf20Sopenharmony_ci			i8042_suppress_kbd_ack--;
5038c2ecf20Sopenharmony_ci			dbg("Extra keyboard ACK - filtered out\n");
5048c2ecf20Sopenharmony_ci			return true;
5058c2ecf20Sopenharmony_ci		}
5068c2ecf20Sopenharmony_ci	}
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	if (i8042_platform_filter && i8042_platform_filter(data, str, serio)) {
5098c2ecf20Sopenharmony_ci		dbg("Filtered out by platform filter\n");
5108c2ecf20Sopenharmony_ci		return true;
5118c2ecf20Sopenharmony_ci	}
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	return false;
5148c2ecf20Sopenharmony_ci}
5158c2ecf20Sopenharmony_ci
5168c2ecf20Sopenharmony_ci/*
5178c2ecf20Sopenharmony_ci * i8042_interrupt() is the most important function in this driver -
5188c2ecf20Sopenharmony_ci * it handles the interrupts from the i8042, and sends incoming bytes
5198c2ecf20Sopenharmony_ci * to the upper layers.
5208c2ecf20Sopenharmony_ci */
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_cistatic irqreturn_t i8042_interrupt(int irq, void *dev_id)
5238c2ecf20Sopenharmony_ci{
5248c2ecf20Sopenharmony_ci	struct i8042_port *port;
5258c2ecf20Sopenharmony_ci	struct serio *serio;
5268c2ecf20Sopenharmony_ci	unsigned long flags;
5278c2ecf20Sopenharmony_ci	unsigned char str, data;
5288c2ecf20Sopenharmony_ci	unsigned int dfl;
5298c2ecf20Sopenharmony_ci	unsigned int port_no;
5308c2ecf20Sopenharmony_ci	bool filtered;
5318c2ecf20Sopenharmony_ci	int ret = 1;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	str = i8042_read_status();
5368c2ecf20Sopenharmony_ci	if (unlikely(~str & I8042_STR_OBF)) {
5378c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&i8042_lock, flags);
5388c2ecf20Sopenharmony_ci		if (irq)
5398c2ecf20Sopenharmony_ci			dbg("Interrupt %d, without any data\n", irq);
5408c2ecf20Sopenharmony_ci		ret = 0;
5418c2ecf20Sopenharmony_ci		goto out;
5428c2ecf20Sopenharmony_ci	}
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	data = i8042_read_data();
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	if (i8042_mux_present && (str & I8042_STR_AUXDATA)) {
5478c2ecf20Sopenharmony_ci		static unsigned long last_transmit;
5488c2ecf20Sopenharmony_ci		static unsigned char last_str;
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci		dfl = 0;
5518c2ecf20Sopenharmony_ci		if (str & I8042_STR_MUXERR) {
5528c2ecf20Sopenharmony_ci			dbg("MUX error, status is %02x, data is %02x\n",
5538c2ecf20Sopenharmony_ci			    str, data);
5548c2ecf20Sopenharmony_ci/*
5558c2ecf20Sopenharmony_ci * When MUXERR condition is signalled the data register can only contain
5568c2ecf20Sopenharmony_ci * 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately
5578c2ecf20Sopenharmony_ci * it is not always the case. Some KBCs also report 0xfc when there is
5588c2ecf20Sopenharmony_ci * nothing connected to the port while others sometimes get confused which
5598c2ecf20Sopenharmony_ci * port the data came from and signal error leaving the data intact. They
5608c2ecf20Sopenharmony_ci * _do not_ revert to legacy mode (actually I've never seen KBC reverting
5618c2ecf20Sopenharmony_ci * to legacy mode yet, when we see one we'll add proper handling).
5628c2ecf20Sopenharmony_ci * Anyway, we process 0xfc, 0xfd, 0xfe and 0xff as timeouts, and for the
5638c2ecf20Sopenharmony_ci * rest assume that the data came from the same serio last byte
5648c2ecf20Sopenharmony_ci * was transmitted (if transmission happened not too long ago).
5658c2ecf20Sopenharmony_ci */
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ci			switch (data) {
5688c2ecf20Sopenharmony_ci				default:
5698c2ecf20Sopenharmony_ci					if (time_before(jiffies, last_transmit + HZ/10)) {
5708c2ecf20Sopenharmony_ci						str = last_str;
5718c2ecf20Sopenharmony_ci						break;
5728c2ecf20Sopenharmony_ci					}
5738c2ecf20Sopenharmony_ci					fallthrough;	/* report timeout */
5748c2ecf20Sopenharmony_ci				case 0xfc:
5758c2ecf20Sopenharmony_ci				case 0xfd:
5768c2ecf20Sopenharmony_ci				case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break;
5778c2ecf20Sopenharmony_ci				case 0xff: dfl = SERIO_PARITY;  data = 0xfe; break;
5788c2ecf20Sopenharmony_ci			}
5798c2ecf20Sopenharmony_ci		}
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci		port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3);
5828c2ecf20Sopenharmony_ci		last_str = str;
5838c2ecf20Sopenharmony_ci		last_transmit = jiffies;
5848c2ecf20Sopenharmony_ci	} else {
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ci		dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
5878c2ecf20Sopenharmony_ci		      ((str & I8042_STR_TIMEOUT && !i8042_notimeout) ? SERIO_TIMEOUT : 0);
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci		port_no = (str & I8042_STR_AUXDATA) ?
5908c2ecf20Sopenharmony_ci				I8042_AUX_PORT_NO : I8042_KBD_PORT_NO;
5918c2ecf20Sopenharmony_ci	}
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	port = &i8042_ports[port_no];
5948c2ecf20Sopenharmony_ci	serio = port->exists ? port->serio : NULL;
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci	filter_dbg(port->driver_bound, data, "<- i8042 (interrupt, %d, %d%s%s)\n",
5978c2ecf20Sopenharmony_ci		   port_no, irq,
5988c2ecf20Sopenharmony_ci		   dfl & SERIO_PARITY ? ", bad parity" : "",
5998c2ecf20Sopenharmony_ci		   dfl & SERIO_TIMEOUT ? ", timeout" : "");
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	filtered = i8042_filter(data, str, serio);
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci	if (likely(serio && !filtered))
6068c2ecf20Sopenharmony_ci		serio_interrupt(serio, data, dfl);
6078c2ecf20Sopenharmony_ci
6088c2ecf20Sopenharmony_ci out:
6098c2ecf20Sopenharmony_ci	return IRQ_RETVAL(ret);
6108c2ecf20Sopenharmony_ci}
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci/*
6138c2ecf20Sopenharmony_ci * i8042_enable_kbd_port enables keyboard port on chip
6148c2ecf20Sopenharmony_ci */
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_cistatic int i8042_enable_kbd_port(void)
6178c2ecf20Sopenharmony_ci{
6188c2ecf20Sopenharmony_ci	i8042_ctr &= ~I8042_CTR_KBDDIS;
6198c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_KBDINT;
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
6228c2ecf20Sopenharmony_ci		i8042_ctr &= ~I8042_CTR_KBDINT;
6238c2ecf20Sopenharmony_ci		i8042_ctr |= I8042_CTR_KBDDIS;
6248c2ecf20Sopenharmony_ci		pr_err("Failed to enable KBD port\n");
6258c2ecf20Sopenharmony_ci		return -EIO;
6268c2ecf20Sopenharmony_ci	}
6278c2ecf20Sopenharmony_ci
6288c2ecf20Sopenharmony_ci	return 0;
6298c2ecf20Sopenharmony_ci}
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci/*
6328c2ecf20Sopenharmony_ci * i8042_enable_aux_port enables AUX (mouse) port on chip
6338c2ecf20Sopenharmony_ci */
6348c2ecf20Sopenharmony_ci
6358c2ecf20Sopenharmony_cistatic int i8042_enable_aux_port(void)
6368c2ecf20Sopenharmony_ci{
6378c2ecf20Sopenharmony_ci	i8042_ctr &= ~I8042_CTR_AUXDIS;
6388c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_AUXINT;
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
6418c2ecf20Sopenharmony_ci		i8042_ctr &= ~I8042_CTR_AUXINT;
6428c2ecf20Sopenharmony_ci		i8042_ctr |= I8042_CTR_AUXDIS;
6438c2ecf20Sopenharmony_ci		pr_err("Failed to enable AUX port\n");
6448c2ecf20Sopenharmony_ci		return -EIO;
6458c2ecf20Sopenharmony_ci	}
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_ci	return 0;
6488c2ecf20Sopenharmony_ci}
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci/*
6518c2ecf20Sopenharmony_ci * i8042_enable_mux_ports enables 4 individual AUX ports after
6528c2ecf20Sopenharmony_ci * the controller has been switched into Multiplexed mode
6538c2ecf20Sopenharmony_ci */
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_cistatic int i8042_enable_mux_ports(void)
6568c2ecf20Sopenharmony_ci{
6578c2ecf20Sopenharmony_ci	unsigned char param;
6588c2ecf20Sopenharmony_ci	int i;
6598c2ecf20Sopenharmony_ci
6608c2ecf20Sopenharmony_ci	for (i = 0; i < I8042_NUM_MUX_PORTS; i++) {
6618c2ecf20Sopenharmony_ci		i8042_command(&param, I8042_CMD_MUX_PFX + i);
6628c2ecf20Sopenharmony_ci		i8042_command(&param, I8042_CMD_AUX_ENABLE);
6638c2ecf20Sopenharmony_ci	}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_ci	return i8042_enable_aux_port();
6668c2ecf20Sopenharmony_ci}
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci/*
6698c2ecf20Sopenharmony_ci * i8042_set_mux_mode checks whether the controller has an
6708c2ecf20Sopenharmony_ci * active multiplexor and puts the chip into Multiplexed (true)
6718c2ecf20Sopenharmony_ci * or Legacy (false) mode.
6728c2ecf20Sopenharmony_ci */
6738c2ecf20Sopenharmony_ci
6748c2ecf20Sopenharmony_cistatic int i8042_set_mux_mode(bool multiplex, unsigned char *mux_version)
6758c2ecf20Sopenharmony_ci{
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	unsigned char param, val;
6788c2ecf20Sopenharmony_ci/*
6798c2ecf20Sopenharmony_ci * Get rid of bytes in the queue.
6808c2ecf20Sopenharmony_ci */
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci	i8042_flush();
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_ci/*
6858c2ecf20Sopenharmony_ci * Internal loopback test - send three bytes, they should come back from the
6868c2ecf20Sopenharmony_ci * mouse interface, the last should be version.
6878c2ecf20Sopenharmony_ci */
6888c2ecf20Sopenharmony_ci
6898c2ecf20Sopenharmony_ci	param = val = 0xf0;
6908c2ecf20Sopenharmony_ci	if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != val)
6918c2ecf20Sopenharmony_ci		return -1;
6928c2ecf20Sopenharmony_ci	param = val = multiplex ? 0x56 : 0xf6;
6938c2ecf20Sopenharmony_ci	if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != val)
6948c2ecf20Sopenharmony_ci		return -1;
6958c2ecf20Sopenharmony_ci	param = val = multiplex ? 0xa4 : 0xa5;
6968c2ecf20Sopenharmony_ci	if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param == val)
6978c2ecf20Sopenharmony_ci		return -1;
6988c2ecf20Sopenharmony_ci
6998c2ecf20Sopenharmony_ci/*
7008c2ecf20Sopenharmony_ci * Workaround for interference with USB Legacy emulation
7018c2ecf20Sopenharmony_ci * that causes a v10.12 MUX to be found.
7028c2ecf20Sopenharmony_ci */
7038c2ecf20Sopenharmony_ci	if (param == 0xac)
7048c2ecf20Sopenharmony_ci		return -1;
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	if (mux_version)
7078c2ecf20Sopenharmony_ci		*mux_version = param;
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	return 0;
7108c2ecf20Sopenharmony_ci}
7118c2ecf20Sopenharmony_ci
7128c2ecf20Sopenharmony_ci/*
7138c2ecf20Sopenharmony_ci * i8042_check_mux() checks whether the controller supports the PS/2 Active
7148c2ecf20Sopenharmony_ci * Multiplexing specification by Synaptics, Phoenix, Insyde and
7158c2ecf20Sopenharmony_ci * LCS/Telegraphics.
7168c2ecf20Sopenharmony_ci */
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_cistatic int i8042_check_mux(void)
7198c2ecf20Sopenharmony_ci{
7208c2ecf20Sopenharmony_ci	unsigned char mux_version;
7218c2ecf20Sopenharmony_ci
7228c2ecf20Sopenharmony_ci	if (i8042_set_mux_mode(true, &mux_version))
7238c2ecf20Sopenharmony_ci		return -1;
7248c2ecf20Sopenharmony_ci
7258c2ecf20Sopenharmony_ci	pr_info("Detected active multiplexing controller, rev %d.%d\n",
7268c2ecf20Sopenharmony_ci		(mux_version >> 4) & 0xf, mux_version & 0xf);
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci/*
7298c2ecf20Sopenharmony_ci * Disable all muxed ports by disabling AUX.
7308c2ecf20Sopenharmony_ci */
7318c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_AUXDIS;
7328c2ecf20Sopenharmony_ci	i8042_ctr &= ~I8042_CTR_AUXINT;
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
7358c2ecf20Sopenharmony_ci		pr_err("Failed to disable AUX port, can't use MUX\n");
7368c2ecf20Sopenharmony_ci		return -EIO;
7378c2ecf20Sopenharmony_ci	}
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_ci	i8042_mux_present = true;
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	return 0;
7428c2ecf20Sopenharmony_ci}
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_ci/*
7458c2ecf20Sopenharmony_ci * The following is used to test AUX IRQ delivery.
7468c2ecf20Sopenharmony_ci */
7478c2ecf20Sopenharmony_cistatic struct completion i8042_aux_irq_delivered;
7488c2ecf20Sopenharmony_cistatic bool i8042_irq_being_tested;
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_cistatic irqreturn_t i8042_aux_test_irq(int irq, void *dev_id)
7518c2ecf20Sopenharmony_ci{
7528c2ecf20Sopenharmony_ci	unsigned long flags;
7538c2ecf20Sopenharmony_ci	unsigned char str, data;
7548c2ecf20Sopenharmony_ci	int ret = 0;
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
7578c2ecf20Sopenharmony_ci	str = i8042_read_status();
7588c2ecf20Sopenharmony_ci	if (str & I8042_STR_OBF) {
7598c2ecf20Sopenharmony_ci		data = i8042_read_data();
7608c2ecf20Sopenharmony_ci		dbg("%02x <- i8042 (aux_test_irq, %s)\n",
7618c2ecf20Sopenharmony_ci		    data, str & I8042_STR_AUXDATA ? "aux" : "kbd");
7628c2ecf20Sopenharmony_ci		if (i8042_irq_being_tested &&
7638c2ecf20Sopenharmony_ci		    data == 0xa5 && (str & I8042_STR_AUXDATA))
7648c2ecf20Sopenharmony_ci			complete(&i8042_aux_irq_delivered);
7658c2ecf20Sopenharmony_ci		ret = 1;
7668c2ecf20Sopenharmony_ci	}
7678c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
7688c2ecf20Sopenharmony_ci
7698c2ecf20Sopenharmony_ci	return IRQ_RETVAL(ret);
7708c2ecf20Sopenharmony_ci}
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_ci/*
7738c2ecf20Sopenharmony_ci * i8042_toggle_aux - enables or disables AUX port on i8042 via command and
7748c2ecf20Sopenharmony_ci * verifies success by readinng CTR. Used when testing for presence of AUX
7758c2ecf20Sopenharmony_ci * port.
7768c2ecf20Sopenharmony_ci */
7778c2ecf20Sopenharmony_cistatic int i8042_toggle_aux(bool on)
7788c2ecf20Sopenharmony_ci{
7798c2ecf20Sopenharmony_ci	unsigned char param;
7808c2ecf20Sopenharmony_ci	int i;
7818c2ecf20Sopenharmony_ci
7828c2ecf20Sopenharmony_ci	if (i8042_command(&param,
7838c2ecf20Sopenharmony_ci			on ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE))
7848c2ecf20Sopenharmony_ci		return -1;
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci	/* some chips need some time to set the I8042_CTR_AUXDIS bit */
7878c2ecf20Sopenharmony_ci	for (i = 0; i < 100; i++) {
7888c2ecf20Sopenharmony_ci		udelay(50);
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_ci		if (i8042_command(&param, I8042_CMD_CTL_RCTR))
7918c2ecf20Sopenharmony_ci			return -1;
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci		if (!(param & I8042_CTR_AUXDIS) == on)
7948c2ecf20Sopenharmony_ci			return 0;
7958c2ecf20Sopenharmony_ci	}
7968c2ecf20Sopenharmony_ci
7978c2ecf20Sopenharmony_ci	return -1;
7988c2ecf20Sopenharmony_ci}
7998c2ecf20Sopenharmony_ci
8008c2ecf20Sopenharmony_ci/*
8018c2ecf20Sopenharmony_ci * i8042_check_aux() applies as much paranoia as it can at detecting
8028c2ecf20Sopenharmony_ci * the presence of an AUX interface.
8038c2ecf20Sopenharmony_ci */
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_cistatic int i8042_check_aux(void)
8068c2ecf20Sopenharmony_ci{
8078c2ecf20Sopenharmony_ci	int retval = -1;
8088c2ecf20Sopenharmony_ci	bool irq_registered = false;
8098c2ecf20Sopenharmony_ci	bool aux_loop_broken = false;
8108c2ecf20Sopenharmony_ci	unsigned long flags;
8118c2ecf20Sopenharmony_ci	unsigned char param;
8128c2ecf20Sopenharmony_ci
8138c2ecf20Sopenharmony_ci/*
8148c2ecf20Sopenharmony_ci * Get rid of bytes in the queue.
8158c2ecf20Sopenharmony_ci */
8168c2ecf20Sopenharmony_ci
8178c2ecf20Sopenharmony_ci	i8042_flush();
8188c2ecf20Sopenharmony_ci
8198c2ecf20Sopenharmony_ci/*
8208c2ecf20Sopenharmony_ci * Internal loopback test - filters out AT-type i8042's. Unfortunately
8218c2ecf20Sopenharmony_ci * SiS screwed up and their 5597 doesn't support the LOOP command even
8228c2ecf20Sopenharmony_ci * though it has an AUX port.
8238c2ecf20Sopenharmony_ci */
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	param = 0x5a;
8268c2ecf20Sopenharmony_ci	retval = i8042_command(&param, I8042_CMD_AUX_LOOP);
8278c2ecf20Sopenharmony_ci	if (retval || param != 0x5a) {
8288c2ecf20Sopenharmony_ci
8298c2ecf20Sopenharmony_ci/*
8308c2ecf20Sopenharmony_ci * External connection test - filters out AT-soldered PS/2 i8042's
8318c2ecf20Sopenharmony_ci * 0x00 - no error, 0x01-0x03 - clock/data stuck, 0xff - general error
8328c2ecf20Sopenharmony_ci * 0xfa - no error on some notebooks which ignore the spec
8338c2ecf20Sopenharmony_ci * Because it's common for chipsets to return error on perfectly functioning
8348c2ecf20Sopenharmony_ci * AUX ports, we test for this only when the LOOP command failed.
8358c2ecf20Sopenharmony_ci */
8368c2ecf20Sopenharmony_ci
8378c2ecf20Sopenharmony_ci		if (i8042_command(&param, I8042_CMD_AUX_TEST) ||
8388c2ecf20Sopenharmony_ci		    (param && param != 0xfa && param != 0xff))
8398c2ecf20Sopenharmony_ci			return -1;
8408c2ecf20Sopenharmony_ci
8418c2ecf20Sopenharmony_ci/*
8428c2ecf20Sopenharmony_ci * If AUX_LOOP completed without error but returned unexpected data
8438c2ecf20Sopenharmony_ci * mark it as broken
8448c2ecf20Sopenharmony_ci */
8458c2ecf20Sopenharmony_ci		if (!retval)
8468c2ecf20Sopenharmony_ci			aux_loop_broken = true;
8478c2ecf20Sopenharmony_ci	}
8488c2ecf20Sopenharmony_ci
8498c2ecf20Sopenharmony_ci/*
8508c2ecf20Sopenharmony_ci * Bit assignment test - filters out PS/2 i8042's in AT mode
8518c2ecf20Sopenharmony_ci */
8528c2ecf20Sopenharmony_ci
8538c2ecf20Sopenharmony_ci	if (i8042_toggle_aux(false)) {
8548c2ecf20Sopenharmony_ci		pr_warn("Failed to disable AUX port, but continuing anyway... Is this a SiS?\n");
8558c2ecf20Sopenharmony_ci		pr_warn("If AUX port is really absent please use the 'i8042.noaux' option\n");
8568c2ecf20Sopenharmony_ci	}
8578c2ecf20Sopenharmony_ci
8588c2ecf20Sopenharmony_ci	if (i8042_toggle_aux(true))
8598c2ecf20Sopenharmony_ci		return -1;
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci/*
8628c2ecf20Sopenharmony_ci * Reset keyboard (needed on some laptops to successfully detect
8638c2ecf20Sopenharmony_ci * touchpad, e.g., some Gigabyte laptop models with Elantech
8648c2ecf20Sopenharmony_ci * touchpads).
8658c2ecf20Sopenharmony_ci */
8668c2ecf20Sopenharmony_ci	if (i8042_kbdreset) {
8678c2ecf20Sopenharmony_ci		pr_warn("Attempting to reset device connected to KBD port\n");
8688c2ecf20Sopenharmony_ci		i8042_kbd_write(NULL, (unsigned char) 0xff);
8698c2ecf20Sopenharmony_ci	}
8708c2ecf20Sopenharmony_ci
8718c2ecf20Sopenharmony_ci/*
8728c2ecf20Sopenharmony_ci * Test AUX IRQ delivery to make sure BIOS did not grab the IRQ and
8738c2ecf20Sopenharmony_ci * used it for a PCI card or somethig else.
8748c2ecf20Sopenharmony_ci */
8758c2ecf20Sopenharmony_ci
8768c2ecf20Sopenharmony_ci	if (i8042_noloop || i8042_bypass_aux_irq_test || aux_loop_broken) {
8778c2ecf20Sopenharmony_ci/*
8788c2ecf20Sopenharmony_ci * Without LOOP command we can't test AUX IRQ delivery. Assume the port
8798c2ecf20Sopenharmony_ci * is working and hope we are right.
8808c2ecf20Sopenharmony_ci */
8818c2ecf20Sopenharmony_ci		retval = 0;
8828c2ecf20Sopenharmony_ci		goto out;
8838c2ecf20Sopenharmony_ci	}
8848c2ecf20Sopenharmony_ci
8858c2ecf20Sopenharmony_ci	if (request_irq(I8042_AUX_IRQ, i8042_aux_test_irq, IRQF_SHARED,
8868c2ecf20Sopenharmony_ci			"i8042", i8042_platform_device))
8878c2ecf20Sopenharmony_ci		goto out;
8888c2ecf20Sopenharmony_ci
8898c2ecf20Sopenharmony_ci	irq_registered = true;
8908c2ecf20Sopenharmony_ci
8918c2ecf20Sopenharmony_ci	if (i8042_enable_aux_port())
8928c2ecf20Sopenharmony_ci		goto out;
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
8958c2ecf20Sopenharmony_ci
8968c2ecf20Sopenharmony_ci	init_completion(&i8042_aux_irq_delivered);
8978c2ecf20Sopenharmony_ci	i8042_irq_being_tested = true;
8988c2ecf20Sopenharmony_ci
8998c2ecf20Sopenharmony_ci	param = 0xa5;
9008c2ecf20Sopenharmony_ci	retval = __i8042_command(&param, I8042_CMD_AUX_LOOP & 0xf0ff);
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_ci	if (retval)
9058c2ecf20Sopenharmony_ci		goto out;
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci	if (wait_for_completion_timeout(&i8042_aux_irq_delivered,
9088c2ecf20Sopenharmony_ci					msecs_to_jiffies(250)) == 0) {
9098c2ecf20Sopenharmony_ci/*
9108c2ecf20Sopenharmony_ci * AUX IRQ was never delivered so we need to flush the controller to
9118c2ecf20Sopenharmony_ci * get rid of the byte we put there; otherwise keyboard may not work.
9128c2ecf20Sopenharmony_ci */
9138c2ecf20Sopenharmony_ci		dbg("     -- i8042 (aux irq test timeout)\n");
9148c2ecf20Sopenharmony_ci		i8042_flush();
9158c2ecf20Sopenharmony_ci		retval = -1;
9168c2ecf20Sopenharmony_ci	}
9178c2ecf20Sopenharmony_ci
9188c2ecf20Sopenharmony_ci out:
9198c2ecf20Sopenharmony_ci
9208c2ecf20Sopenharmony_ci/*
9218c2ecf20Sopenharmony_ci * Disable the interface.
9228c2ecf20Sopenharmony_ci */
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_AUXDIS;
9258c2ecf20Sopenharmony_ci	i8042_ctr &= ~I8042_CTR_AUXINT;
9268c2ecf20Sopenharmony_ci
9278c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
9288c2ecf20Sopenharmony_ci		retval = -1;
9298c2ecf20Sopenharmony_ci
9308c2ecf20Sopenharmony_ci	if (irq_registered)
9318c2ecf20Sopenharmony_ci		free_irq(I8042_AUX_IRQ, i8042_platform_device);
9328c2ecf20Sopenharmony_ci
9338c2ecf20Sopenharmony_ci	return retval;
9348c2ecf20Sopenharmony_ci}
9358c2ecf20Sopenharmony_ci
9368c2ecf20Sopenharmony_cistatic int i8042_controller_check(void)
9378c2ecf20Sopenharmony_ci{
9388c2ecf20Sopenharmony_ci	if (i8042_flush()) {
9398c2ecf20Sopenharmony_ci		pr_info("No controller found\n");
9408c2ecf20Sopenharmony_ci		return -ENODEV;
9418c2ecf20Sopenharmony_ci	}
9428c2ecf20Sopenharmony_ci
9438c2ecf20Sopenharmony_ci	return 0;
9448c2ecf20Sopenharmony_ci}
9458c2ecf20Sopenharmony_ci
9468c2ecf20Sopenharmony_cistatic int i8042_controller_selftest(void)
9478c2ecf20Sopenharmony_ci{
9488c2ecf20Sopenharmony_ci	unsigned char param;
9498c2ecf20Sopenharmony_ci	int i = 0;
9508c2ecf20Sopenharmony_ci
9518c2ecf20Sopenharmony_ci	/*
9528c2ecf20Sopenharmony_ci	 * We try this 5 times; on some really fragile systems this does not
9538c2ecf20Sopenharmony_ci	 * take the first time...
9548c2ecf20Sopenharmony_ci	 */
9558c2ecf20Sopenharmony_ci	do {
9568c2ecf20Sopenharmony_ci
9578c2ecf20Sopenharmony_ci		if (i8042_command(&param, I8042_CMD_CTL_TEST)) {
9588c2ecf20Sopenharmony_ci			pr_err("i8042 controller selftest timeout\n");
9598c2ecf20Sopenharmony_ci			return -ENODEV;
9608c2ecf20Sopenharmony_ci		}
9618c2ecf20Sopenharmony_ci
9628c2ecf20Sopenharmony_ci		if (param == I8042_RET_CTL_TEST)
9638c2ecf20Sopenharmony_ci			return 0;
9648c2ecf20Sopenharmony_ci
9658c2ecf20Sopenharmony_ci		dbg("i8042 controller selftest: %#x != %#x\n",
9668c2ecf20Sopenharmony_ci		    param, I8042_RET_CTL_TEST);
9678c2ecf20Sopenharmony_ci		msleep(50);
9688c2ecf20Sopenharmony_ci	} while (i++ < 5);
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_ci#ifdef CONFIG_X86
9718c2ecf20Sopenharmony_ci	/*
9728c2ecf20Sopenharmony_ci	 * On x86, we don't fail entire i8042 initialization if controller
9738c2ecf20Sopenharmony_ci	 * reset fails in hopes that keyboard port will still be functional
9748c2ecf20Sopenharmony_ci	 * and user will still get a working keyboard. This is especially
9758c2ecf20Sopenharmony_ci	 * important on netbooks. On other arches we trust hardware more.
9768c2ecf20Sopenharmony_ci	 */
9778c2ecf20Sopenharmony_ci	pr_info("giving up on controller selftest, continuing anyway...\n");
9788c2ecf20Sopenharmony_ci	return 0;
9798c2ecf20Sopenharmony_ci#else
9808c2ecf20Sopenharmony_ci	pr_err("i8042 controller selftest failed\n");
9818c2ecf20Sopenharmony_ci	return -EIO;
9828c2ecf20Sopenharmony_ci#endif
9838c2ecf20Sopenharmony_ci}
9848c2ecf20Sopenharmony_ci
9858c2ecf20Sopenharmony_ci/*
9868c2ecf20Sopenharmony_ci * i8042_controller init initializes the i8042 controller, and,
9878c2ecf20Sopenharmony_ci * most importantly, sets it into non-xlated mode if that's
9888c2ecf20Sopenharmony_ci * desired.
9898c2ecf20Sopenharmony_ci */
9908c2ecf20Sopenharmony_ci
9918c2ecf20Sopenharmony_cistatic int i8042_controller_init(void)
9928c2ecf20Sopenharmony_ci{
9938c2ecf20Sopenharmony_ci	unsigned long flags;
9948c2ecf20Sopenharmony_ci	int n = 0;
9958c2ecf20Sopenharmony_ci	unsigned char ctr[2];
9968c2ecf20Sopenharmony_ci
9978c2ecf20Sopenharmony_ci/*
9988c2ecf20Sopenharmony_ci * Save the CTR for restore on unload / reboot.
9998c2ecf20Sopenharmony_ci */
10008c2ecf20Sopenharmony_ci
10018c2ecf20Sopenharmony_ci	do {
10028c2ecf20Sopenharmony_ci		if (n >= 10) {
10038c2ecf20Sopenharmony_ci			pr_err("Unable to get stable CTR read\n");
10048c2ecf20Sopenharmony_ci			return -EIO;
10058c2ecf20Sopenharmony_ci		}
10068c2ecf20Sopenharmony_ci
10078c2ecf20Sopenharmony_ci		if (n != 0)
10088c2ecf20Sopenharmony_ci			udelay(50);
10098c2ecf20Sopenharmony_ci
10108c2ecf20Sopenharmony_ci		if (i8042_command(&ctr[n++ % 2], I8042_CMD_CTL_RCTR)) {
10118c2ecf20Sopenharmony_ci			pr_err("Can't read CTR while initializing i8042\n");
10128c2ecf20Sopenharmony_ci			return i8042_probe_defer ? -EPROBE_DEFER : -EIO;
10138c2ecf20Sopenharmony_ci		}
10148c2ecf20Sopenharmony_ci
10158c2ecf20Sopenharmony_ci	} while (n < 2 || ctr[0] != ctr[1]);
10168c2ecf20Sopenharmony_ci
10178c2ecf20Sopenharmony_ci	i8042_initial_ctr = i8042_ctr = ctr[0];
10188c2ecf20Sopenharmony_ci
10198c2ecf20Sopenharmony_ci/*
10208c2ecf20Sopenharmony_ci * Disable the keyboard interface and interrupt.
10218c2ecf20Sopenharmony_ci */
10228c2ecf20Sopenharmony_ci
10238c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_KBDDIS;
10248c2ecf20Sopenharmony_ci	i8042_ctr &= ~I8042_CTR_KBDINT;
10258c2ecf20Sopenharmony_ci
10268c2ecf20Sopenharmony_ci/*
10278c2ecf20Sopenharmony_ci * Handle keylock.
10288c2ecf20Sopenharmony_ci */
10298c2ecf20Sopenharmony_ci
10308c2ecf20Sopenharmony_ci	spin_lock_irqsave(&i8042_lock, flags);
10318c2ecf20Sopenharmony_ci	if (~i8042_read_status() & I8042_STR_KEYLOCK) {
10328c2ecf20Sopenharmony_ci		if (i8042_unlock)
10338c2ecf20Sopenharmony_ci			i8042_ctr |= I8042_CTR_IGNKEYLOCK;
10348c2ecf20Sopenharmony_ci		else
10358c2ecf20Sopenharmony_ci			pr_warn("Warning: Keylock active\n");
10368c2ecf20Sopenharmony_ci	}
10378c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&i8042_lock, flags);
10388c2ecf20Sopenharmony_ci
10398c2ecf20Sopenharmony_ci/*
10408c2ecf20Sopenharmony_ci * If the chip is configured into nontranslated mode by the BIOS, don't
10418c2ecf20Sopenharmony_ci * bother enabling translating and be happy.
10428c2ecf20Sopenharmony_ci */
10438c2ecf20Sopenharmony_ci
10448c2ecf20Sopenharmony_ci	if (~i8042_ctr & I8042_CTR_XLATE)
10458c2ecf20Sopenharmony_ci		i8042_direct = true;
10468c2ecf20Sopenharmony_ci
10478c2ecf20Sopenharmony_ci/*
10488c2ecf20Sopenharmony_ci * Set nontranslated mode for the kbd interface if requested by an option.
10498c2ecf20Sopenharmony_ci * After this the kbd interface becomes a simple serial in/out, like the aux
10508c2ecf20Sopenharmony_ci * interface is. We don't do this by default, since it can confuse notebook
10518c2ecf20Sopenharmony_ci * BIOSes.
10528c2ecf20Sopenharmony_ci */
10538c2ecf20Sopenharmony_ci
10548c2ecf20Sopenharmony_ci	if (i8042_direct)
10558c2ecf20Sopenharmony_ci		i8042_ctr &= ~I8042_CTR_XLATE;
10568c2ecf20Sopenharmony_ci
10578c2ecf20Sopenharmony_ci/*
10588c2ecf20Sopenharmony_ci * Write CTR back.
10598c2ecf20Sopenharmony_ci */
10608c2ecf20Sopenharmony_ci
10618c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
10628c2ecf20Sopenharmony_ci		pr_err("Can't write CTR while initializing i8042\n");
10638c2ecf20Sopenharmony_ci		return -EIO;
10648c2ecf20Sopenharmony_ci	}
10658c2ecf20Sopenharmony_ci
10668c2ecf20Sopenharmony_ci/*
10678c2ecf20Sopenharmony_ci * Flush whatever accumulated while we were disabling keyboard port.
10688c2ecf20Sopenharmony_ci */
10698c2ecf20Sopenharmony_ci
10708c2ecf20Sopenharmony_ci	i8042_flush();
10718c2ecf20Sopenharmony_ci
10728c2ecf20Sopenharmony_ci	return 0;
10738c2ecf20Sopenharmony_ci}
10748c2ecf20Sopenharmony_ci
10758c2ecf20Sopenharmony_ci
10768c2ecf20Sopenharmony_ci/*
10778c2ecf20Sopenharmony_ci * Reset the controller and reset CRT to the original value set by BIOS.
10788c2ecf20Sopenharmony_ci */
10798c2ecf20Sopenharmony_ci
10808c2ecf20Sopenharmony_cistatic void i8042_controller_reset(bool s2r_wants_reset)
10818c2ecf20Sopenharmony_ci{
10828c2ecf20Sopenharmony_ci	i8042_flush();
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_ci/*
10858c2ecf20Sopenharmony_ci * Disable both KBD and AUX interfaces so they don't get in the way
10868c2ecf20Sopenharmony_ci */
10878c2ecf20Sopenharmony_ci
10888c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_KBDDIS | I8042_CTR_AUXDIS;
10898c2ecf20Sopenharmony_ci	i8042_ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT);
10908c2ecf20Sopenharmony_ci
10918c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
10928c2ecf20Sopenharmony_ci		pr_warn("Can't write CTR while resetting\n");
10938c2ecf20Sopenharmony_ci
10948c2ecf20Sopenharmony_ci/*
10958c2ecf20Sopenharmony_ci * Disable MUX mode if present.
10968c2ecf20Sopenharmony_ci */
10978c2ecf20Sopenharmony_ci
10988c2ecf20Sopenharmony_ci	if (i8042_mux_present)
10998c2ecf20Sopenharmony_ci		i8042_set_mux_mode(false, NULL);
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_ci/*
11028c2ecf20Sopenharmony_ci * Reset the controller if requested.
11038c2ecf20Sopenharmony_ci */
11048c2ecf20Sopenharmony_ci
11058c2ecf20Sopenharmony_ci	if (i8042_reset == I8042_RESET_ALWAYS ||
11068c2ecf20Sopenharmony_ci	    (i8042_reset == I8042_RESET_ON_S2RAM && s2r_wants_reset)) {
11078c2ecf20Sopenharmony_ci		i8042_controller_selftest();
11088c2ecf20Sopenharmony_ci	}
11098c2ecf20Sopenharmony_ci
11108c2ecf20Sopenharmony_ci/*
11118c2ecf20Sopenharmony_ci * Restore the original control register setting.
11128c2ecf20Sopenharmony_ci */
11138c2ecf20Sopenharmony_ci
11148c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_initial_ctr, I8042_CMD_CTL_WCTR))
11158c2ecf20Sopenharmony_ci		pr_warn("Can't restore CTR\n");
11168c2ecf20Sopenharmony_ci}
11178c2ecf20Sopenharmony_ci
11188c2ecf20Sopenharmony_ci
11198c2ecf20Sopenharmony_ci/*
11208c2ecf20Sopenharmony_ci * i8042_panic_blink() will turn the keyboard LEDs on or off and is called
11218c2ecf20Sopenharmony_ci * when kernel panics. Flashing LEDs is useful for users running X who may
11228c2ecf20Sopenharmony_ci * not see the console and will help distinguishing panics from "real"
11238c2ecf20Sopenharmony_ci * lockups.
11248c2ecf20Sopenharmony_ci *
11258c2ecf20Sopenharmony_ci * Note that DELAY has a limit of 10ms so we will not get stuck here
11268c2ecf20Sopenharmony_ci * waiting for KBC to free up even if KBD interrupt is off
11278c2ecf20Sopenharmony_ci */
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_ci#define DELAY do { mdelay(1); if (++delay > 10) return delay; } while(0)
11308c2ecf20Sopenharmony_ci
11318c2ecf20Sopenharmony_cistatic long i8042_panic_blink(int state)
11328c2ecf20Sopenharmony_ci{
11338c2ecf20Sopenharmony_ci	long delay = 0;
11348c2ecf20Sopenharmony_ci	char led;
11358c2ecf20Sopenharmony_ci
11368c2ecf20Sopenharmony_ci	led = (state) ? 0x01 | 0x04 : 0;
11378c2ecf20Sopenharmony_ci	while (i8042_read_status() & I8042_STR_IBF)
11388c2ecf20Sopenharmony_ci		DELAY;
11398c2ecf20Sopenharmony_ci	dbg("%02x -> i8042 (panic blink)\n", 0xed);
11408c2ecf20Sopenharmony_ci	i8042_suppress_kbd_ack = 2;
11418c2ecf20Sopenharmony_ci	i8042_write_data(0xed); /* set leds */
11428c2ecf20Sopenharmony_ci	DELAY;
11438c2ecf20Sopenharmony_ci	while (i8042_read_status() & I8042_STR_IBF)
11448c2ecf20Sopenharmony_ci		DELAY;
11458c2ecf20Sopenharmony_ci	DELAY;
11468c2ecf20Sopenharmony_ci	dbg("%02x -> i8042 (panic blink)\n", led);
11478c2ecf20Sopenharmony_ci	i8042_write_data(led);
11488c2ecf20Sopenharmony_ci	DELAY;
11498c2ecf20Sopenharmony_ci	return delay;
11508c2ecf20Sopenharmony_ci}
11518c2ecf20Sopenharmony_ci
11528c2ecf20Sopenharmony_ci#undef DELAY
11538c2ecf20Sopenharmony_ci
11548c2ecf20Sopenharmony_ci#ifdef CONFIG_X86
11558c2ecf20Sopenharmony_cistatic void i8042_dritek_enable(void)
11568c2ecf20Sopenharmony_ci{
11578c2ecf20Sopenharmony_ci	unsigned char param = 0x90;
11588c2ecf20Sopenharmony_ci	int error;
11598c2ecf20Sopenharmony_ci
11608c2ecf20Sopenharmony_ci	error = i8042_command(&param, 0x1059);
11618c2ecf20Sopenharmony_ci	if (error)
11628c2ecf20Sopenharmony_ci		pr_warn("Failed to enable DRITEK extension: %d\n", error);
11638c2ecf20Sopenharmony_ci}
11648c2ecf20Sopenharmony_ci#endif
11658c2ecf20Sopenharmony_ci
11668c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
11678c2ecf20Sopenharmony_ci
11688c2ecf20Sopenharmony_ci/*
11698c2ecf20Sopenharmony_ci * Here we try to reset everything back to a state we had
11708c2ecf20Sopenharmony_ci * before suspending.
11718c2ecf20Sopenharmony_ci */
11728c2ecf20Sopenharmony_ci
11738c2ecf20Sopenharmony_cistatic int i8042_controller_resume(bool s2r_wants_reset)
11748c2ecf20Sopenharmony_ci{
11758c2ecf20Sopenharmony_ci	int error;
11768c2ecf20Sopenharmony_ci
11778c2ecf20Sopenharmony_ci	error = i8042_controller_check();
11788c2ecf20Sopenharmony_ci	if (error)
11798c2ecf20Sopenharmony_ci		return error;
11808c2ecf20Sopenharmony_ci
11818c2ecf20Sopenharmony_ci	if (i8042_reset == I8042_RESET_ALWAYS ||
11828c2ecf20Sopenharmony_ci	    (i8042_reset == I8042_RESET_ON_S2RAM && s2r_wants_reset)) {
11838c2ecf20Sopenharmony_ci		error = i8042_controller_selftest();
11848c2ecf20Sopenharmony_ci		if (error)
11858c2ecf20Sopenharmony_ci			return error;
11868c2ecf20Sopenharmony_ci	}
11878c2ecf20Sopenharmony_ci
11888c2ecf20Sopenharmony_ci/*
11898c2ecf20Sopenharmony_ci * Restore original CTR value and disable all ports
11908c2ecf20Sopenharmony_ci */
11918c2ecf20Sopenharmony_ci
11928c2ecf20Sopenharmony_ci	i8042_ctr = i8042_initial_ctr;
11938c2ecf20Sopenharmony_ci	if (i8042_direct)
11948c2ecf20Sopenharmony_ci		i8042_ctr &= ~I8042_CTR_XLATE;
11958c2ecf20Sopenharmony_ci	i8042_ctr |= I8042_CTR_AUXDIS | I8042_CTR_KBDDIS;
11968c2ecf20Sopenharmony_ci	i8042_ctr &= ~(I8042_CTR_AUXINT | I8042_CTR_KBDINT);
11978c2ecf20Sopenharmony_ci	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
11988c2ecf20Sopenharmony_ci		pr_warn("Can't write CTR to resume, retrying...\n");
11998c2ecf20Sopenharmony_ci		msleep(50);
12008c2ecf20Sopenharmony_ci		if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
12018c2ecf20Sopenharmony_ci			pr_err("CTR write retry failed\n");
12028c2ecf20Sopenharmony_ci			return -EIO;
12038c2ecf20Sopenharmony_ci		}
12048c2ecf20Sopenharmony_ci	}
12058c2ecf20Sopenharmony_ci
12068c2ecf20Sopenharmony_ci
12078c2ecf20Sopenharmony_ci#ifdef CONFIG_X86
12088c2ecf20Sopenharmony_ci	if (i8042_dritek)
12098c2ecf20Sopenharmony_ci		i8042_dritek_enable();
12108c2ecf20Sopenharmony_ci#endif
12118c2ecf20Sopenharmony_ci
12128c2ecf20Sopenharmony_ci	if (i8042_mux_present) {
12138c2ecf20Sopenharmony_ci		if (i8042_set_mux_mode(true, NULL) || i8042_enable_mux_ports())
12148c2ecf20Sopenharmony_ci			pr_warn("failed to resume active multiplexor, mouse won't work\n");
12158c2ecf20Sopenharmony_ci	} else if (i8042_ports[I8042_AUX_PORT_NO].serio)
12168c2ecf20Sopenharmony_ci		i8042_enable_aux_port();
12178c2ecf20Sopenharmony_ci
12188c2ecf20Sopenharmony_ci	if (i8042_ports[I8042_KBD_PORT_NO].serio)
12198c2ecf20Sopenharmony_ci		i8042_enable_kbd_port();
12208c2ecf20Sopenharmony_ci
12218c2ecf20Sopenharmony_ci	i8042_interrupt(0, NULL);
12228c2ecf20Sopenharmony_ci
12238c2ecf20Sopenharmony_ci	return 0;
12248c2ecf20Sopenharmony_ci}
12258c2ecf20Sopenharmony_ci
12268c2ecf20Sopenharmony_ci/*
12278c2ecf20Sopenharmony_ci * Here we try to restore the original BIOS settings to avoid
12288c2ecf20Sopenharmony_ci * upsetting it.
12298c2ecf20Sopenharmony_ci */
12308c2ecf20Sopenharmony_ci
12318c2ecf20Sopenharmony_cistatic int i8042_pm_suspend(struct device *dev)
12328c2ecf20Sopenharmony_ci{
12338c2ecf20Sopenharmony_ci	int i;
12348c2ecf20Sopenharmony_ci
12358c2ecf20Sopenharmony_ci	if (pm_suspend_via_firmware())
12368c2ecf20Sopenharmony_ci		i8042_controller_reset(true);
12378c2ecf20Sopenharmony_ci
12388c2ecf20Sopenharmony_ci	/* Set up serio interrupts for system wakeup. */
12398c2ecf20Sopenharmony_ci	for (i = 0; i < I8042_NUM_PORTS; i++) {
12408c2ecf20Sopenharmony_ci		struct serio *serio = i8042_ports[i].serio;
12418c2ecf20Sopenharmony_ci
12428c2ecf20Sopenharmony_ci		if (serio && device_may_wakeup(&serio->dev))
12438c2ecf20Sopenharmony_ci			enable_irq_wake(i8042_ports[i].irq);
12448c2ecf20Sopenharmony_ci	}
12458c2ecf20Sopenharmony_ci
12468c2ecf20Sopenharmony_ci	return 0;
12478c2ecf20Sopenharmony_ci}
12488c2ecf20Sopenharmony_ci
12498c2ecf20Sopenharmony_cistatic int i8042_pm_resume_noirq(struct device *dev)
12508c2ecf20Sopenharmony_ci{
12518c2ecf20Sopenharmony_ci	if (!pm_resume_via_firmware())
12528c2ecf20Sopenharmony_ci		i8042_interrupt(0, NULL);
12538c2ecf20Sopenharmony_ci
12548c2ecf20Sopenharmony_ci	return 0;
12558c2ecf20Sopenharmony_ci}
12568c2ecf20Sopenharmony_ci
12578c2ecf20Sopenharmony_cistatic int i8042_pm_resume(struct device *dev)
12588c2ecf20Sopenharmony_ci{
12598c2ecf20Sopenharmony_ci	bool want_reset;
12608c2ecf20Sopenharmony_ci	int i;
12618c2ecf20Sopenharmony_ci
12628c2ecf20Sopenharmony_ci	for (i = 0; i < I8042_NUM_PORTS; i++) {
12638c2ecf20Sopenharmony_ci		struct serio *serio = i8042_ports[i].serio;
12648c2ecf20Sopenharmony_ci
12658c2ecf20Sopenharmony_ci		if (serio && device_may_wakeup(&serio->dev))
12668c2ecf20Sopenharmony_ci			disable_irq_wake(i8042_ports[i].irq);
12678c2ecf20Sopenharmony_ci	}
12688c2ecf20Sopenharmony_ci
12698c2ecf20Sopenharmony_ci	/*
12708c2ecf20Sopenharmony_ci	 * If platform firmware was not going to be involved in suspend, we did
12718c2ecf20Sopenharmony_ci	 * not restore the controller state to whatever it had been at boot
12728c2ecf20Sopenharmony_ci	 * time, so we do not need to do anything.
12738c2ecf20Sopenharmony_ci	 */
12748c2ecf20Sopenharmony_ci	if (!pm_suspend_via_firmware())
12758c2ecf20Sopenharmony_ci		return 0;
12768c2ecf20Sopenharmony_ci
12778c2ecf20Sopenharmony_ci	/*
12788c2ecf20Sopenharmony_ci	 * We only need to reset the controller if we are resuming after handing
12798c2ecf20Sopenharmony_ci	 * off control to the platform firmware, otherwise we can simply restore
12808c2ecf20Sopenharmony_ci	 * the mode.
12818c2ecf20Sopenharmony_ci	 */
12828c2ecf20Sopenharmony_ci	want_reset = pm_resume_via_firmware();
12838c2ecf20Sopenharmony_ci
12848c2ecf20Sopenharmony_ci	return i8042_controller_resume(want_reset);
12858c2ecf20Sopenharmony_ci}
12868c2ecf20Sopenharmony_ci
12878c2ecf20Sopenharmony_cistatic int i8042_pm_thaw(struct device *dev)
12888c2ecf20Sopenharmony_ci{
12898c2ecf20Sopenharmony_ci	i8042_interrupt(0, NULL);
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci	return 0;
12928c2ecf20Sopenharmony_ci}
12938c2ecf20Sopenharmony_ci
12948c2ecf20Sopenharmony_cistatic int i8042_pm_reset(struct device *dev)
12958c2ecf20Sopenharmony_ci{
12968c2ecf20Sopenharmony_ci	i8042_controller_reset(false);
12978c2ecf20Sopenharmony_ci
12988c2ecf20Sopenharmony_ci	return 0;
12998c2ecf20Sopenharmony_ci}
13008c2ecf20Sopenharmony_ci
13018c2ecf20Sopenharmony_cistatic int i8042_pm_restore(struct device *dev)
13028c2ecf20Sopenharmony_ci{
13038c2ecf20Sopenharmony_ci	return i8042_controller_resume(false);
13048c2ecf20Sopenharmony_ci}
13058c2ecf20Sopenharmony_ci
13068c2ecf20Sopenharmony_cistatic const struct dev_pm_ops i8042_pm_ops = {
13078c2ecf20Sopenharmony_ci	.suspend	= i8042_pm_suspend,
13088c2ecf20Sopenharmony_ci	.resume_noirq	= i8042_pm_resume_noirq,
13098c2ecf20Sopenharmony_ci	.resume		= i8042_pm_resume,
13108c2ecf20Sopenharmony_ci	.thaw		= i8042_pm_thaw,
13118c2ecf20Sopenharmony_ci	.poweroff	= i8042_pm_reset,
13128c2ecf20Sopenharmony_ci	.restore	= i8042_pm_restore,
13138c2ecf20Sopenharmony_ci};
13148c2ecf20Sopenharmony_ci
13158c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */
13168c2ecf20Sopenharmony_ci
13178c2ecf20Sopenharmony_ci/*
13188c2ecf20Sopenharmony_ci * We need to reset the 8042 back to original mode on system shutdown,
13198c2ecf20Sopenharmony_ci * because otherwise BIOSes will be confused.
13208c2ecf20Sopenharmony_ci */
13218c2ecf20Sopenharmony_ci
13228c2ecf20Sopenharmony_cistatic void i8042_shutdown(struct platform_device *dev)
13238c2ecf20Sopenharmony_ci{
13248c2ecf20Sopenharmony_ci	i8042_controller_reset(false);
13258c2ecf20Sopenharmony_ci}
13268c2ecf20Sopenharmony_ci
13278c2ecf20Sopenharmony_cistatic int i8042_create_kbd_port(void)
13288c2ecf20Sopenharmony_ci{
13298c2ecf20Sopenharmony_ci	struct serio *serio;
13308c2ecf20Sopenharmony_ci	struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO];
13318c2ecf20Sopenharmony_ci
13328c2ecf20Sopenharmony_ci	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
13338c2ecf20Sopenharmony_ci	if (!serio)
13348c2ecf20Sopenharmony_ci		return -ENOMEM;
13358c2ecf20Sopenharmony_ci
13368c2ecf20Sopenharmony_ci	serio->id.type		= i8042_direct ? SERIO_8042 : SERIO_8042_XL;
13378c2ecf20Sopenharmony_ci	serio->write		= i8042_dumbkbd ? NULL : i8042_kbd_write;
13388c2ecf20Sopenharmony_ci	serio->start		= i8042_start;
13398c2ecf20Sopenharmony_ci	serio->stop		= i8042_stop;
13408c2ecf20Sopenharmony_ci	serio->close		= i8042_port_close;
13418c2ecf20Sopenharmony_ci	serio->ps2_cmd_mutex	= &i8042_mutex;
13428c2ecf20Sopenharmony_ci	serio->port_data	= port;
13438c2ecf20Sopenharmony_ci	serio->dev.parent	= &i8042_platform_device->dev;
13448c2ecf20Sopenharmony_ci	strlcpy(serio->name, "i8042 KBD port", sizeof(serio->name));
13458c2ecf20Sopenharmony_ci	strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys));
13468c2ecf20Sopenharmony_ci	strlcpy(serio->firmware_id, i8042_kbd_firmware_id,
13478c2ecf20Sopenharmony_ci		sizeof(serio->firmware_id));
13488c2ecf20Sopenharmony_ci	set_primary_fwnode(&serio->dev, i8042_kbd_fwnode);
13498c2ecf20Sopenharmony_ci
13508c2ecf20Sopenharmony_ci	port->serio = serio;
13518c2ecf20Sopenharmony_ci	port->irq = I8042_KBD_IRQ;
13528c2ecf20Sopenharmony_ci
13538c2ecf20Sopenharmony_ci	return 0;
13548c2ecf20Sopenharmony_ci}
13558c2ecf20Sopenharmony_ci
13568c2ecf20Sopenharmony_cistatic int i8042_create_aux_port(int idx)
13578c2ecf20Sopenharmony_ci{
13588c2ecf20Sopenharmony_ci	struct serio *serio;
13598c2ecf20Sopenharmony_ci	int port_no = idx < 0 ? I8042_AUX_PORT_NO : I8042_MUX_PORT_NO + idx;
13608c2ecf20Sopenharmony_ci	struct i8042_port *port = &i8042_ports[port_no];
13618c2ecf20Sopenharmony_ci
13628c2ecf20Sopenharmony_ci	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
13638c2ecf20Sopenharmony_ci	if (!serio)
13648c2ecf20Sopenharmony_ci		return -ENOMEM;
13658c2ecf20Sopenharmony_ci
13668c2ecf20Sopenharmony_ci	serio->id.type		= SERIO_8042;
13678c2ecf20Sopenharmony_ci	serio->write		= i8042_aux_write;
13688c2ecf20Sopenharmony_ci	serio->start		= i8042_start;
13698c2ecf20Sopenharmony_ci	serio->stop		= i8042_stop;
13708c2ecf20Sopenharmony_ci	serio->ps2_cmd_mutex	= &i8042_mutex;
13718c2ecf20Sopenharmony_ci	serio->port_data	= port;
13728c2ecf20Sopenharmony_ci	serio->dev.parent	= &i8042_platform_device->dev;
13738c2ecf20Sopenharmony_ci	if (idx < 0) {
13748c2ecf20Sopenharmony_ci		strlcpy(serio->name, "i8042 AUX port", sizeof(serio->name));
13758c2ecf20Sopenharmony_ci		strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys));
13768c2ecf20Sopenharmony_ci		strlcpy(serio->firmware_id, i8042_aux_firmware_id,
13778c2ecf20Sopenharmony_ci			sizeof(serio->firmware_id));
13788c2ecf20Sopenharmony_ci		serio->close = i8042_port_close;
13798c2ecf20Sopenharmony_ci	} else {
13808c2ecf20Sopenharmony_ci		snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx);
13818c2ecf20Sopenharmony_ci		snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, idx + 1);
13828c2ecf20Sopenharmony_ci		strlcpy(serio->firmware_id, i8042_aux_firmware_id,
13838c2ecf20Sopenharmony_ci			sizeof(serio->firmware_id));
13848c2ecf20Sopenharmony_ci	}
13858c2ecf20Sopenharmony_ci
13868c2ecf20Sopenharmony_ci	port->serio = serio;
13878c2ecf20Sopenharmony_ci	port->mux = idx;
13888c2ecf20Sopenharmony_ci	port->irq = I8042_AUX_IRQ;
13898c2ecf20Sopenharmony_ci
13908c2ecf20Sopenharmony_ci	return 0;
13918c2ecf20Sopenharmony_ci}
13928c2ecf20Sopenharmony_ci
13938c2ecf20Sopenharmony_cistatic void i8042_free_kbd_port(void)
13948c2ecf20Sopenharmony_ci{
13958c2ecf20Sopenharmony_ci	kfree(i8042_ports[I8042_KBD_PORT_NO].serio);
13968c2ecf20Sopenharmony_ci	i8042_ports[I8042_KBD_PORT_NO].serio = NULL;
13978c2ecf20Sopenharmony_ci}
13988c2ecf20Sopenharmony_ci
13998c2ecf20Sopenharmony_cistatic void i8042_free_aux_ports(void)
14008c2ecf20Sopenharmony_ci{
14018c2ecf20Sopenharmony_ci	int i;
14028c2ecf20Sopenharmony_ci
14038c2ecf20Sopenharmony_ci	for (i = I8042_AUX_PORT_NO; i < I8042_NUM_PORTS; i++) {
14048c2ecf20Sopenharmony_ci		kfree(i8042_ports[i].serio);
14058c2ecf20Sopenharmony_ci		i8042_ports[i].serio = NULL;
14068c2ecf20Sopenharmony_ci	}
14078c2ecf20Sopenharmony_ci}
14088c2ecf20Sopenharmony_ci
14098c2ecf20Sopenharmony_cistatic void i8042_register_ports(void)
14108c2ecf20Sopenharmony_ci{
14118c2ecf20Sopenharmony_ci	int i;
14128c2ecf20Sopenharmony_ci
14138c2ecf20Sopenharmony_ci	for (i = 0; i < I8042_NUM_PORTS; i++) {
14148c2ecf20Sopenharmony_ci		struct serio *serio = i8042_ports[i].serio;
14158c2ecf20Sopenharmony_ci
14168c2ecf20Sopenharmony_ci		if (!serio)
14178c2ecf20Sopenharmony_ci			continue;
14188c2ecf20Sopenharmony_ci
14198c2ecf20Sopenharmony_ci		printk(KERN_INFO "serio: %s at %#lx,%#lx irq %d\n",
14208c2ecf20Sopenharmony_ci			serio->name,
14218c2ecf20Sopenharmony_ci			(unsigned long) I8042_DATA_REG,
14228c2ecf20Sopenharmony_ci			(unsigned long) I8042_COMMAND_REG,
14238c2ecf20Sopenharmony_ci			i8042_ports[i].irq);
14248c2ecf20Sopenharmony_ci		serio_register_port(serio);
14258c2ecf20Sopenharmony_ci	}
14268c2ecf20Sopenharmony_ci}
14278c2ecf20Sopenharmony_ci
14288c2ecf20Sopenharmony_cistatic void i8042_unregister_ports(void)
14298c2ecf20Sopenharmony_ci{
14308c2ecf20Sopenharmony_ci	int i;
14318c2ecf20Sopenharmony_ci
14328c2ecf20Sopenharmony_ci	for (i = 0; i < I8042_NUM_PORTS; i++) {
14338c2ecf20Sopenharmony_ci		if (i8042_ports[i].serio) {
14348c2ecf20Sopenharmony_ci			serio_unregister_port(i8042_ports[i].serio);
14358c2ecf20Sopenharmony_ci			i8042_ports[i].serio = NULL;
14368c2ecf20Sopenharmony_ci		}
14378c2ecf20Sopenharmony_ci	}
14388c2ecf20Sopenharmony_ci}
14398c2ecf20Sopenharmony_ci
14408c2ecf20Sopenharmony_cistatic void i8042_free_irqs(void)
14418c2ecf20Sopenharmony_ci{
14428c2ecf20Sopenharmony_ci	if (i8042_aux_irq_registered)
14438c2ecf20Sopenharmony_ci		free_irq(I8042_AUX_IRQ, i8042_platform_device);
14448c2ecf20Sopenharmony_ci	if (i8042_kbd_irq_registered)
14458c2ecf20Sopenharmony_ci		free_irq(I8042_KBD_IRQ, i8042_platform_device);
14468c2ecf20Sopenharmony_ci
14478c2ecf20Sopenharmony_ci	i8042_aux_irq_registered = i8042_kbd_irq_registered = false;
14488c2ecf20Sopenharmony_ci}
14498c2ecf20Sopenharmony_ci
14508c2ecf20Sopenharmony_cistatic int i8042_setup_aux(void)
14518c2ecf20Sopenharmony_ci{
14528c2ecf20Sopenharmony_ci	int (*aux_enable)(void);
14538c2ecf20Sopenharmony_ci	int error;
14548c2ecf20Sopenharmony_ci	int i;
14558c2ecf20Sopenharmony_ci
14568c2ecf20Sopenharmony_ci	if (i8042_check_aux())
14578c2ecf20Sopenharmony_ci		return -ENODEV;
14588c2ecf20Sopenharmony_ci
14598c2ecf20Sopenharmony_ci	if (i8042_nomux || i8042_check_mux()) {
14608c2ecf20Sopenharmony_ci		error = i8042_create_aux_port(-1);
14618c2ecf20Sopenharmony_ci		if (error)
14628c2ecf20Sopenharmony_ci			goto err_free_ports;
14638c2ecf20Sopenharmony_ci		aux_enable = i8042_enable_aux_port;
14648c2ecf20Sopenharmony_ci	} else {
14658c2ecf20Sopenharmony_ci		for (i = 0; i < I8042_NUM_MUX_PORTS; i++) {
14668c2ecf20Sopenharmony_ci			error = i8042_create_aux_port(i);
14678c2ecf20Sopenharmony_ci			if (error)
14688c2ecf20Sopenharmony_ci				goto err_free_ports;
14698c2ecf20Sopenharmony_ci		}
14708c2ecf20Sopenharmony_ci		aux_enable = i8042_enable_mux_ports;
14718c2ecf20Sopenharmony_ci	}
14728c2ecf20Sopenharmony_ci
14738c2ecf20Sopenharmony_ci	error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED,
14748c2ecf20Sopenharmony_ci			    "i8042", i8042_platform_device);
14758c2ecf20Sopenharmony_ci	if (error)
14768c2ecf20Sopenharmony_ci		goto err_free_ports;
14778c2ecf20Sopenharmony_ci
14788c2ecf20Sopenharmony_ci	error = aux_enable();
14798c2ecf20Sopenharmony_ci	if (error)
14808c2ecf20Sopenharmony_ci		goto err_free_irq;
14818c2ecf20Sopenharmony_ci
14828c2ecf20Sopenharmony_ci	i8042_aux_irq_registered = true;
14838c2ecf20Sopenharmony_ci	return 0;
14848c2ecf20Sopenharmony_ci
14858c2ecf20Sopenharmony_ci err_free_irq:
14868c2ecf20Sopenharmony_ci	free_irq(I8042_AUX_IRQ, i8042_platform_device);
14878c2ecf20Sopenharmony_ci err_free_ports:
14888c2ecf20Sopenharmony_ci	i8042_free_aux_ports();
14898c2ecf20Sopenharmony_ci	return error;
14908c2ecf20Sopenharmony_ci}
14918c2ecf20Sopenharmony_ci
14928c2ecf20Sopenharmony_cistatic int i8042_setup_kbd(void)
14938c2ecf20Sopenharmony_ci{
14948c2ecf20Sopenharmony_ci	int error;
14958c2ecf20Sopenharmony_ci
14968c2ecf20Sopenharmony_ci	error = i8042_create_kbd_port();
14978c2ecf20Sopenharmony_ci	if (error)
14988c2ecf20Sopenharmony_ci		return error;
14998c2ecf20Sopenharmony_ci
15008c2ecf20Sopenharmony_ci	error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
15018c2ecf20Sopenharmony_ci			    "i8042", i8042_platform_device);
15028c2ecf20Sopenharmony_ci	if (error)
15038c2ecf20Sopenharmony_ci		goto err_free_port;
15048c2ecf20Sopenharmony_ci
15058c2ecf20Sopenharmony_ci	error = i8042_enable_kbd_port();
15068c2ecf20Sopenharmony_ci	if (error)
15078c2ecf20Sopenharmony_ci		goto err_free_irq;
15088c2ecf20Sopenharmony_ci
15098c2ecf20Sopenharmony_ci	i8042_kbd_irq_registered = true;
15108c2ecf20Sopenharmony_ci	return 0;
15118c2ecf20Sopenharmony_ci
15128c2ecf20Sopenharmony_ci err_free_irq:
15138c2ecf20Sopenharmony_ci	free_irq(I8042_KBD_IRQ, i8042_platform_device);
15148c2ecf20Sopenharmony_ci err_free_port:
15158c2ecf20Sopenharmony_ci	i8042_free_kbd_port();
15168c2ecf20Sopenharmony_ci	return error;
15178c2ecf20Sopenharmony_ci}
15188c2ecf20Sopenharmony_ci
15198c2ecf20Sopenharmony_cistatic int i8042_kbd_bind_notifier(struct notifier_block *nb,
15208c2ecf20Sopenharmony_ci				   unsigned long action, void *data)
15218c2ecf20Sopenharmony_ci{
15228c2ecf20Sopenharmony_ci	struct device *dev = data;
15238c2ecf20Sopenharmony_ci	struct serio *serio = to_serio_port(dev);
15248c2ecf20Sopenharmony_ci	struct i8042_port *port = serio->port_data;
15258c2ecf20Sopenharmony_ci
15268c2ecf20Sopenharmony_ci	if (serio != i8042_ports[I8042_KBD_PORT_NO].serio)
15278c2ecf20Sopenharmony_ci		return 0;
15288c2ecf20Sopenharmony_ci
15298c2ecf20Sopenharmony_ci	switch (action) {
15308c2ecf20Sopenharmony_ci	case BUS_NOTIFY_BOUND_DRIVER:
15318c2ecf20Sopenharmony_ci		port->driver_bound = true;
15328c2ecf20Sopenharmony_ci		break;
15338c2ecf20Sopenharmony_ci
15348c2ecf20Sopenharmony_ci	case BUS_NOTIFY_UNBIND_DRIVER:
15358c2ecf20Sopenharmony_ci		port->driver_bound = false;
15368c2ecf20Sopenharmony_ci		break;
15378c2ecf20Sopenharmony_ci	}
15388c2ecf20Sopenharmony_ci
15398c2ecf20Sopenharmony_ci	return 0;
15408c2ecf20Sopenharmony_ci}
15418c2ecf20Sopenharmony_ci
15428c2ecf20Sopenharmony_cistatic int i8042_probe(struct platform_device *dev)
15438c2ecf20Sopenharmony_ci{
15448c2ecf20Sopenharmony_ci	int error;
15458c2ecf20Sopenharmony_ci
15468c2ecf20Sopenharmony_ci	if (i8042_reset == I8042_RESET_ALWAYS) {
15478c2ecf20Sopenharmony_ci		error = i8042_controller_selftest();
15488c2ecf20Sopenharmony_ci		if (error)
15498c2ecf20Sopenharmony_ci			return error;
15508c2ecf20Sopenharmony_ci	}
15518c2ecf20Sopenharmony_ci
15528c2ecf20Sopenharmony_ci	error = i8042_controller_init();
15538c2ecf20Sopenharmony_ci	if (error)
15548c2ecf20Sopenharmony_ci		return error;
15558c2ecf20Sopenharmony_ci
15568c2ecf20Sopenharmony_ci#ifdef CONFIG_X86
15578c2ecf20Sopenharmony_ci	if (i8042_dritek)
15588c2ecf20Sopenharmony_ci		i8042_dritek_enable();
15598c2ecf20Sopenharmony_ci#endif
15608c2ecf20Sopenharmony_ci
15618c2ecf20Sopenharmony_ci	if (!i8042_noaux) {
15628c2ecf20Sopenharmony_ci		error = i8042_setup_aux();
15638c2ecf20Sopenharmony_ci		if (error && error != -ENODEV && error != -EBUSY)
15648c2ecf20Sopenharmony_ci			goto out_fail;
15658c2ecf20Sopenharmony_ci	}
15668c2ecf20Sopenharmony_ci
15678c2ecf20Sopenharmony_ci	if (!i8042_nokbd) {
15688c2ecf20Sopenharmony_ci		error = i8042_setup_kbd();
15698c2ecf20Sopenharmony_ci		if (error)
15708c2ecf20Sopenharmony_ci			goto out_fail;
15718c2ecf20Sopenharmony_ci	}
15728c2ecf20Sopenharmony_ci/*
15738c2ecf20Sopenharmony_ci * Ok, everything is ready, let's register all serio ports
15748c2ecf20Sopenharmony_ci */
15758c2ecf20Sopenharmony_ci	i8042_register_ports();
15768c2ecf20Sopenharmony_ci
15778c2ecf20Sopenharmony_ci	return 0;
15788c2ecf20Sopenharmony_ci
15798c2ecf20Sopenharmony_ci out_fail:
15808c2ecf20Sopenharmony_ci	i8042_free_aux_ports();	/* in case KBD failed but AUX not */
15818c2ecf20Sopenharmony_ci	i8042_free_irqs();
15828c2ecf20Sopenharmony_ci	i8042_controller_reset(false);
15838c2ecf20Sopenharmony_ci
15848c2ecf20Sopenharmony_ci	return error;
15858c2ecf20Sopenharmony_ci}
15868c2ecf20Sopenharmony_ci
15878c2ecf20Sopenharmony_cistatic int i8042_remove(struct platform_device *dev)
15888c2ecf20Sopenharmony_ci{
15898c2ecf20Sopenharmony_ci	i8042_unregister_ports();
15908c2ecf20Sopenharmony_ci	i8042_free_irqs();
15918c2ecf20Sopenharmony_ci	i8042_controller_reset(false);
15928c2ecf20Sopenharmony_ci
15938c2ecf20Sopenharmony_ci	return 0;
15948c2ecf20Sopenharmony_ci}
15958c2ecf20Sopenharmony_ci
15968c2ecf20Sopenharmony_cistatic struct platform_driver i8042_driver = {
15978c2ecf20Sopenharmony_ci	.driver		= {
15988c2ecf20Sopenharmony_ci		.name	= "i8042",
15998c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
16008c2ecf20Sopenharmony_ci		.pm	= &i8042_pm_ops,
16018c2ecf20Sopenharmony_ci#endif
16028c2ecf20Sopenharmony_ci	},
16038c2ecf20Sopenharmony_ci	.probe		= i8042_probe,
16048c2ecf20Sopenharmony_ci	.remove		= i8042_remove,
16058c2ecf20Sopenharmony_ci	.shutdown	= i8042_shutdown,
16068c2ecf20Sopenharmony_ci};
16078c2ecf20Sopenharmony_ci
16088c2ecf20Sopenharmony_cistatic struct notifier_block i8042_kbd_bind_notifier_block = {
16098c2ecf20Sopenharmony_ci	.notifier_call = i8042_kbd_bind_notifier,
16108c2ecf20Sopenharmony_ci};
16118c2ecf20Sopenharmony_ci
16128c2ecf20Sopenharmony_cistatic int __init i8042_init(void)
16138c2ecf20Sopenharmony_ci{
16148c2ecf20Sopenharmony_ci	int err;
16158c2ecf20Sopenharmony_ci
16168c2ecf20Sopenharmony_ci	dbg_init();
16178c2ecf20Sopenharmony_ci
16188c2ecf20Sopenharmony_ci	err = i8042_platform_init();
16198c2ecf20Sopenharmony_ci	if (err)
16208c2ecf20Sopenharmony_ci		return (err == -ENODEV) ? 0 : err;
16218c2ecf20Sopenharmony_ci
16228c2ecf20Sopenharmony_ci	err = i8042_controller_check();
16238c2ecf20Sopenharmony_ci	if (err)
16248c2ecf20Sopenharmony_ci		goto err_platform_exit;
16258c2ecf20Sopenharmony_ci
16268c2ecf20Sopenharmony_ci	/* Set this before creating the dev to allow i8042_command to work right away */
16278c2ecf20Sopenharmony_ci	i8042_present = true;
16288c2ecf20Sopenharmony_ci
16298c2ecf20Sopenharmony_ci	err = platform_driver_register(&i8042_driver);
16308c2ecf20Sopenharmony_ci	if (err)
16318c2ecf20Sopenharmony_ci		goto err_platform_exit;
16328c2ecf20Sopenharmony_ci
16338c2ecf20Sopenharmony_ci	i8042_platform_device = platform_device_alloc("i8042", -1);
16348c2ecf20Sopenharmony_ci	if (!i8042_platform_device) {
16358c2ecf20Sopenharmony_ci		err = -ENOMEM;
16368c2ecf20Sopenharmony_ci		goto err_unregister_driver;
16378c2ecf20Sopenharmony_ci	}
16388c2ecf20Sopenharmony_ci
16398c2ecf20Sopenharmony_ci	err = platform_device_add(i8042_platform_device);
16408c2ecf20Sopenharmony_ci	if (err)
16418c2ecf20Sopenharmony_ci		goto err_free_device;
16428c2ecf20Sopenharmony_ci
16438c2ecf20Sopenharmony_ci	bus_register_notifier(&serio_bus, &i8042_kbd_bind_notifier_block);
16448c2ecf20Sopenharmony_ci	panic_blink = i8042_panic_blink;
16458c2ecf20Sopenharmony_ci
16468c2ecf20Sopenharmony_ci	return 0;
16478c2ecf20Sopenharmony_ci
16488c2ecf20Sopenharmony_cierr_free_device:
16498c2ecf20Sopenharmony_ci	platform_device_put(i8042_platform_device);
16508c2ecf20Sopenharmony_cierr_unregister_driver:
16518c2ecf20Sopenharmony_ci	platform_driver_unregister(&i8042_driver);
16528c2ecf20Sopenharmony_ci err_platform_exit:
16538c2ecf20Sopenharmony_ci	i8042_platform_exit();
16548c2ecf20Sopenharmony_ci	return err;
16558c2ecf20Sopenharmony_ci}
16568c2ecf20Sopenharmony_ci
16578c2ecf20Sopenharmony_cistatic void __exit i8042_exit(void)
16588c2ecf20Sopenharmony_ci{
16598c2ecf20Sopenharmony_ci	if (!i8042_present)
16608c2ecf20Sopenharmony_ci		return;
16618c2ecf20Sopenharmony_ci
16628c2ecf20Sopenharmony_ci	platform_device_unregister(i8042_platform_device);
16638c2ecf20Sopenharmony_ci	platform_driver_unregister(&i8042_driver);
16648c2ecf20Sopenharmony_ci	i8042_platform_exit();
16658c2ecf20Sopenharmony_ci
16668c2ecf20Sopenharmony_ci	bus_unregister_notifier(&serio_bus, &i8042_kbd_bind_notifier_block);
16678c2ecf20Sopenharmony_ci	panic_blink = NULL;
16688c2ecf20Sopenharmony_ci}
16698c2ecf20Sopenharmony_ci
16708c2ecf20Sopenharmony_cimodule_init(i8042_init);
16718c2ecf20Sopenharmony_cimodule_exit(i8042_exit);
1672