summaryrefslogtreecommitdiff
path: root/drivers/input/mxc_keyb.c
diff options
context:
space:
mode:
authorYe Li <ye.li@nxp.com>2016-03-09 20:59:43 +0800
committerYe Li <ye.li@nxp.com>2018-04-27 02:21:25 -0700
commit7f8757016e97adeacba256bd0cb6ad8882f6a51e (patch)
tree291bffbdd8e584c2d6f717c0fcc992f897a41744 /drivers/input/mxc_keyb.c
parent148488728486137a790a89e9b869cc938c3e0c57 (diff)
MLK-12527-1 mxc_keyb: Add MXC keyboard driver
The i.MX6SL EVK needs this driver in android fastboot support. Add this driver to u-boot. To use the driver, user must define: CONFIG_MXC_KPD Enable the driver CONFIG_MXC_KEYMAPPING Key mapping matrix CONFIG_MXC_KPD_COLMAX The column size of key mapping matrix CONFIG_MXC_KPD_ROWMAX The row size of the key mapping matrix Signed-off-by: Ye Li <ye.li@nxp.com> (cherry picked from commit 5096e572667ff41217deb4ba9b1bd15e93fa6b59) (cherry picked from commit e84160eaf5c057da45a227039c6f8a7911f43a82)
Diffstat (limited to 'drivers/input/mxc_keyb.c')
-rw-r--r--drivers/input/mxc_keyb.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/drivers/input/mxc_keyb.c b/drivers/input/mxc_keyb.c
new file mode 100644
index 0000000000..dabe2c98a4
--- /dev/null
+++ b/drivers/input/mxc_keyb.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2009-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*!
+ * @file mxc_keyb.c
+ *
+ * @brief Driver for the Freescale Semiconductor MXC keypad port.
+ *
+ * The keypad driver is designed as a standard Input driver which interacts
+ * with low level keypad port hardware. Upon opening, the Keypad driver
+ * initializes the keypad port. When the keypad interrupt happens the driver
+ * calles keypad polling timer and scans the keypad matrix for key
+ * press/release. If all key press/release happened it comes out of timer and
+ * waits for key press interrupt. The scancode for key press and release events
+ * are passed to Input subsytem.
+ *
+ * @ingroup keypad
+ */
+
+#include <asm/io.h>
+#include <common.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <asm/mach-imx/mxc_key_defs.h>
+#include <malloc.h>
+
+/*
+ * * Module header file
+ * */
+#include <mxc_keyb.h>
+
+/*!
+ * Comment KPP_DEBUG to disable debug messages
+ */
+
+#undef KPP_DEBUG
+
+#ifdef KPP_DEBUG
+#define KPP_PRINTF(fmt, args...) printf(fmt , ##args)
+
+static void mxc_kpp_dump_regs()
+{
+ unsigned short t1, t2, t3;
+
+ t1 = __raw_readw(KPCR);
+ t2 = __raw_readw(KPSR);
+ t3 = __raw_readw(KDDR);
+ /*
+ KPP_PRINTF("KPCR=0x%04x, KPSR=0x%04x, KDDR=0x%04x\n",
+ t1, t2, t3);
+ */
+}
+#else
+#define KPP_PRINTF(fmt, args...)
+#endif
+
+static u16 mxc_key_mapping[] = CONFIG_MXC_KEYMAPPING;
+
+/*!
+ * This structure holds the keypad private data structure.
+ */
+static struct keypad_priv kpp_dev;
+
+/*! Indicates if the key pad device is enabled. */
+
+/*! This static variable indicates whether a key event is pressed/released. */
+static unsigned short KPress;
+
+/*! cur_rcmap and prev_rcmap array is used to detect key press and release. */
+static unsigned short *cur_rcmap; /* max 64 bits (8x8 matrix) */
+static unsigned short *prev_rcmap;
+
+/*!
+ * Debounce polling period(10ms) in system ticks.
+ */
+/*static unsigned short KScanRate = (10 * CONFIG_SYS_HZ) / 1000;*/
+
+/*!
+ * These arrays are used to store press and release scancodes.
+ */
+static short **press_scancode;
+static short **release_scancode;
+
+static const unsigned short *mxckpd_keycodes;
+static unsigned short mxckpd_keycodes_size;
+
+/*!
+ * This function is called to scan the keypad matrix to find out the key press
+ * and key release events. Make scancode and break scancode are generated for
+ * key press and key release events.
+ *
+ * The following scanning sequence are done for
+ * keypad row and column scanning,
+ * -# Write 1's to KPDR[15:8], setting column data to 1's
+ * -# Configure columns as totem pole outputs(for quick discharging of keypad
+ * capacitance)
+ * -# Configure columns as open-drain
+ * -# Write a single column to 0, others to 1.
+ * -# Sample row inputs and save data. Multiple key presses can be detected on
+ * a single column.
+ * -# Repeat steps the above steps for remaining columns.
+ * -# Return all columns to 0 in preparation for standby mode.
+ * -# Clear KPKD and KPKR status bit(s) by writing to a 1,
+ * Set the KPKR synchronizer chain by writing "1" to KRSS register,
+ * Clear the KPKD synchronizer chain by writing "1" to KDSC register
+ *
+ * @result Number of key pressed/released.
+ */
+static int mxc_kpp_scan_matrix(void)
+{
+ unsigned short reg_val;
+ int col, row;
+ short scancode = 0;
+ int keycnt = 0; /* How many keys are still pressed */
+
+ /*
+ * wmb() linux kernel function which guarantees orderings in write
+ * operations
+ */
+ /* wmb(); */
+
+ /* save cur keypad matrix to prev */
+ memcpy(prev_rcmap, cur_rcmap, kpp_dev.kpp_rows * sizeof(prev_rcmap[0]));
+ memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0]));
+
+ /*1. Disable both (depress and release) keypad interrupts.*/
+
+ /* KDIE has been disabled in mxc_kpp_getc before calling scan matrix.
+ * KRIE is always disabled in this driver.
+ */
+
+ for (col = 0; col < kpp_dev.kpp_cols; col++) { /* Col */
+ /* 2. Write 1.s to KPDR[15:8] setting column data to 1.s */
+ reg_val = __raw_readw(KPDR);
+ reg_val |= 0xff00;
+ __raw_writew(reg_val, KPDR);
+
+ /*
+ * 3. Configure columns as totem pole outputs(for quick
+ * discharging of keypad capacitance)
+ */
+ reg_val = __raw_readw(KPCR);
+ reg_val &= 0x00ff;
+ __raw_writew(reg_val, KPCR);
+
+ udelay(2);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ /*
+ * 4. Configure columns as open-drain
+ */
+ reg_val = __raw_readw(KPCR);
+ reg_val |= ((1 << kpp_dev.kpp_cols) - 1) << 8;
+ __raw_writew(reg_val, KPCR);
+
+ /*
+ * 5. Write a single column to 0, others to 1.
+ * 6. Sample row inputs and save data. Multiple key presses
+ * can be detected on a single column.
+ * 7. Repeat steps 2 - 6 for remaining columns.
+ */
+
+ /* Col bit starts at 8th bit in KPDR */
+ reg_val = __raw_readw(KPDR);
+ reg_val &= ~(1 << (8 + col));
+ __raw_writew(reg_val, KPDR);
+
+ /* Delay added to avoid propagating the 0 from column to row
+ * when scanning. */
+
+ udelay(5);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ /* Read row input */
+ reg_val = __raw_readw(KPDR);
+ for (row = 0; row < kpp_dev.kpp_rows; row++) { /* sample row */
+ if (TEST_BIT(reg_val, row) == 0) {
+ cur_rcmap[row] = BITSET(cur_rcmap[row], col);
+ keycnt++;
+ }
+ }
+ }
+
+ /*
+ * 8. Return all columns to 0 in preparation for standby mode.
+ * 9. Clear KPKD and KPKR status bit(s) by writing to a .1.,
+ * set the KPKR synchronizer chain by writing "1" to KRSS register,
+ * clear the KPKD synchronizer chain by writing "1" to KDSC register
+ */
+ reg_val = 0x00;
+ __raw_writew(reg_val, KPDR);
+ reg_val = __raw_readw(KPDR);
+ reg_val = __raw_readw(KPSR);
+ reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR | KBD_STAT_KRSS |
+ KBD_STAT_KDSC;
+ __raw_writew(reg_val, KPSR);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ /* Check key press status change */
+
+ /*
+ * prev_rcmap array will contain the previous status of the keypad
+ * matrix. cur_rcmap array will contains the present status of the
+ * keypad matrix. If a bit is set in the array, that (row, col) bit is
+ * pressed, else it is not pressed.
+ *
+ * XORing these two variables will give us the change in bit for
+ * particular row and column. If a bit is set in XOR output, then that
+ * (row, col) has a change of status from the previous state. From
+ * the diff variable the key press and key release of row and column
+ * are found out.
+ *
+ * If the key press is determined then scancode for key pressed
+ * can be generated using the following statement:
+ * scancode = ((row * 8) + col);
+ *
+ * If the key release is determined then scancode for key release
+ * can be generated using the following statement:
+ * scancode = ((row * 8) + col) + MXC_KEYRELEASE;
+ */
+ for (row = 0; row < kpp_dev.kpp_rows; row++) {
+ unsigned char diff;
+
+ /*
+ * Calculate the change in the keypad row status
+ */
+ diff = prev_rcmap[row] ^ cur_rcmap[row];
+
+ for (col = 0; col < kpp_dev.kpp_cols; col++) {
+ if ((diff >> col) & 0x1) {
+ /* There is a status change on col */
+ if ((prev_rcmap[row] & BITSET(0, col)) == 0) {
+ /*
+ * Previous state is 0, so now
+ * a key is pressed
+ */
+ scancode =
+ ((row * kpp_dev.kpp_cols) +
+ col);
+ KPress = 1;
+ kpp_dev.iKeyState = KStateUp;
+
+ KPP_PRINTF("Press (%d, %d) scan=%d "
+ "Kpress=%d\n",
+ row, col, scancode, KPress);
+ press_scancode[row][col] =
+ (short)scancode;
+ } else {
+ /*
+ * Previous state is not 0, so
+ * now a key is released
+ */
+ scancode =
+ (row * kpp_dev.kpp_cols) +
+ col + MXC_KEYRELEASE;
+ KPress = 0;
+ kpp_dev.iKeyState = KStateDown;
+
+ KPP_PRINTF
+ ("Release (%d, %d) scan=%d Kpress=%d\n",
+ row, col, scancode, KPress);
+ release_scancode[row][col] =
+ (short)scancode;
+ keycnt++;
+ }
+ }
+ }
+ }
+
+ return keycnt;
+}
+
+static int mxc_kpp_reset(void)
+{
+ unsigned short reg_val;
+ int i;
+
+ /*
+ * Stop scanning and wait for interrupt.
+ * Enable press interrupt and disable release interrupt.
+ */
+ __raw_writew(0x00FF, KPDR);
+ reg_val = __raw_readw(KPSR);
+ reg_val |= (KBD_STAT_KPKR | KBD_STAT_KPKD);
+ reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC;
+ __raw_writew(reg_val, KPSR);
+ reg_val |= KBD_STAT_KDIE;
+ reg_val &= ~KBD_STAT_KRIE;
+ __raw_writew(reg_val, KPSR);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ /*
+ * No more keys pressed... make sure unwanted key codes are
+ * not given upstairs
+ */
+ for (i = 0; i < kpp_dev.kpp_rows; i++) {
+ memset(press_scancode[i], -1,
+ sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols);
+ memset(release_scancode[i], -1,
+ sizeof(release_scancode[0][0]) *
+ kpp_dev.kpp_cols);
+ }
+
+ return 0;
+}
+
+int mxc_kpp_getc(struct kpp_key_info **key_info)
+{
+ int col, row;
+ int key_cnt;
+ unsigned short reg_val;
+ short scancode = 0;
+ int index = 0;
+ struct kpp_key_info *keyi;
+
+ reg_val = __raw_readw(KPSR);
+
+ if (reg_val & KBD_STAT_KPKD) {
+ /*
+ * Disable key press(KDIE status bit) interrupt
+ */
+ reg_val &= ~KBD_STAT_KDIE;
+ __raw_writew(reg_val, KPSR);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ key_cnt = mxc_kpp_scan_matrix();
+ } else {
+ return 0;
+ }
+
+ if (key_cnt <= 0)
+ return 0;
+
+ *key_info = keyi =
+ (struct kpp_key_info *)malloc
+ (sizeof(struct kpp_key_info) * key_cnt);
+
+ /*
+ * This switch case statement is the
+ * implementation of state machine of debounc
+ * logic for key press/release.
+ * The explaination of state machine is as
+ * follows:
+ *
+ * KStateUp State:
+ * This is in intial state of the state machine
+ * this state it checks for any key presses.
+ * The key press can be checked using the
+ * variable KPress. If KPress is set, then key
+ * press is identified and switches the to
+ * KStateFirstDown state for key press to
+ * debounce.
+ *
+ * KStateFirstDown:
+ * After debounce delay(10ms), if the KPress is
+ * still set then pass scancode generated to
+ * input device and change the state to
+ * KStateDown, else key press debounce is not
+ * satisfied so change the state to KStateUp.
+ *
+ * KStateDown:
+ * In this state it checks for any key release.
+ * If KPress variable is cleared, then key
+ * release is indicated and so, switch the
+ * state to KStateFirstUp else to state
+ * KStateDown.
+ *
+ * KStateFirstUp:
+ * After debounce delay(10ms), if the KPress is
+ * still reset then pass the key release
+ * scancode to input device and change
+ * the state to KStateUp else key release is
+ * not satisfied so change the state to
+ * KStateDown.
+ */
+
+ for (row = 0; row < kpp_dev.kpp_rows; row++) {
+ for (col = 0; col < kpp_dev.kpp_cols; col++) {
+ if ((press_scancode[row][col] != -1)) {
+ /* Still Down, so add scancode */
+ scancode =
+ press_scancode[row][col];
+
+ keyi[index].val = mxckpd_keycodes[scancode];
+ keyi[index++].evt = KDepress;
+
+ KPP_PRINTF("KStateFirstDown: scan=%d val=%d\n",
+ scancode, mxckpd_keycodes[scancode]);
+ if (index >= key_cnt)
+ goto key_detect;
+
+ kpp_dev.iKeyState = KStateDown;
+ press_scancode[row][col] = -1;
+ }
+ }
+ }
+
+ for (row = 0; row < kpp_dev.kpp_rows; row++) {
+ for (col = 0; col < kpp_dev.kpp_cols; col++) {
+ if ((release_scancode[row][col] != -1)) {
+ scancode =
+ release_scancode[row][col];
+ scancode =
+ scancode - MXC_KEYRELEASE;
+
+ keyi[index].val = mxckpd_keycodes[scancode];
+ keyi[index++].evt = KRelease;
+
+ KPP_PRINTF("KStateFirstUp: scan=%d val=%d\n",
+ scancode, mxckpd_keycodes[scancode]);
+ if (index >= key_cnt)
+ goto key_detect;
+
+ kpp_dev.iKeyState = KStateUp;
+ release_scancode[row][col] = -1;
+ }
+ }
+ }
+
+key_detect:
+ mxc_kpp_reset();
+ return key_cnt;
+}
+
+/*!
+ * This function is called to free the allocated memory for local arrays
+ */
+static void mxc_kpp_free_allocated(void)
+{
+ int i;
+
+ if (press_scancode) {
+ for (i = 0; i < kpp_dev.kpp_rows; i++) {
+ if (press_scancode[i])
+ free(press_scancode[i]);
+ }
+ free(press_scancode);
+ }
+
+ if (release_scancode) {
+ for (i = 0; i < kpp_dev.kpp_rows; i++) {
+ if (release_scancode[i])
+ free(release_scancode[i]);
+ }
+ free(release_scancode);
+ }
+
+ if (cur_rcmap)
+ free(cur_rcmap);
+
+ if (prev_rcmap)
+ free(prev_rcmap);
+}
+
+/*!
+ * This function is called during the driver binding process.
+ *
+ * @param pdev the device structure used to store device specific
+ * information that is used by the suspend, resume and remove
+ * functions.
+ *
+ * @return The function returns 0 on successful registration. Otherwise returns
+ * specific error code.
+ */
+int mxc_kpp_init(void)
+{
+ int i;
+ int retval;
+ unsigned int reg_val;
+
+ kpp_dev.kpp_cols = CONFIG_MXC_KPD_COLMAX;
+ kpp_dev.kpp_rows = CONFIG_MXC_KPD_ROWMAX;
+
+ /* clock and IOMUX configuration for keypad */
+ setup_mxc_kpd();
+
+ /* Configure keypad */
+
+ /* Enable number of rows in keypad (KPCR[7:0])
+ * Configure keypad columns as open-drain (KPCR[15:8])
+ *
+ * Configure the rows/cols in KPP
+ * LSB nibble in KPP is for 8 rows
+ * MSB nibble in KPP is for 8 cols
+ */
+ reg_val = __raw_readw(KPCR);
+ reg_val |= (1 << kpp_dev.kpp_rows) - 1; /* LSB */
+ reg_val |= ((1 << kpp_dev.kpp_cols) - 1) << 8; /* MSB */
+ __raw_writew(reg_val, KPCR);
+
+ /* Write 0's to KPDR[15:8] */
+ reg_val = __raw_readw(KPDR);
+ reg_val &= 0x00ff;
+ __raw_writew(reg_val, KPDR);
+
+ /* Configure columns as output,
+ * rows as input (KDDR[15:0]) */
+ reg_val = __raw_readw(KDDR);
+ reg_val |= 0xff00;
+ reg_val &= 0xff00;
+ __raw_writew(reg_val, KDDR);
+
+ /* Clear the KPKD Status Flag
+ * and Synchronizer chain. */
+ reg_val = __raw_readw(KPSR);
+ reg_val &= ~(KBD_STAT_KPKR | KBD_STAT_KPKD);
+ reg_val |= KBD_STAT_KPKD;
+ reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC;
+ __raw_writew(reg_val, KPSR);
+ /* Set the KDIE control bit, and clear the KRIE
+ * control bit (avoid false release events). */
+ reg_val |= KBD_STAT_KDIE;
+ reg_val &= ~KBD_STAT_KRIE;
+ __raw_writew(reg_val, KPSR);
+
+#ifdef KPP_DEBUG
+ mxc_kpp_dump_regs();
+#endif
+
+ mxckpd_keycodes = mxc_key_mapping;
+ mxckpd_keycodes_size = kpp_dev.kpp_cols * kpp_dev.kpp_rows;
+
+ if ((mxckpd_keycodes == (void *)0)
+ || (mxckpd_keycodes_size == 0)) {
+ retval = -ENODEV;
+ goto err;
+ }
+
+ /* allocate required memory */
+ press_scancode = (short **)malloc(kpp_dev.kpp_rows * sizeof(press_scancode[0]));
+ release_scancode = (short **)malloc(kpp_dev.kpp_rows * sizeof(release_scancode[0]));
+
+ if (!press_scancode || !release_scancode) {
+ retval = -ENOMEM;
+ goto err;
+ }
+
+ for (i = 0; i < kpp_dev.kpp_rows; i++) {
+ press_scancode[i] = (short *)malloc(kpp_dev.kpp_cols
+ * sizeof(press_scancode[0][0]));
+ release_scancode[i] =
+ (short *)malloc(kpp_dev.kpp_cols * sizeof(release_scancode[0][0]));
+
+ if (!press_scancode[i] || !release_scancode[i]) {
+ retval = -ENOMEM;
+ goto err;
+ }
+ }
+
+ cur_rcmap =
+ (unsigned short *)malloc(kpp_dev.kpp_rows * sizeof(cur_rcmap[0]));
+ prev_rcmap =
+ (unsigned short *)malloc(kpp_dev.kpp_rows * sizeof(prev_rcmap[0]));
+
+ if (!cur_rcmap || !prev_rcmap) {
+ retval = -ENOMEM;
+ goto err;
+ }
+
+ for (i = 0; i < kpp_dev.kpp_rows; i++) {
+ memset(press_scancode[i], -1,
+ sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols);
+ memset(release_scancode[i], -1,
+ sizeof(release_scancode[0][0]) * kpp_dev.kpp_cols);
+ }
+ memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0]));
+ memset(prev_rcmap, 0, kpp_dev.kpp_rows * sizeof(prev_rcmap[0]));
+
+ return 0;
+
+err:
+ mxc_kpp_free_allocated();
+ return retval;
+}