summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRakesh Iyer <riyer@nvidia.com>2011-05-24 14:11:14 -0700
committerSimon Glass <sjg@chromium.org>2011-08-24 10:01:50 -0700
commit40e9147ccc3a8a8d841d044373a8e756534640bd (patch)
treed005493f6ab1db8663d7e5c02cd828891fc4af11 /drivers
parent31cc6903e6790835acd042d078984d98a8277eff (diff)
Tegra2: input: Add tegra keyboard support.
Add support for internal matrix keyboard controller for Nvidia Tegra platforms. BUG=None TEST=Keyboard works fine on Seaboard. Change-Id: I1031997c11dea9caa9d10c5875f40a9189ea1a46 Signed-off-by: Rakesh Iyer <riyer@nvidia.com> Reviewed-on: http://gerrit.chromium.org/gerrit/1484 Reviewed-by: Anton Staaf <robotboy@chromium.org> Tested-by: Anton Staaf <robotboy@chromium.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/Makefile1
-rwxr-xr-xdrivers/input/tegra-kbc.c440
2 files changed, 441 insertions, 0 deletions
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 1f4dad35b53..4684e55b2e7 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 100755
index 00000000000..cb6fbcaec7f
--- /dev/null
+++ b/drivers/input/tegra-kbc.c
@@ -0,0 +1,440 @@
+/*
+ * (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
+ */
+
+#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 <tegra-kbc.h>
+
+#define DEVNAME "tegra-kbc"
+
+#define KBC_MAX_GPIO 24
+#define KBC_MAX_KPENT 8
+#define KBC_MAX_ROW 16
+#define KBC_MAX_COL 8
+
+#define KBC_MAX_KEY (KBC_MAX_ROW * KBC_MAX_COL)
+
+#define TEGRA2_KBC_BASE 0x7000E200
+
+/* KBC row scan time and delay for beginning the row scan. */
+#define KBC_ROW_SCAN_TIME 16
+#define KBC_ROW_SCAN_DLY 5
+
+/* uses a 32KHz clock so a cycle = 1/32Khz */
+#define KBC_CYCLE_USEC 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
+
+/* kbc globals */
+unsigned int kbc_repoll_time;
+int kbc_last_keypress;
+int *kbc_plain_keycode;
+int *kbc_fn_keycode;
+int *kbc_shift_keycode;
+
+static int tegra_kbc_keycode(int r, int c, int modifier)
+{
+ if (modifier == KEY_FN)
+ return kbc_fn_keycode[(r * KBC_MAX_COL) + c];
+ else if (modifier == KEY_SHIFT)
+ return kbc_shift_keycode[(r * KBC_MAX_COL) + c];
+ else
+ return kbc_plain_keycode[(r * KBC_MAX_COL) + c];
+}
+
+/* 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(int *fifo)
+{
+ struct kbc_tegra *kbc = (struct kbc_tegra *)TEGRA2_KBC_BASE;
+ 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(&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(rows_val[i], cols_val[i], 0);
+ if ((k == KEY_FN) || (k == KEY_SHIFT)) {
+ 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(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;
+}
+
+/* processes all the keypresses in fifo and returns a single key */
+static int tegra_kbc_get_single_char(u32 fifo_cnt)
+{
+ int i, cnt, j;
+ int fifo[KBC_MAX_KPENT] = {0};
+ static int prev_fifo[KBC_MAX_KPENT];
+ static int prev_cnt;
+ static int prev_key;
+ char key = 0;
+
+ if (fifo_cnt) {
+ cnt = tegra_kbc_find_keys(fifo);
+ /* If this is a ghost key combination report no change. */
+ key = prev_key;
+ if (cnt <= KBC_MAX_KPENT) {
+ int prev_key_in_fifo = 0;
+ /*
+ * 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 < prev_cnt; j++) {
+ if (fifo[i] == prev_fifo[j])
+ break;
+ }
+ /* Found a new key. */
+ if (j == prev_cnt) {
+ key = fifo[i];
+ break;
+ }
+ if (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++)
+ prev_fifo[i] = fifo[i];
+ prev_cnt = cnt;
+ prev_key = key;
+ }
+ } else {
+ prev_cnt = 0;
+ prev_key = 0;
+ }
+ udelay((fifo_cnt == 1) ? kbc_repoll_time : 1000);
+
+ return key;
+}
+
+/* manages keyboard hardware registers on keypresses and returns a key.*/
+static unsigned char tegra_kbc_get_char(void)
+{
+ struct kbc_tegra *kbc = (struct kbc_tegra *)TEGRA2_KBC_BASE;
+ 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)) {
+ ctl |= KBC_CONTROL_FIFO_CNT_INT_EN;
+ writel(ctl, &kbc->control);
+ return 0;
+ }
+
+ key = tegra_kbc_get_single_char((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(int test)
+{
+ unsigned char key;
+ static unsigned char prev_key;
+ static unsigned int rpt_dly = KBC_RPT_DLY;
+
+ do {
+ key = tegra_kbc_get_char();
+ if (!key) {
+ prev_key = 0;
+ if (test)
+ break;
+ else
+ continue;
+ }
+
+ /* This logic takes care of the repeat rate */
+ if ((key != prev_key) || !(rpt_dly--))
+ break;
+ } while (1);
+
+ if (key == prev_key) {
+ rpt_dly = KBC_RPT_RATE;
+ } else {
+ rpt_dly = KBC_RPT_DLY;
+ prev_key = key;
+ }
+
+ return key;
+}
+
+static int kbd_testc(void)
+{
+ unsigned char key = kbd_fetch_char(1);
+
+ if (key)
+ kbc_last_keypress = key;
+
+ return (key != 0);
+}
+
+static int kbd_getc(void)
+{
+ unsigned char key;
+
+ if (kbc_last_keypress) {
+ key = kbc_last_keypress;
+ kbc_last_keypress = 0;
+ } else {
+ key = kbd_fetch_char(0);
+ }
+
+ return key;
+}
+
+/* configures keyboard GPIO registers to use the rows and columns */
+static void config_kbc(void)
+{
+ struct kbc_tegra *kbc = (struct kbc_tegra *)TEGRA2_KBC_BASE;
+ 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)
+{
+ struct kbc_tegra *kbc = (struct kbc_tegra *)TEGRA2_KBC_BASE;
+ unsigned int scan_time_rows, debounce_cnt, rpt_cnt;
+ u32 val = 0;
+
+ config_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 * (1000 / KBC_CYCLE_USEC);
+ debounce_cnt = 2;
+ scan_time_rows = (KBC_ROW_SCAN_TIME + debounce_cnt) * KBC_MAX_ROW;
+ kbc_repoll_time = KBC_ROW_SCAN_DLY + scan_time_rows + rpt_cnt;
+ kbc_repoll_time = (kbc_repoll_time * KBC_CYCLE_USEC) + 999;
+
+ 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);
+
+ /*
+ * Atomically clear out any remaining entries in the key FIFO
+ * and enable keyboard interrupts.
+ */
+ while (1) {
+ val = readl(&kbc->interrupt);
+ val >>= 4;
+ if (val) {
+ readl(kbc->kp_ent[0]);
+ readl(kbc->kp_ent[1]);
+ } else {
+ break;
+ }
+ }
+ writel(0x7, &kbc->interrupt);
+
+ 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);
+ }
+}
+
+int drv_keyboard_init(void)
+{
+ int error;
+ struct stdio_dev kbddev;
+ char *stdinname;
+
+ 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);
+
+ kbc_plain_keycode = board_keyboard_config.plain_keycode;
+ kbc_shift_keycode = board_keyboard_config.shift_keycode;
+ kbc_fn_keycode = board_keyboard_config.function_keycode;
+
+ 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;
+}