From: Rakesh Iyer <ri...@nvidia.com> Add support for internal matrix keyboard controller for Nvidia Tegra platforms. This driver uses the fdt decode function to obtain its key codes.
Support for the Ctrl modifier is provided. The left and right ctrl keys are dealt with in the same way. When the user presses Ctrl+D, the top of the keyboard controller FIFO is usually filled with several "Ctrl" only keycodes. The modifier keys handling code would remove the keycode and return 0. So, the "tstc" will indicate that no key is pressed. The correct behaviour for "tstc" is to check all the entries of the FIFO until we find a significant one or return 0 if there is none. This driver adds a small FIFO that is used to store conversions of keycodes to escape sequences that U-Boot expects to see from its input device drivers. The fifo also replaces the testc/getc last keypress management code. Key detection before the driver is loaded is supported. This key will be picked up with the keyboard driver is initialized. Signed-off-by: Simon Glass <s...@chromium.org> --- drivers/input/Makefile | 1 + drivers/input/tegra-kbc.c | 626 +++++++++++++++++++++++++++++++++++++++++++++ include/fdtdec.h | 1 + include/tegra-kbc.h | 33 +++ lib/fdtdec.c | 1 + 5 files changed, 662 insertions(+), 0 deletions(-) create mode 100644 drivers/input/tegra-kbc.c create mode 100644 include/tegra-kbc.h diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 1f4dad3..4684e55 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -26,6 +26,7 @@ include $(TOPDIR)/config.mk LIB := $(obj)libinput.o COBJS-$(CONFIG_I8042_KBD) += i8042.o +COBJS-$(CONFIG_TEGRA2_KEYBOARD) += tegra-kbc.o ifdef CONFIG_PS2KBD COBJS-y += keyboard.o pc_keyb.o COBJS-$(CONFIG_PS2MULT) += ps2mult.o ps2ser.o diff --git a/drivers/input/tegra-kbc.c b/drivers/input/tegra-kbc.c new file mode 100644 index 0000000..7a45440 --- /dev/null +++ b/drivers/input/tegra-kbc.c @@ -0,0 +1,626 @@ +/* + * (C) Copyright 2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#define DEBUG +#include <common.h> +#include <malloc.h> +#include <stdio_dev.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/timer.h> +#include <tegra-kbc.h> +#include <fdtdec.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define DEVNAME "tegra-kbc" + +enum { + KBC_MAX_ROW = 16, + KBC_MAX_COL = 8, + KBC_KEY_COUNT = KBC_MAX_ROW * KBC_MAX_COL, + KBC_MAX_GPIO = 24, + KBC_MAX_KPENT = 8, /* size of keypress entry queue */ +}; + +enum KEYS { + KEY_FIRST_MODIFIER = 220, + KEY_RIGHT_CTRL = KEY_FIRST_MODIFIER, + KEY_LEFT_CTRL, + KEY_FN, + KEY_SHIFT, +}; + +/* KBC row scan time and delay for beginning the row scan (in usecs) */ +#define KBC_ROW_SCAN_TIME 16 +#define KBC_ROW_SCAN_DLY 5 + +/* uses a 32KHz clock so a cycle = 1/32Khz */ +#define KBC_CYCLE_IN_USEC DIV_ROUND_UP(1000, 32) + +#define KBC_FIFO_TH_CNT_SHIFT(cnt) (cnt << 14) +#define KBC_DEBOUNCE_CNT_SHIFT(cnt) (cnt << 4) +#define KBC_CONTROL_FIFO_CNT_INT_EN (1 << 3) +#define KBC_CONTROL_KBC_EN (1 << 0) +#define KBC_INT_FIFO_CNT_INT_STATUS (1 << 2) +#define KBC_KPENT_VALID (1 << 7) + +#define KBC_RPT_DLY 20 +#define KBC_RPT_RATE 4 + +/* + * Use a simple FIFO to convert some keys into escape sequences and to handle + * testc vs getc. The FIFO length must be a power of two. Currently, four + * characters is the smallest power of two that is large enough to contain all + * generated escape sequences. + */ +#define KBC_FIFO_LENGTH (1 << 2) + +static struct keyb { + /* + * Information about keycode mappings. The plain_keycode array must + * exist but the others may be NULL in which case they are not + * decoded. + */ + const u8 *plain_keycode; /* key code for each row / column */ + const u8 *shift_keycode; /* ...when shift held down */ + const u8 *fn_keycode; /* ...when Fn held down */ + const u8 *ctrl_keycode; /* ...when ctrl held down */ + + struct kbc_tegra *kbc; /* tegra keyboard controller */ + int prev_fifo[KBC_MAX_KPENT]; /* keys left over from last scan */ + int prev_cnt; /* number of keys left over */ + unsigned char prev_key; /* previously decoded key */ + unsigned int rpt_dly; /* delay until next key repeat */ + unsigned char repeat_key; /* the key being held down */ + unsigned char inited; /* 1 if keyboard has been inited */ + int fifo[KBC_FIFO_LENGTH]; /* key fifo, with decoded keys */ + int fifo_read; /* Key fifo read pointer */ + int fifo_write; /* Key fifo write pointer */ + unsigned int repoll_time; /* next time to poll keyboard (us) */ + + /* + * After init we must wait a short time before polling the keyboard. + * This gives the tegra keyboard controller time to react after reset + * and lets us grab keys pressed during reset. + */ + unsigned int init_dly; /* Delay before we can read keyboard */ + unsigned long start_time; /* Time that we inited (in us) */ +} config; + + +static int tegra_kbc_keycode(struct keyb *config, int r, int c, + int modifier) +{ + int entry = r * KBC_MAX_COL + c; + + if (modifier == KEY_FN && config->fn_keycode) + return config->fn_keycode[entry]; + + /* Put ctrl keys ahead of shift */ + else if ((modifier == KEY_LEFT_CTRL || modifier == KEY_RIGHT_CTRL) + && config->ctrl_keycode) + return config->ctrl_keycode[entry]; + else if (modifier == KEY_SHIFT && config->shift_keycode) + return config->shift_keycode[entry]; + else + return config->plain_keycode[entry]; +} + +/* determines if current keypress configuration can cause key ghosting */ +static int is_ghost_key_config(int *rows_val, int *cols_val, int valid) +{ + int i, j, key_in_same_col = 0, key_in_same_row = 0; + + /* + * Matrix keyboard designs are prone to keyboard ghosting. + * Ghosting occurs if there are 3 keys such that - + * any 2 of the 3 keys share a row, and any 2 of them share a column. + */ + for (i = 0; i < valid; i++) { + /* + * Find 2 keys such that one key is in the same row + * and the other is in the same column as the i-th key. + */ + for (j = i + 1; j < valid; j++) { + if (cols_val[j] == cols_val[i]) + key_in_same_col = 1; + if (rows_val[j] == rows_val[i]) + key_in_same_row = 1; + } + } + + if (key_in_same_col && key_in_same_row) + return 1; + else + return 0; +} + +/* reads the keyboard fifo for current keypresses. */ +static int tegra_kbc_find_keys(struct keyb *config, int *fifo) +{ + int rows_val[KBC_MAX_KPENT], cols_val[KBC_MAX_KPENT]; + u32 kp_ent_val[(KBC_MAX_KPENT + 3) / 4]; + u32 *kp_ents = kp_ent_val; + u32 kp_ent = 0; + int i, j, k, valid = 0; + int modifier = 0; + + for (i = 0; i < ARRAY_SIZE(kp_ent_val); i++) + kp_ent_val[i] = readl(&config->kbc->kp_ent[i]); + + valid = 0; + for (i = 0; i < KBC_MAX_KPENT; i++) { + if (!(i&3)) + kp_ent = *kp_ents++; + + if (kp_ent & KBC_KPENT_VALID) { + cols_val[valid] = kp_ent & 0x7; + rows_val[valid++] = (kp_ent >> 3) & 0xf; + } + kp_ent >>= 8; + } + for (i = 0; i < valid; i++) { + k = tegra_kbc_keycode(config, rows_val[i], cols_val[i], 0); + if (KEY_IS_MODIFIER(k)) { + modifier = k; + break; + } + } + + /* For a ghost key config, ignore the keypresses for this iteration. */ + if ((valid >= 3) && is_ghost_key_config(rows_val, cols_val, valid)) + return KBC_MAX_KPENT + 1; + + j = 0; + for (i = 0; i < valid; i++) { + k = tegra_kbc_keycode(config, rows_val[i], cols_val[i], + modifier); + /* Key modifiers (Fn and Shift) will not be part of the fifo */ + if (k && (k != -1)) + fifo[j++] = k; + } + + return j; +} + +static void process_keys(struct keyb *config, int fifo[], int cnt) +{ + int prev_key_in_fifo = 0; + char key = config->prev_key; + int i, j; + + /* + * If multiple keys are pressed such that it results in * 2 or more + * unmodified keys, we will determine the newest key and report + * that to the upper layer. + */ + for (i = 0; i < cnt; i++) { + for (j = 0; j < config->prev_cnt; j++) { + if (fifo[i] == config->prev_fifo[j]) + break; + } + /* Found a new key. */ + if (j == config->prev_cnt) { + key = fifo[i]; + break; + } + if (config->prev_key == fifo[i]) + prev_key_in_fifo = 1; + } + /* + * Keys were released and FIFO does not contain * the previous + * reported key. So report a null key. + */ + if (i == cnt && !prev_key_in_fifo) + key = 0; + + for (i = 0; i < cnt; i++) + config->prev_fifo[i] = fifo[i]; + config->prev_cnt = cnt; + config->prev_key = key; +} + +/* processes all the keypresses in fifo and returns a single key */ +static int tegra_kbc_get_single_char(struct keyb *config, u32 fifo_cnt) +{ + int fifo[KBC_MAX_KPENT] = {0}; + int cnt; + + if (fifo_cnt) { + do { + cnt = tegra_kbc_find_keys(config, fifo); + /* If this is a ghost key combo, report no change */ + if (cnt <= KBC_MAX_KPENT) + process_keys(config, fifo, cnt); + } while (!config->prev_key && --fifo_cnt); + } else { + config->prev_cnt = 0; + config->prev_key = 0; + } + + udelay((fifo_cnt == 1) ? config->repoll_time : 1000); + + return config->prev_key; +} + +/* manages keyboard hardware registers on keypresses and returns a key.*/ +static unsigned char tegra_kbc_get_char(struct keyb *config) +{ + struct kbc_tegra *kbc = config->kbc; + u32 val, ctl; + char key = 0; + + /* + * Until all keys are released, defer further processing to + * the polling loop. + */ + ctl = readl(&kbc->control); + ctl &= ~KBC_CONTROL_FIFO_CNT_INT_EN; + writel(ctl, &kbc->control); + + /* + * Quickly bail out & reenable interrupts if the interrupt source + * wasn't fifo count threshold + */ + val = readl(&kbc->interrupt); + writel(val, &kbc->interrupt); + + if (val & KBC_INT_FIFO_CNT_INT_STATUS) + key = tegra_kbc_get_single_char(config, (val >> 4) & 0xf); + + ctl |= KBC_CONTROL_FIFO_CNT_INT_EN; + writel(ctl, &kbc->control); + + return key; +} + +/* handles key repeat delay and key repeat rate.*/ +static int kbd_fetch_char(struct keyb *config, int test) +{ + unsigned char key; + + do { + key = tegra_kbc_get_char(config); + if (!key) { + config->repeat_key = 0; + if (test) + break; + else + continue; + } + + /* This logic takes care of the repeat rate */ + if ((key != config->repeat_key) || !(config->rpt_dly--)) + break; + } while (1); + + if (key == config->repeat_key) { + config->rpt_dly = KBC_RPT_RATE; + } else { + config->rpt_dly = KBC_RPT_DLY; + config->repeat_key = key; + } + + return key; +} + +/* + * Return true if there are no characters in the FIFO. + */ +static int kbd_fifo_empty(struct keyb *config) +{ + return config->fifo_read == config->fifo_write; +} + +/* + * Return number of characters of free space in the FIFO. + */ +static int kbd_fifo_free_space(struct keyb *config) +{ + return KBC_FIFO_LENGTH - (config->fifo_write - config->fifo_read); +} + +/* + * Insert a character into the FIFO. Calling this function when the FIFO is + * full will overwrite the oldest character in the FIFO. + */ +static void kbd_fifo_insert(struct keyb *config, int key) +{ + int index = config->fifo_write & (KBC_FIFO_LENGTH - 1); + + assert(kbd_fifo_free_space(config) > 0); + + config->fifo[index] = key; + + config->fifo_write++; +} + +/* + * Remove a character from the FIFO, it is an error to call this function when + * the FIFO is empty. + */ +static int kbd_fifo_remove(struct keyb *config) +{ + int index = config->fifo_read & (KBC_FIFO_LENGTH - 1); + int key = config->fifo[index]; + + assert(!kbd_fifo_empty(config)); + + config->fifo_read++; + + return key; +} + +/* + * Given a keycode, convert it to the sequence of characters that U-Boot expects + * to recieve from its input devices and insert the characters into the FIFO. + * If the keycode is 0, ignore it. Currently this function will only ever add + * at most three characters to the FIFO, so it is always safe to call when the + * FIFO is empty. + */ +static void kbd_fifo_refill(struct keyb *config, int key) +{ + switch (key) { + /* + * We need to deal with a zero keycode value here because the + * testc call can call us with zero if no key is pressed. + */ + case 0x00: + break; + + /* + * Generate escape sequences for arrow keys. Additional escape + * sequences can be added to the switch statements, but it may + * be better in the future t0 use the top bit of the keycode to + * indicate a key that generates an escape sequence. Then the + * outer switch could turn into an "if (key & 0x80)". + */ + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + kbd_fifo_insert(config, 0x1B); /* Escape */ + kbd_fifo_insert(config, '['); + + switch (key) { + case 0x1C: + kbd_fifo_insert(config, 'D'); + break; + case 0x1D: + kbd_fifo_insert(config, 'C'); + break; + case 0x1E: + kbd_fifo_insert(config, 'A'); + break; + case 0x1F: + kbd_fifo_insert(config, 'B'); + break; + } + break; + + /* + * All other keycodes can be treated as characters as is and + * are inserted into the FIFO directly. + */ + default: + kbd_fifo_insert(config, key); + break; + } +} + +static void kbd_wait_for_fifo_init(struct keyb *config) +{ + /* + * In order to detect keys pressed on boot, wait for the hardware to + * complete scanning the keys. This includes time to transition from + * Wkup mode to Continous polling mode and the repoll time. We can + * deduct the time thats already elapsed. + */ + if (!config->inited) { + unsigned long elapsed_time; + long delay; + + elapsed_time = timer_get_us() - config->start_time; + delay = config->init_dly + config->repoll_time - elapsed_time; + if (delay > 0) + udelay(delay); + + config->inited = 1; + } +} + +static int kbd_testc(void) +{ + kbd_wait_for_fifo_init(&config); + + if (kbd_fifo_empty(&config)) + kbd_fifo_refill(&config, kbd_fetch_char(&config, 1)); + + return !kbd_fifo_empty(&config); +} + +static int kbd_getc(void) +{ + if (kbd_fifo_empty(&config)) + kbd_fifo_refill(&config, kbd_fetch_char(&config, 0)); + + return kbd_fifo_remove(&config); +} + +/* configures keyboard GPIO registers to use the rows and columns */ +static void config_kbc(struct kbc_tegra *kbc) +{ + int i; + + for (i = 0; i < KBC_MAX_GPIO; i++) { + u32 row_cfg, col_cfg; + u32 r_shift = 5 * (i % 6); + u32 c_shift = 4 * (i % 8); + u32 r_mask = 0x1f << r_shift; + u32 c_mask = 0xf << c_shift; + u32 r_offs = i / 6; + u32 c_offs = i / 8; + + row_cfg = readl(&kbc->row_cfg[r_offs]); + col_cfg = readl(&kbc->col_cfg[c_offs]); + + row_cfg &= ~r_mask; + col_cfg &= ~c_mask; + + if (i < KBC_MAX_ROW) + row_cfg |= ((i << 1) | 1) << r_shift; + else + col_cfg |= (((i - KBC_MAX_ROW) << 1) | 1) << c_shift; + + writel(row_cfg, &kbc->row_cfg[r_offs]); + writel(col_cfg, &kbc->col_cfg[c_offs]); + } +} + +static int tegra_kbc_open(void) +{ + unsigned int scan_time_rows, debounce_cnt, rpt_cnt; + struct kbc_tegra *kbc = config.kbc; + u32 val = 0; + + config_kbc(kbc); + + /* + * The time delay between two consecutive reads of the FIFO is + * the sum of the repeat time and the time taken for scanning + * the rows. There is an additional delay before the row scanning + * starts. The repoll delay is computed in microseconds. + */ + rpt_cnt = 5 * DIV_ROUND_UP(1000, KBC_CYCLE_IN_USEC); + debounce_cnt = 2; + scan_time_rows = (KBC_ROW_SCAN_TIME + debounce_cnt) * KBC_MAX_ROW; + config.repoll_time = KBC_ROW_SCAN_DLY + scan_time_rows + rpt_cnt; + config.repoll_time = config.repoll_time * KBC_CYCLE_IN_USEC; + + writel(rpt_cnt, &kbc->rpt_dly); + + val = KBC_DEBOUNCE_CNT_SHIFT(debounce_cnt); + val |= KBC_FIFO_TH_CNT_SHIFT(1); /* set fifo interrupt threshold to 1 */ + val |= KBC_CONTROL_FIFO_CNT_INT_EN; /* interrupt on FIFO threshold */ + val |= KBC_CONTROL_KBC_EN; /* enable */ + + writel(val, &kbc->control); + + config.init_dly = readl(&kbc->init_dly) * KBC_CYCLE_IN_USEC; + config.start_time = timer_get_us(); + + return 0; +} + +void config_kbc_pinmux(void) +{ + enum pmux_pingrp pingrp[] = {PINGRP_KBCA, PINGRP_KBCB, PINGRP_KBCC, + PINGRP_KBCD, PINGRP_KBCE, PINGRP_KBCF}; + int i; + + for (i = 0; i < (sizeof(pingrp)/sizeof(pingrp[0])); i++) { + pinmux_tristate_disable(pingrp[i]); + pinmux_set_func(pingrp[i], PMUX_FUNC_KBC); + pinmux_set_pullupdown(pingrp[i], PMUX_PULL_UP); + } +} + +static int fdt_decode_kbc(const void *blob, int node, struct keyb *config) +{ + config->kbc = (struct kbc_tegra *)fdtdec_get_addr(blob, node, "reg"); + config->plain_keycode = fdtdec_locate_byte_array(blob, node, + "keycode-plain", KBC_KEY_COUNT); + config->shift_keycode = fdtdec_locate_byte_array(blob, node, + "keycode-shift", KBC_KEY_COUNT); + config->fn_keycode = fdtdec_locate_byte_array(blob, node, + "keycode-fn", KBC_KEY_COUNT); + config->ctrl_keycode = fdtdec_locate_byte_array(blob, node, + "keycode-ctrl", KBC_KEY_COUNT); + if (!config->plain_keycode) { + debug("%s: cannot find keycode-plain map\n", __func__); + return -1; + } + return 0; +} + +int drv_keyboard_init(void) +{ + int error; + struct stdio_dev kbddev; + char *stdinname; + +#ifdef CONFIG_OF_CONTROL + int node; + + node = fdtdec_next_compatible(gd->fdt_blob, 0, + COMPAT_NVIDIA_TEGRA20_KBC); + if (node < 0) { + debug("%s: cannot locate keyboard node\n", __func__); + return node; + } + if (fdt_decode_kbc(gd->fdt_blob, node, &config)) + return -1; +#else +#error "Tegra keyboard driver requires FDT definitions" +#endif + config.rpt_dly = KBC_RPT_DLY; + + config_kbc_pinmux(); + + /* + * All of the Tegra board use the same clock configuration for now. + * This can be moved to the board specific configuration if that + * changes. + */ + clock_enable(PERIPH_ID_KBC); + + stdinname = getenv("stdin"); + memset(&kbddev, 0, sizeof(kbddev)); + strcpy(kbddev.name, DEVNAME); + kbddev.flags = DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; + kbddev.putc = NULL; + kbddev.puts = NULL; + kbddev.getc = kbd_getc; + kbddev.tstc = kbd_testc; + kbddev.start = tegra_kbc_open; + + error = stdio_register(&kbddev); + if (!error) { + /* check if this is the standard input device */ + if (strcmp(stdinname, DEVNAME) == 0) { + /* reassign the console */ + if (OVERWRITE_CONSOLE) + return 1; + + error = console_assign(stdin, DEVNAME); + if (!error) + return 0; + else + return error; + } + return 1; + } + + return error; +} diff --git a/include/fdtdec.h b/include/fdtdec.h index f72d219..9a61611 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -58,6 +58,7 @@ struct fdt_memory { enum fdt_compat_id { COMPAT_UNKNOWN, COMPAT_NVIDIA_TEGRA20_USB, /* Tegra2 USB port */ + COMPAT_NVIDIA_TEGRA20_KBC, /* Tegra2 Keyboard */ COMPAT_COUNT, }; diff --git a/include/tegra-kbc.h b/include/tegra-kbc.h new file mode 100644 index 0000000..7803f10 --- /dev/null +++ b/include/tegra-kbc.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __include_tegra_kbc_h__ +#define __include_tegra_kbc_h__ + +#include <common.h> + +#define KEY_IS_MODIFIER(key) ((key) >= KEY_FIRST_MODIFIER) + +struct kbc_tegra { + u32 control; + u32 interrupt; + u32 row_cfg[4]; + u32 col_cfg[3]; + u32 timeout_cnt; + u32 init_dly; + u32 rpt_dly; + u32 kp_ent[2]; + u32 row_mask[16]; +}; + +#ifdef CONFIG_SYS_CONSOLE_OVERWRITE_ROUTINE +extern int overwrite_console(void); +#define OVERWRITE_CONSOLE overwrite_console() +#else +#define OVERWRITE_CONSOLE 0 +#endif /* CONFIG_SYS_CONSOLE_OVERWRITE_ROUTINE */ + +#endif /* __include_tegra_kbc_h__ */ diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 90db53c..6db814a 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -38,6 +38,7 @@ DECLARE_GLOBAL_DATA_PTR; static const char * const compat_names[COMPAT_COUNT] = { COMPAT(UNKNOWN, "<none>"), COMPAT(NVIDIA_TEGRA20_USB, "nvidia,tegra20-ehci"), + COMPAT(NVIDIA_TEGRA20_KBC, "nvidia,tegra20-kbc"), }; /** -- 1.7.3.1 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot