u-boot/drivers/input/i8042.c
Tom Rini d678a59d2d Revert "Merge patch series "arm: dts: am62-beagleplay: Fix Beagleplay Ethernet""
When bringing in the series 'arm: dts: am62-beagleplay: Fix Beagleplay
Ethernet"' I failed to notice that b4 noticed it was based on next and
so took that as the base commit and merged that part of next to master.

This reverts commit c8ffd1356d, reversing
changes made to 2ee6f3a5f7.

Reported-by: Jonas Karlman <jonas@kwiboo.se>
Signed-off-by: Tom Rini <trini@konsulko.com>
2024-05-19 08:16:36 -06:00

382 lines
8.0 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2002 ELTEC Elektronik AG
* Frank Gottschling <fgottschling@eltec.de>
*/
/* i8042.c - Intel 8042 keyboard driver routines */
#define LOG_CATEGORY UCLASS_KEYBOARD
#include <common.h>
#include <dm.h>
#include <env.h>
#include <errno.h>
#include <i8042.h>
#include <input.h>
#include <keyboard.h>
#include <log.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <linux/delay.h>
DECLARE_GLOBAL_DATA_PTR;
/* defines */
#define in8(p) inb(p)
#define out8(p, v) outb(v, p)
enum {
QUIRK_DUP_POR = 1 << 0,
};
/* locals */
struct i8042_kbd_priv {
bool extended; /* true if an extended keycode is expected next */
int quirks; /* quirks that we support */
};
static unsigned char ext_key_map[] = {
0x1c, /* keypad enter */
0x1d, /* right control */
0x35, /* keypad slash */
0x37, /* print screen */
0x38, /* right alt */
0x46, /* break */
0x47, /* editpad home */
0x48, /* editpad up */
0x49, /* editpad pgup */
0x4b, /* editpad left */
0x4d, /* editpad right */
0x4f, /* editpad end */
0x50, /* editpad dn */
0x51, /* editpad pgdn */
0x52, /* editpad ins */
0x53, /* editpad del */
0x00 /* map end */
};
/**
* kbd_input_empty() - Wait until the keyboard is ready for a command
*
* Checks the IBF flag (input buffer full), waiting for it to indicate that
* any previous command has been processed.
*
* Return: true if ready, false if it timed out
*/
static int kbd_input_empty(void)
{
int kbd_timeout = KBD_TIMEOUT * 1000;
while ((in8(I8042_STS_REG) & STATUS_IBF) && kbd_timeout--)
udelay(1);
return kbd_timeout != -1;
}
/**
* kbd_output_full() - Wait until the keyboard has data available
*
* Checks the OBF flag (output buffer full), waiting for it to indicate that
* a response to a previous command is available
*/
static int kbd_output_full(void)
{
int kbd_timeout = KBD_TIMEOUT * 1000;
while (((in8(I8042_STS_REG) & STATUS_OBF) == 0) && kbd_timeout--)
udelay(1);
return kbd_timeout != -1;
}
/**
* check_leds() - Check the keyboard LEDs and update them it needed
*
* @ret: Value to return
* Return: value of @ret
*/
static int i8042_kbd_update_leds(struct udevice *dev, int leds)
{
kbd_input_empty();
out8(I8042_DATA_REG, CMD_SET_KBD_LED);
kbd_input_empty();
out8(I8042_DATA_REG, leds & 0x7);
return 0;
}
static int kbd_write(int reg, int value)
{
if (!kbd_input_empty())
return -1;
out8(reg, value);
return 0;
}
static int kbd_read(int reg)
{
if (!kbd_output_full())
return -1;
return in8(reg);
}
static int kbd_cmd_read(int cmd)
{
if (kbd_write(I8042_CMD_REG, cmd))
return -1;
return kbd_read(I8042_DATA_REG);
}
static int kbd_cmd_write(int cmd, int data)
{
if (kbd_write(I8042_CMD_REG, cmd))
return -1;
return kbd_write(I8042_DATA_REG, data);
}
static int kbd_reset(int quirk)
{
int config;
if (!kbd_input_empty())
goto err;
/* controller self test */
if (kbd_cmd_read(CMD_SELF_TEST) != KBC_TEST_OK)
goto err;
/* keyboard reset */
if (kbd_write(I8042_DATA_REG, CMD_RESET_KBD) ||
kbd_read(I8042_DATA_REG) != KBD_ACK ||
kbd_read(I8042_DATA_REG) != KBD_POR)
goto err;
if (kbd_write(I8042_DATA_REG, CMD_DRAIN_OUTPUT) ||
kbd_read(I8042_DATA_REG) != KBD_ACK)
goto err;
/* set AT translation and disable irq */
config = kbd_cmd_read(CMD_RD_CONFIG);
if (config == -1)
goto err;
/* Sometimes get a second byte */
else if ((quirk & QUIRK_DUP_POR) && config == KBD_POR)
config = kbd_cmd_read(CMD_RD_CONFIG);
config |= CFG_AT_TRANS;
config &= ~(CFG_KIRQ_EN | CFG_MIRQ_EN);
if (kbd_cmd_write(CMD_WR_CONFIG, config))
goto err;
/* enable keyboard */
if (kbd_write(I8042_CMD_REG, CMD_KBD_EN) ||
!kbd_input_empty())
goto err;
return 0;
err:
debug("%s: Keyboard failure\n", __func__);
return -1;
}
static int kbd_controller_present(void)
{
return in8(I8042_STS_REG) != 0xff;
}
/** Flush all buffer from keyboard controller to host*/
static void i8042_flush(void)
{
int timeout;
/*
* The delay is to give the keyboard controller some time
* to fill the next byte.
*/
while (1) {
timeout = 100; /* wait for no longer than 100us */
while (timeout > 0 && !(in8(I8042_STS_REG) & STATUS_OBF)) {
udelay(1);
timeout--;
}
/* Try to pull next byte if not timeout */
if (in8(I8042_STS_REG) & STATUS_OBF)
in8(I8042_DATA_REG);
else
break;
}
}
/**
* Disables the keyboard so that key strokes no longer generate scancodes to
* the host.
*
* Return: 0 if ok, -1 if keyboard input was found while disabling
*/
static int i8042_disable(void)
{
if (kbd_input_empty() == 0)
return -1;
/* Disable keyboard */
out8(I8042_CMD_REG, CMD_KBD_DIS);
if (kbd_input_empty() == 0)
return -1;
return 0;
}
static int i8042_kbd_check(struct input_config *input)
{
struct i8042_kbd_priv *priv = dev_get_priv(input->dev);
if ((in8(I8042_STS_REG) & STATUS_OBF) == 0) {
return 0;
} else {
bool release = false;
int scan_code;
int i;
scan_code = in8(I8042_DATA_REG);
if (scan_code == 0xfa) {
return 0;
} else if (scan_code == 0xe0) {
priv->extended = true;
return 0;
}
if (scan_code & 0x80) {
scan_code &= 0x7f;
release = true;
}
if (priv->extended) {
priv->extended = false;
for (i = 0; ext_key_map[i]; i++) {
if (ext_key_map[i] == scan_code) {
scan_code = 0x60 + i;
break;
}
}
/* not found ? */
if (!ext_key_map[i])
return 0;
}
input_add_keycode(input, scan_code, release);
return 1;
}
}
/* i8042_kbd_init - reset keyboard and init state flags */
static int i8042_start(struct udevice *dev)
{
struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
struct i8042_kbd_priv *priv = dev_get_priv(dev);
struct input_config *input = &uc_priv->input;
int keymap, try;
char *penv;
int ret;
if (!kbd_controller_present()) {
debug("i8042 keyboard controller is not present\n");
return -ENOENT;
}
/* Init keyboard device (default US layout) */
keymap = KBD_US;
penv = env_get("keymap");
if (penv != NULL) {
if (strncmp(penv, "de", 3) == 0)
keymap = KBD_GER;
}
for (try = 0; kbd_reset(priv->quirks) != 0; try++) {
if (try >= KBD_RESET_TRIES)
return -1;
}
ret = input_add_tables(input, keymap == KBD_GER);
if (ret)
return ret;
i8042_kbd_update_leds(dev, NORMAL);
debug("%s: started\n", __func__);
return 0;
}
static int i8042_kbd_remove(struct udevice *dev)
{
if (i8042_disable())
log_debug("i8042_disable() failed. fine, continue.\n");
i8042_flush();
return 0;
}
/**
* Set up the i8042 keyboard. This is called by the stdio device handler
*
* We want to do this init when the keyboard is actually used rather than
* at start-up, since keyboard input may not currently be selected.
*
* Once the keyboard starts there will be a period during which we must
* wait for the keyboard to init. We do this only when a key is first
* read - see kbd_wait_for_fifo_init().
*
* Return: 0 if ok, -ve on error
*/
static int i8042_kbd_probe(struct udevice *dev)
{
struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
struct i8042_kbd_priv *priv = dev_get_priv(dev);
struct stdio_dev *sdev = &uc_priv->sdev;
struct input_config *input = &uc_priv->input;
int ret;
if (fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
"intel,duplicate-por"))
priv->quirks |= QUIRK_DUP_POR;
/* Register the device. i8042_start() will be called soon */
input->dev = dev;
input->read_keys = i8042_kbd_check;
input_allow_repeats(input, true);
strcpy(sdev->name, "i8042-kbd");
ret = input_stdio_register(sdev);
if (ret) {
debug("%s: input_stdio_register() failed\n", __func__);
return ret;
}
debug("%s: ready\n", __func__);
return 0;
}
static const struct keyboard_ops i8042_kbd_ops = {
.start = i8042_start,
.update_leds = i8042_kbd_update_leds,
};
static const struct udevice_id i8042_kbd_ids[] = {
{ .compatible = "intel,i8042-keyboard" },
{ }
};
U_BOOT_DRIVER(i8042_kbd) = {
.name = "i8042_kbd",
.id = UCLASS_KEYBOARD,
.of_match = i8042_kbd_ids,
.probe = i8042_kbd_probe,
.remove = i8042_kbd_remove,
.ops = &i8042_kbd_ops,
.priv_auto = sizeof(struct i8042_kbd_priv),
};