18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Driver for the IMX keypad port. 48c2ecf20Sopenharmony_ci// Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com> 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/clk.h> 78c2ecf20Sopenharmony_ci#include <linux/delay.h> 88c2ecf20Sopenharmony_ci#include <linux/device.h> 98c2ecf20Sopenharmony_ci#include <linux/err.h> 108c2ecf20Sopenharmony_ci#include <linux/input/matrix_keypad.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci#include <linux/timer.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci * Keypad Controller registers (halfword) 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_ci#define KPCR 0x00 /* Keypad Control Register */ 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define KPSR 0x02 /* Keypad Status Register */ 278c2ecf20Sopenharmony_ci#define KBD_STAT_KPKD (0x1 << 0) /* Key Press Interrupt Status bit (w1c) */ 288c2ecf20Sopenharmony_ci#define KBD_STAT_KPKR (0x1 << 1) /* Key Release Interrupt Status bit (w1c) */ 298c2ecf20Sopenharmony_ci#define KBD_STAT_KDSC (0x1 << 2) /* Key Depress Synch Chain Status bit (w1c)*/ 308c2ecf20Sopenharmony_ci#define KBD_STAT_KRSS (0x1 << 3) /* Key Release Synch Status bit (w1c)*/ 318c2ecf20Sopenharmony_ci#define KBD_STAT_KDIE (0x1 << 8) /* Key Depress Interrupt Enable Status bit */ 328c2ecf20Sopenharmony_ci#define KBD_STAT_KRIE (0x1 << 9) /* Key Release Interrupt Enable */ 338c2ecf20Sopenharmony_ci#define KBD_STAT_KPPEN (0x1 << 10) /* Keypad Clock Enable */ 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define KDDR 0x04 /* Keypad Data Direction Register */ 368c2ecf20Sopenharmony_ci#define KPDR 0x06 /* Keypad Data Register */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define MAX_MATRIX_KEY_ROWS 8 398c2ecf20Sopenharmony_ci#define MAX_MATRIX_KEY_COLS 8 408c2ecf20Sopenharmony_ci#define MATRIX_ROW_SHIFT 3 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define MAX_MATRIX_KEY_NUM (MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS) 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct imx_keypad { 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci struct clk *clk; 478c2ecf20Sopenharmony_ci struct input_dev *input_dev; 488c2ecf20Sopenharmony_ci void __iomem *mmio_base; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci int irq; 518c2ecf20Sopenharmony_ci struct timer_list check_matrix_timer; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci /* 548c2ecf20Sopenharmony_ci * The matrix is stable only if no changes are detected after 558c2ecf20Sopenharmony_ci * IMX_KEYPAD_SCANS_FOR_STABILITY scans 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_ci#define IMX_KEYPAD_SCANS_FOR_STABILITY 3 588c2ecf20Sopenharmony_ci int stable_count; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci bool enabled; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* Masks for enabled rows/cols */ 638c2ecf20Sopenharmony_ci unsigned short rows_en_mask; 648c2ecf20Sopenharmony_ci unsigned short cols_en_mask; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci unsigned short keycodes[MAX_MATRIX_KEY_NUM]; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci /* 698c2ecf20Sopenharmony_ci * Matrix states: 708c2ecf20Sopenharmony_ci * -stable: achieved after a complete debounce process. 718c2ecf20Sopenharmony_ci * -unstable: used in the debouncing process. 728c2ecf20Sopenharmony_ci */ 738c2ecf20Sopenharmony_ci unsigned short matrix_stable_state[MAX_MATRIX_KEY_COLS]; 748c2ecf20Sopenharmony_ci unsigned short matrix_unstable_state[MAX_MATRIX_KEY_COLS]; 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci/* Scan the matrix and return the new state in *matrix_volatile_state. */ 788c2ecf20Sopenharmony_cistatic void imx_keypad_scan_matrix(struct imx_keypad *keypad, 798c2ecf20Sopenharmony_ci unsigned short *matrix_volatile_state) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci int col; 828c2ecf20Sopenharmony_ci unsigned short reg_val; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) { 858c2ecf20Sopenharmony_ci if ((keypad->cols_en_mask & (1 << col)) == 0) 868c2ecf20Sopenharmony_ci continue; 878c2ecf20Sopenharmony_ci /* 888c2ecf20Sopenharmony_ci * Discharge keypad capacitance: 898c2ecf20Sopenharmony_ci * 2. write 1s on column data. 908c2ecf20Sopenharmony_ci * 3. configure columns as totem-pole to discharge capacitance. 918c2ecf20Sopenharmony_ci * 4. configure columns as open-drain. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPDR); 948c2ecf20Sopenharmony_ci reg_val |= 0xff00; 958c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPDR); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPCR); 988c2ecf20Sopenharmony_ci reg_val &= ~((keypad->cols_en_mask & 0xff) << 8); 998c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPCR); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci udelay(2); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPCR); 1048c2ecf20Sopenharmony_ci reg_val |= (keypad->cols_en_mask & 0xff) << 8; 1058c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPCR); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* 1088c2ecf20Sopenharmony_ci * 5. Write a single column to 0, others to 1. 1098c2ecf20Sopenharmony_ci * 6. Sample row inputs and save data. 1108c2ecf20Sopenharmony_ci * 7. Repeat steps 2 - 6 for remaining columns. 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPDR); 1138c2ecf20Sopenharmony_ci reg_val &= ~(1 << (8 + col)); 1148c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPDR); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci /* 1178c2ecf20Sopenharmony_ci * Delay added to avoid propagating the 0 from column to row 1188c2ecf20Sopenharmony_ci * when scanning. 1198c2ecf20Sopenharmony_ci */ 1208c2ecf20Sopenharmony_ci udelay(5); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci /* 1238c2ecf20Sopenharmony_ci * 1s in matrix_volatile_state[col] means key pressures 1248c2ecf20Sopenharmony_ci * throw data from non enabled rows. 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPDR); 1278c2ecf20Sopenharmony_ci matrix_volatile_state[col] = (~reg_val) & keypad->rows_en_mask; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* 1318c2ecf20Sopenharmony_ci * Return in standby mode: 1328c2ecf20Sopenharmony_ci * 9. write 0s to columns 1338c2ecf20Sopenharmony_ci */ 1348c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPDR); 1358c2ecf20Sopenharmony_ci reg_val &= 0x00ff; 1368c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPDR); 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/* 1408c2ecf20Sopenharmony_ci * Compare the new matrix state (volatile) with the stable one stored in 1418c2ecf20Sopenharmony_ci * keypad->matrix_stable_state and fire events if changes are detected. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_cistatic void imx_keypad_fire_events(struct imx_keypad *keypad, 1448c2ecf20Sopenharmony_ci unsigned short *matrix_volatile_state) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct input_dev *input_dev = keypad->input_dev; 1478c2ecf20Sopenharmony_ci int row, col; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) { 1508c2ecf20Sopenharmony_ci unsigned short bits_changed; 1518c2ecf20Sopenharmony_ci int code; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if ((keypad->cols_en_mask & (1 << col)) == 0) 1548c2ecf20Sopenharmony_ci continue; /* Column is not enabled */ 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci bits_changed = keypad->matrix_stable_state[col] ^ 1578c2ecf20Sopenharmony_ci matrix_volatile_state[col]; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (bits_changed == 0) 1608c2ecf20Sopenharmony_ci continue; /* Column does not contain changes */ 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) { 1638c2ecf20Sopenharmony_ci if ((keypad->rows_en_mask & (1 << row)) == 0) 1648c2ecf20Sopenharmony_ci continue; /* Row is not enabled */ 1658c2ecf20Sopenharmony_ci if ((bits_changed & (1 << row)) == 0) 1668c2ecf20Sopenharmony_ci continue; /* Row does not contain changes */ 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT); 1698c2ecf20Sopenharmony_ci input_event(input_dev, EV_MSC, MSC_SCAN, code); 1708c2ecf20Sopenharmony_ci input_report_key(input_dev, keypad->keycodes[code], 1718c2ecf20Sopenharmony_ci matrix_volatile_state[col] & (1 << row)); 1728c2ecf20Sopenharmony_ci dev_dbg(&input_dev->dev, "Event code: %d, val: %d", 1738c2ecf20Sopenharmony_ci keypad->keycodes[code], 1748c2ecf20Sopenharmony_ci matrix_volatile_state[col] & (1 << row)); 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci input_sync(input_dev); 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci/* 1818c2ecf20Sopenharmony_ci * imx_keypad_check_for_events is the timer handler. 1828c2ecf20Sopenharmony_ci */ 1838c2ecf20Sopenharmony_cistatic void imx_keypad_check_for_events(struct timer_list *t) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct imx_keypad *keypad = from_timer(keypad, t, check_matrix_timer); 1868c2ecf20Sopenharmony_ci unsigned short matrix_volatile_state[MAX_MATRIX_KEY_COLS]; 1878c2ecf20Sopenharmony_ci unsigned short reg_val; 1888c2ecf20Sopenharmony_ci bool state_changed, is_zero_matrix; 1898c2ecf20Sopenharmony_ci int i; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci memset(matrix_volatile_state, 0, sizeof(matrix_volatile_state)); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci imx_keypad_scan_matrix(keypad, matrix_volatile_state); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci state_changed = false; 1968c2ecf20Sopenharmony_ci for (i = 0; i < MAX_MATRIX_KEY_COLS; i++) { 1978c2ecf20Sopenharmony_ci if ((keypad->cols_en_mask & (1 << i)) == 0) 1988c2ecf20Sopenharmony_ci continue; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (keypad->matrix_unstable_state[i] ^ matrix_volatile_state[i]) { 2018c2ecf20Sopenharmony_ci state_changed = true; 2028c2ecf20Sopenharmony_ci break; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* 2078c2ecf20Sopenharmony_ci * If the matrix state is changed from the previous scan 2088c2ecf20Sopenharmony_ci * (Re)Begin the debouncing process, saving the new state in 2098c2ecf20Sopenharmony_ci * keypad->matrix_unstable_state. 2108c2ecf20Sopenharmony_ci * else 2118c2ecf20Sopenharmony_ci * Increase the count of number of scans with a stable state. 2128c2ecf20Sopenharmony_ci */ 2138c2ecf20Sopenharmony_ci if (state_changed) { 2148c2ecf20Sopenharmony_ci memcpy(keypad->matrix_unstable_state, matrix_volatile_state, 2158c2ecf20Sopenharmony_ci sizeof(matrix_volatile_state)); 2168c2ecf20Sopenharmony_ci keypad->stable_count = 0; 2178c2ecf20Sopenharmony_ci } else 2188c2ecf20Sopenharmony_ci keypad->stable_count++; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci /* 2218c2ecf20Sopenharmony_ci * If the matrix is not as stable as we want reschedule scan 2228c2ecf20Sopenharmony_ci * in the near future. 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_ci if (keypad->stable_count < IMX_KEYPAD_SCANS_FOR_STABILITY) { 2258c2ecf20Sopenharmony_ci mod_timer(&keypad->check_matrix_timer, 2268c2ecf20Sopenharmony_ci jiffies + msecs_to_jiffies(10)); 2278c2ecf20Sopenharmony_ci return; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* 2318c2ecf20Sopenharmony_ci * If the matrix state is stable, fire the events and save the new 2328c2ecf20Sopenharmony_ci * stable state. Note, if the matrix is kept stable for longer 2338c2ecf20Sopenharmony_ci * (keypad->stable_count > IMX_KEYPAD_SCANS_FOR_STABILITY) all 2348c2ecf20Sopenharmony_ci * events have already been generated. 2358c2ecf20Sopenharmony_ci */ 2368c2ecf20Sopenharmony_ci if (keypad->stable_count == IMX_KEYPAD_SCANS_FOR_STABILITY) { 2378c2ecf20Sopenharmony_ci imx_keypad_fire_events(keypad, matrix_volatile_state); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci memcpy(keypad->matrix_stable_state, matrix_volatile_state, 2408c2ecf20Sopenharmony_ci sizeof(matrix_volatile_state)); 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci is_zero_matrix = true; 2448c2ecf20Sopenharmony_ci for (i = 0; i < MAX_MATRIX_KEY_COLS; i++) { 2458c2ecf20Sopenharmony_ci if (matrix_volatile_state[i] != 0) { 2468c2ecf20Sopenharmony_ci is_zero_matrix = false; 2478c2ecf20Sopenharmony_ci break; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (is_zero_matrix) { 2538c2ecf20Sopenharmony_ci /* 2548c2ecf20Sopenharmony_ci * All keys have been released. Enable only the KDI 2558c2ecf20Sopenharmony_ci * interrupt for future key presses (clear the KDI 2568c2ecf20Sopenharmony_ci * status bit and its sync chain before that). 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 2598c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC; 2608c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 2638c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KDIE; 2648c2ecf20Sopenharmony_ci reg_val &= ~KBD_STAT_KRIE; 2658c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 2668c2ecf20Sopenharmony_ci } else { 2678c2ecf20Sopenharmony_ci /* 2688c2ecf20Sopenharmony_ci * Some keys are still pressed. Schedule a rescan in 2698c2ecf20Sopenharmony_ci * attempt to detect multiple key presses and enable 2708c2ecf20Sopenharmony_ci * the KRI interrupt to react quickly to key release 2718c2ecf20Sopenharmony_ci * event. 2728c2ecf20Sopenharmony_ci */ 2738c2ecf20Sopenharmony_ci mod_timer(&keypad->check_matrix_timer, 2748c2ecf20Sopenharmony_ci jiffies + msecs_to_jiffies(60)); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 2778c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KPKR | KBD_STAT_KRSS; 2788c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 2818c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KRIE; 2828c2ecf20Sopenharmony_ci reg_val &= ~KBD_STAT_KDIE; 2838c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistatic irqreturn_t imx_keypad_irq_handler(int irq, void *dev_id) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct imx_keypad *keypad = dev_id; 2908c2ecf20Sopenharmony_ci unsigned short reg_val; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci /* Disable both interrupt types */ 2958c2ecf20Sopenharmony_ci reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE); 2968c2ecf20Sopenharmony_ci /* Clear interrupts status bits */ 2978c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD; 2988c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci if (keypad->enabled) { 3018c2ecf20Sopenharmony_ci /* The matrix is supposed to be changed */ 3028c2ecf20Sopenharmony_ci keypad->stable_count = 0; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci /* Schedule the scanning procedure near in the future */ 3058c2ecf20Sopenharmony_ci mod_timer(&keypad->check_matrix_timer, 3068c2ecf20Sopenharmony_ci jiffies + msecs_to_jiffies(2)); 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3108c2ecf20Sopenharmony_ci} 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_cistatic void imx_keypad_config(struct imx_keypad *keypad) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci unsigned short reg_val; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci /* 3178c2ecf20Sopenharmony_ci * Include enabled rows in interrupt generation (KPCR[7:0]) 3188c2ecf20Sopenharmony_ci * Configure keypad columns as open-drain (KPCR[15:8]) 3198c2ecf20Sopenharmony_ci */ 3208c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPCR); 3218c2ecf20Sopenharmony_ci reg_val |= keypad->rows_en_mask & 0xff; /* rows */ 3228c2ecf20Sopenharmony_ci reg_val |= (keypad->cols_en_mask & 0xff) << 8; /* cols */ 3238c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPCR); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci /* Write 0's to KPDR[15:8] (Colums) */ 3268c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPDR); 3278c2ecf20Sopenharmony_ci reg_val &= 0x00ff; 3288c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPDR); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* Configure columns as output, rows as input (KDDR[15:0]) */ 3318c2ecf20Sopenharmony_ci writew(0xff00, keypad->mmio_base + KDDR); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci /* 3348c2ecf20Sopenharmony_ci * Clear Key Depress and Key Release status bit. 3358c2ecf20Sopenharmony_ci * Clear both synchronizer chain. 3368c2ecf20Sopenharmony_ci */ 3378c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 3388c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD | 3398c2ecf20Sopenharmony_ci KBD_STAT_KDSC | KBD_STAT_KRSS; 3408c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci /* Enable KDI and disable KRI (avoid false release events). */ 3438c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KDIE; 3448c2ecf20Sopenharmony_ci reg_val &= ~KBD_STAT_KRIE; 3458c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic void imx_keypad_inhibit(struct imx_keypad *keypad) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci unsigned short reg_val; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci /* Inhibit KDI and KRI interrupts. */ 3538c2ecf20Sopenharmony_ci reg_val = readw(keypad->mmio_base + KPSR); 3548c2ecf20Sopenharmony_ci reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE); 3558c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD; 3568c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPSR); 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci /* Colums as open drain and disable all rows */ 3598c2ecf20Sopenharmony_ci reg_val = (keypad->cols_en_mask & 0xff) << 8; 3608c2ecf20Sopenharmony_ci writew(reg_val, keypad->mmio_base + KPCR); 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_cistatic void imx_keypad_close(struct input_dev *dev) 3648c2ecf20Sopenharmony_ci{ 3658c2ecf20Sopenharmony_ci struct imx_keypad *keypad = input_get_drvdata(dev); 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci dev_dbg(&dev->dev, ">%s\n", __func__); 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci /* Mark keypad as being inactive */ 3708c2ecf20Sopenharmony_ci keypad->enabled = false; 3718c2ecf20Sopenharmony_ci synchronize_irq(keypad->irq); 3728c2ecf20Sopenharmony_ci del_timer_sync(&keypad->check_matrix_timer); 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci imx_keypad_inhibit(keypad); 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci /* Disable clock unit */ 3778c2ecf20Sopenharmony_ci clk_disable_unprepare(keypad->clk); 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_cistatic int imx_keypad_open(struct input_dev *dev) 3818c2ecf20Sopenharmony_ci{ 3828c2ecf20Sopenharmony_ci struct imx_keypad *keypad = input_get_drvdata(dev); 3838c2ecf20Sopenharmony_ci int error; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci dev_dbg(&dev->dev, ">%s\n", __func__); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci /* Enable the kpp clock */ 3888c2ecf20Sopenharmony_ci error = clk_prepare_enable(keypad->clk); 3898c2ecf20Sopenharmony_ci if (error) 3908c2ecf20Sopenharmony_ci return error; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci /* We became active from now */ 3938c2ecf20Sopenharmony_ci keypad->enabled = true; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci imx_keypad_config(keypad); 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci /* Sanity control, not all the rows must be actived now. */ 3988c2ecf20Sopenharmony_ci if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) { 3998c2ecf20Sopenharmony_ci dev_err(&dev->dev, 4008c2ecf20Sopenharmony_ci "too many keys pressed, control pins initialisation\n"); 4018c2ecf20Sopenharmony_ci goto open_err; 4028c2ecf20Sopenharmony_ci } 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci return 0; 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ciopen_err: 4078c2ecf20Sopenharmony_ci imx_keypad_close(dev); 4088c2ecf20Sopenharmony_ci return -EIO; 4098c2ecf20Sopenharmony_ci} 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 4128c2ecf20Sopenharmony_cistatic const struct of_device_id imx_keypad_of_match[] = { 4138c2ecf20Sopenharmony_ci { .compatible = "fsl,imx21-kpp", }, 4148c2ecf20Sopenharmony_ci { /* sentinel */ } 4158c2ecf20Sopenharmony_ci}; 4168c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_keypad_of_match); 4178c2ecf20Sopenharmony_ci#endif 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_cistatic int imx_keypad_probe(struct platform_device *pdev) 4208c2ecf20Sopenharmony_ci{ 4218c2ecf20Sopenharmony_ci const struct matrix_keymap_data *keymap_data = 4228c2ecf20Sopenharmony_ci dev_get_platdata(&pdev->dev); 4238c2ecf20Sopenharmony_ci struct imx_keypad *keypad; 4248c2ecf20Sopenharmony_ci struct input_dev *input_dev; 4258c2ecf20Sopenharmony_ci int irq, error, i, row, col; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (!keymap_data && !pdev->dev.of_node) { 4288c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "no keymap defined\n"); 4298c2ecf20Sopenharmony_ci return -EINVAL; 4308c2ecf20Sopenharmony_ci } 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 4338c2ecf20Sopenharmony_ci if (irq < 0) 4348c2ecf20Sopenharmony_ci return irq; 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci input_dev = devm_input_allocate_device(&pdev->dev); 4378c2ecf20Sopenharmony_ci if (!input_dev) { 4388c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to allocate the input device\n"); 4398c2ecf20Sopenharmony_ci return -ENOMEM; 4408c2ecf20Sopenharmony_ci } 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL); 4438c2ecf20Sopenharmony_ci if (!keypad) { 4448c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "not enough memory for driver data\n"); 4458c2ecf20Sopenharmony_ci return -ENOMEM; 4468c2ecf20Sopenharmony_ci } 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci keypad->input_dev = input_dev; 4498c2ecf20Sopenharmony_ci keypad->irq = irq; 4508c2ecf20Sopenharmony_ci keypad->stable_count = 0; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci timer_setup(&keypad->check_matrix_timer, 4538c2ecf20Sopenharmony_ci imx_keypad_check_for_events, 0); 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci keypad->mmio_base = devm_platform_ioremap_resource(pdev, 0); 4568c2ecf20Sopenharmony_ci if (IS_ERR(keypad->mmio_base)) 4578c2ecf20Sopenharmony_ci return PTR_ERR(keypad->mmio_base); 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci keypad->clk = devm_clk_get(&pdev->dev, NULL); 4608c2ecf20Sopenharmony_ci if (IS_ERR(keypad->clk)) { 4618c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get keypad clock\n"); 4628c2ecf20Sopenharmony_ci return PTR_ERR(keypad->clk); 4638c2ecf20Sopenharmony_ci } 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci /* Init the Input device */ 4668c2ecf20Sopenharmony_ci input_dev->name = pdev->name; 4678c2ecf20Sopenharmony_ci input_dev->id.bustype = BUS_HOST; 4688c2ecf20Sopenharmony_ci input_dev->dev.parent = &pdev->dev; 4698c2ecf20Sopenharmony_ci input_dev->open = imx_keypad_open; 4708c2ecf20Sopenharmony_ci input_dev->close = imx_keypad_close; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci error = matrix_keypad_build_keymap(keymap_data, NULL, 4738c2ecf20Sopenharmony_ci MAX_MATRIX_KEY_ROWS, 4748c2ecf20Sopenharmony_ci MAX_MATRIX_KEY_COLS, 4758c2ecf20Sopenharmony_ci keypad->keycodes, input_dev); 4768c2ecf20Sopenharmony_ci if (error) { 4778c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to build keymap\n"); 4788c2ecf20Sopenharmony_ci return error; 4798c2ecf20Sopenharmony_ci } 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci /* Search for rows and cols enabled */ 4828c2ecf20Sopenharmony_ci for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) { 4838c2ecf20Sopenharmony_ci for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) { 4848c2ecf20Sopenharmony_ci i = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT); 4858c2ecf20Sopenharmony_ci if (keypad->keycodes[i] != KEY_RESERVED) { 4868c2ecf20Sopenharmony_ci keypad->rows_en_mask |= 1 << row; 4878c2ecf20Sopenharmony_ci keypad->cols_en_mask |= 1 << col; 4888c2ecf20Sopenharmony_ci } 4898c2ecf20Sopenharmony_ci } 4908c2ecf20Sopenharmony_ci } 4918c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask); 4928c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask); 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci __set_bit(EV_REP, input_dev->evbit); 4958c2ecf20Sopenharmony_ci input_set_capability(input_dev, EV_MSC, MSC_SCAN); 4968c2ecf20Sopenharmony_ci input_set_drvdata(input_dev, keypad); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci /* Ensure that the keypad will stay dormant until opened */ 4998c2ecf20Sopenharmony_ci error = clk_prepare_enable(keypad->clk); 5008c2ecf20Sopenharmony_ci if (error) 5018c2ecf20Sopenharmony_ci return error; 5028c2ecf20Sopenharmony_ci imx_keypad_inhibit(keypad); 5038c2ecf20Sopenharmony_ci clk_disable_unprepare(keypad->clk); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci error = devm_request_irq(&pdev->dev, irq, imx_keypad_irq_handler, 0, 5068c2ecf20Sopenharmony_ci pdev->name, keypad); 5078c2ecf20Sopenharmony_ci if (error) { 5088c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to request IRQ\n"); 5098c2ecf20Sopenharmony_ci return error; 5108c2ecf20Sopenharmony_ci } 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci /* Register the input device */ 5138c2ecf20Sopenharmony_ci error = input_register_device(input_dev); 5148c2ecf20Sopenharmony_ci if (error) { 5158c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register input device\n"); 5168c2ecf20Sopenharmony_ci return error; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, keypad); 5208c2ecf20Sopenharmony_ci device_init_wakeup(&pdev->dev, 1); 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci return 0; 5238c2ecf20Sopenharmony_ci} 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_cistatic int __maybe_unused imx_kbd_noirq_suspend(struct device *dev) 5268c2ecf20Sopenharmony_ci{ 5278c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 5288c2ecf20Sopenharmony_ci struct imx_keypad *kbd = platform_get_drvdata(pdev); 5298c2ecf20Sopenharmony_ci struct input_dev *input_dev = kbd->input_dev; 5308c2ecf20Sopenharmony_ci unsigned short reg_val = readw(kbd->mmio_base + KPSR); 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci /* imx kbd can wake up system even clock is disabled */ 5338c2ecf20Sopenharmony_ci mutex_lock(&input_dev->mutex); 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci if (input_dev->users) 5368c2ecf20Sopenharmony_ci clk_disable_unprepare(kbd->clk); 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci mutex_unlock(&input_dev->mutex); 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci if (device_may_wakeup(&pdev->dev)) { 5418c2ecf20Sopenharmony_ci if (reg_val & KBD_STAT_KPKD) 5428c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KRIE; 5438c2ecf20Sopenharmony_ci if (reg_val & KBD_STAT_KPKR) 5448c2ecf20Sopenharmony_ci reg_val |= KBD_STAT_KDIE; 5458c2ecf20Sopenharmony_ci writew(reg_val, kbd->mmio_base + KPSR); 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci enable_irq_wake(kbd->irq); 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci return 0; 5518c2ecf20Sopenharmony_ci} 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_cistatic int __maybe_unused imx_kbd_noirq_resume(struct device *dev) 5548c2ecf20Sopenharmony_ci{ 5558c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 5568c2ecf20Sopenharmony_ci struct imx_keypad *kbd = platform_get_drvdata(pdev); 5578c2ecf20Sopenharmony_ci struct input_dev *input_dev = kbd->input_dev; 5588c2ecf20Sopenharmony_ci int ret = 0; 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_ci if (device_may_wakeup(&pdev->dev)) 5618c2ecf20Sopenharmony_ci disable_irq_wake(kbd->irq); 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci mutex_lock(&input_dev->mutex); 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_ci if (input_dev->users) { 5668c2ecf20Sopenharmony_ci ret = clk_prepare_enable(kbd->clk); 5678c2ecf20Sopenharmony_ci if (ret) 5688c2ecf20Sopenharmony_ci goto err_clk; 5698c2ecf20Sopenharmony_ci } 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_cierr_clk: 5728c2ecf20Sopenharmony_ci mutex_unlock(&input_dev->mutex); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci return ret; 5758c2ecf20Sopenharmony_ci} 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_cistatic const struct dev_pm_ops imx_kbd_pm_ops = { 5788c2ecf20Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_kbd_noirq_suspend, imx_kbd_noirq_resume) 5798c2ecf20Sopenharmony_ci}; 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_cistatic struct platform_driver imx_keypad_driver = { 5828c2ecf20Sopenharmony_ci .driver = { 5838c2ecf20Sopenharmony_ci .name = "imx-keypad", 5848c2ecf20Sopenharmony_ci .pm = &imx_kbd_pm_ops, 5858c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(imx_keypad_of_match), 5868c2ecf20Sopenharmony_ci }, 5878c2ecf20Sopenharmony_ci .probe = imx_keypad_probe, 5888c2ecf20Sopenharmony_ci}; 5898c2ecf20Sopenharmony_cimodule_platform_driver(imx_keypad_driver); 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>"); 5928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("IMX Keypad Port Driver"); 5938c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 5948c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:imx-keypad"); 595