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