| /* |
| * 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/imx-common/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; |
| } |