162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Helpers for matrix keyboard bindings 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2012 Google, Inc 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: 862306a36Sopenharmony_ci * Olof Johansson <olof@lixom.net> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/device.h> 1262306a36Sopenharmony_ci#include <linux/export.h> 1362306a36Sopenharmony_ci#include <linux/gfp.h> 1462306a36Sopenharmony_ci#include <linux/input.h> 1562306a36Sopenharmony_ci#include <linux/input/matrix_keypad.h> 1662306a36Sopenharmony_ci#include <linux/kernel.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/property.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/types.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic bool matrix_keypad_map_key(struct input_dev *input_dev, 2362306a36Sopenharmony_ci unsigned int rows, unsigned int cols, 2462306a36Sopenharmony_ci unsigned int row_shift, unsigned int key) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci unsigned short *keymap = input_dev->keycode; 2762306a36Sopenharmony_ci unsigned int row = KEY_ROW(key); 2862306a36Sopenharmony_ci unsigned int col = KEY_COL(key); 2962306a36Sopenharmony_ci unsigned short code = KEY_VAL(key); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci if (row >= rows || col >= cols) { 3262306a36Sopenharmony_ci dev_err(input_dev->dev.parent, 3362306a36Sopenharmony_ci "%s: invalid keymap entry 0x%x (row: %d, col: %d, rows: %d, cols: %d)\n", 3462306a36Sopenharmony_ci __func__, key, row, col, rows, cols); 3562306a36Sopenharmony_ci return false; 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci keymap[MATRIX_SCAN_CODE(row, col, row_shift)] = code; 3962306a36Sopenharmony_ci __set_bit(code, input_dev->keybit); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci return true; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci/** 4562306a36Sopenharmony_ci * matrix_keypad_parse_properties() - Read properties of matrix keypad 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * @dev: Device containing properties 4862306a36Sopenharmony_ci * @rows: Returns number of matrix rows 4962306a36Sopenharmony_ci * @cols: Returns number of matrix columns 5062306a36Sopenharmony_ci * @return 0 if OK, <0 on error 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_ciint matrix_keypad_parse_properties(struct device *dev, 5362306a36Sopenharmony_ci unsigned int *rows, unsigned int *cols) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci *rows = *cols = 0; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci device_property_read_u32(dev, "keypad,num-rows", rows); 5862306a36Sopenharmony_ci device_property_read_u32(dev, "keypad,num-columns", cols); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (!*rows || !*cols) { 6162306a36Sopenharmony_ci dev_err(dev, "number of keypad rows/columns not specified\n"); 6262306a36Sopenharmony_ci return -EINVAL; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return 0; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(matrix_keypad_parse_properties); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic int matrix_keypad_parse_keymap(const char *propname, 7062306a36Sopenharmony_ci unsigned int rows, unsigned int cols, 7162306a36Sopenharmony_ci struct input_dev *input_dev) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct device *dev = input_dev->dev.parent; 7462306a36Sopenharmony_ci unsigned int row_shift = get_count_order(cols); 7562306a36Sopenharmony_ci unsigned int max_keys = rows << row_shift; 7662306a36Sopenharmony_ci u32 *keys; 7762306a36Sopenharmony_ci int i; 7862306a36Sopenharmony_ci int size; 7962306a36Sopenharmony_ci int retval; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!propname) 8262306a36Sopenharmony_ci propname = "linux,keymap"; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci size = device_property_count_u32(dev, propname); 8562306a36Sopenharmony_ci if (size <= 0) { 8662306a36Sopenharmony_ci dev_err(dev, "missing or malformed property %s: %d\n", 8762306a36Sopenharmony_ci propname, size); 8862306a36Sopenharmony_ci return size < 0 ? size : -EINVAL; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (size > max_keys) { 9262306a36Sopenharmony_ci dev_err(dev, "%s size overflow (%d vs max %u)\n", 9362306a36Sopenharmony_ci propname, size, max_keys); 9462306a36Sopenharmony_ci return -EINVAL; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci keys = kmalloc_array(size, sizeof(u32), GFP_KERNEL); 9862306a36Sopenharmony_ci if (!keys) 9962306a36Sopenharmony_ci return -ENOMEM; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci retval = device_property_read_u32_array(dev, propname, keys, size); 10262306a36Sopenharmony_ci if (retval) { 10362306a36Sopenharmony_ci dev_err(dev, "failed to read %s property: %d\n", 10462306a36Sopenharmony_ci propname, retval); 10562306a36Sopenharmony_ci goto out; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci for (i = 0; i < size; i++) { 10962306a36Sopenharmony_ci if (!matrix_keypad_map_key(input_dev, rows, cols, 11062306a36Sopenharmony_ci row_shift, keys[i])) { 11162306a36Sopenharmony_ci retval = -EINVAL; 11262306a36Sopenharmony_ci goto out; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci retval = 0; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ciout: 11962306a36Sopenharmony_ci kfree(keys); 12062306a36Sopenharmony_ci return retval; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * matrix_keypad_build_keymap - convert platform keymap into matrix keymap 12562306a36Sopenharmony_ci * @keymap_data: keymap supplied by the platform code 12662306a36Sopenharmony_ci * @keymap_name: name of device tree property containing keymap (if device 12762306a36Sopenharmony_ci * tree support is enabled). 12862306a36Sopenharmony_ci * @rows: number of rows in target keymap array 12962306a36Sopenharmony_ci * @cols: number of cols in target keymap array 13062306a36Sopenharmony_ci * @keymap: expanded version of keymap that is suitable for use by 13162306a36Sopenharmony_ci * matrix keyboard driver 13262306a36Sopenharmony_ci * @input_dev: input devices for which we are setting up the keymap 13362306a36Sopenharmony_ci * 13462306a36Sopenharmony_ci * This function converts platform keymap (encoded with KEY() macro) into 13562306a36Sopenharmony_ci * an array of keycodes that is suitable for using in a standard matrix 13662306a36Sopenharmony_ci * keyboard driver that uses row and col as indices. 13762306a36Sopenharmony_ci * 13862306a36Sopenharmony_ci * If @keymap_data is not supplied and device tree support is enabled 13962306a36Sopenharmony_ci * it will attempt load the keymap from property specified by @keymap_name 14062306a36Sopenharmony_ci * argument (or "linux,keymap" if @keymap_name is %NULL). 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * If @keymap is %NULL the function will automatically allocate managed 14362306a36Sopenharmony_ci * block of memory to store the keymap. This memory will be associated with 14462306a36Sopenharmony_ci * the parent device and automatically freed when device unbinds from the 14562306a36Sopenharmony_ci * driver. 14662306a36Sopenharmony_ci * 14762306a36Sopenharmony_ci * Callers are expected to set up input_dev->dev.parent before calling this 14862306a36Sopenharmony_ci * function. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_ciint matrix_keypad_build_keymap(const struct matrix_keymap_data *keymap_data, 15162306a36Sopenharmony_ci const char *keymap_name, 15262306a36Sopenharmony_ci unsigned int rows, unsigned int cols, 15362306a36Sopenharmony_ci unsigned short *keymap, 15462306a36Sopenharmony_ci struct input_dev *input_dev) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci unsigned int row_shift = get_count_order(cols); 15762306a36Sopenharmony_ci size_t max_keys = rows << row_shift; 15862306a36Sopenharmony_ci int i; 15962306a36Sopenharmony_ci int error; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (WARN_ON(!input_dev->dev.parent)) 16262306a36Sopenharmony_ci return -EINVAL; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (!keymap) { 16562306a36Sopenharmony_ci keymap = devm_kcalloc(input_dev->dev.parent, 16662306a36Sopenharmony_ci max_keys, sizeof(*keymap), 16762306a36Sopenharmony_ci GFP_KERNEL); 16862306a36Sopenharmony_ci if (!keymap) { 16962306a36Sopenharmony_ci dev_err(input_dev->dev.parent, 17062306a36Sopenharmony_ci "Unable to allocate memory for keymap"); 17162306a36Sopenharmony_ci return -ENOMEM; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci input_dev->keycode = keymap; 17662306a36Sopenharmony_ci input_dev->keycodesize = sizeof(*keymap); 17762306a36Sopenharmony_ci input_dev->keycodemax = max_keys; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci __set_bit(EV_KEY, input_dev->evbit); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (keymap_data) { 18262306a36Sopenharmony_ci for (i = 0; i < keymap_data->keymap_size; i++) { 18362306a36Sopenharmony_ci unsigned int key = keymap_data->keymap[i]; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (!matrix_keypad_map_key(input_dev, rows, cols, 18662306a36Sopenharmony_ci row_shift, key)) 18762306a36Sopenharmony_ci return -EINVAL; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci } else { 19062306a36Sopenharmony_ci error = matrix_keypad_parse_keymap(keymap_name, rows, cols, 19162306a36Sopenharmony_ci input_dev); 19262306a36Sopenharmony_ci if (error) 19362306a36Sopenharmony_ci return error; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci __clear_bit(KEY_RESERVED, input_dev->keybit); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ciEXPORT_SYMBOL(matrix_keypad_build_keymap); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 203