mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-04 10:30:32 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
353 lines
7.2 KiB
C
353 lines
7.2 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 */
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <i8042.h>
|
|
#include <input.h>
|
|
#include <keyboard.h>
|
|
#include <asm/io.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 */
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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 |= CONFIG_AT_TRANS;
|
|
config &= ~(CONFIG_KIRQ_EN | CONFIG_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;
|
|
}
|
|
|
|
/*
|
|
* Implement a weak default function for boards that optionally
|
|
* need to skip the i8042 initialization.
|
|
*
|
|
* TODO(sjg@chromium.org): Use device tree for this?
|
|
*/
|
|
int __weak board_i8042_skip(void)
|
|
{
|
|
/* As default, don't skip */
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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() || board_i8042_skip()) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
.ops = &i8042_kbd_ops,
|
|
.priv_auto_alloc_size = sizeof(struct i8042_kbd_priv),
|
|
};
|