summaryrefslogtreecommitdiffstats
path: root/drivers/input/key_matrix.c
blob: 8951e128ec7f4d4890744ea6bf601eaa2eab0a2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// SPDX-License-Identifier: GPL-2.0+
/*
 * Manage Keyboard Matrices
 *
 * Copyright (c) 2012 The Chromium OS Authors.
 * (C) Copyright 2004 DENX Software Engineering, Wolfgang Denk, wd@denx.de
 */

#include <common.h>
#include <dm.h>
#include <key_matrix.h>
#include <malloc.h>
#include <linux/input.h>

/**
 * Determine if the current keypress configuration can cause key ghosting
 *
 * We figure this out by seeing if we have two or more keys in the same
 * column, as well as two or more keys in the same row.
 *
 * @param config	Keyboard matrix config
 * @param keys		List of keys to check
 * @param valid		Number of valid keypresses to check
 * @return 0 if no ghosting is possible, 1 if it is
 */
static int has_ghosting(struct key_matrix *config, struct key_matrix_key *keys,
			int valid)
{
	int key_in_same_col = 0, key_in_same_row = 0;
	int i, j;

	if (!config->ghost_filter || valid < 3)
		return 0;

	for (i = 0; i < valid; i++) {
		/*
		 * Find 2 keys such that one key is in the same row
		 * and the other is in the same column as the i-th key.
		 */
		for (j = i + 1; j < valid; j++) {
			if (keys[j].col == keys[i].col)
				key_in_same_col = 1;
			if (keys[j].row == keys[i].row)
				key_in_same_row = 1;
		}
	}

	if (key_in_same_col && key_in_same_row)
		return 1;
	else
		return 0;
}

int key_matrix_decode(struct key_matrix *config, struct key_matrix_key keys[],
		      int num_keys, int keycode[], int max_keycodes)
{
	const u8 *keymap;
	int valid, upto;
	int pos;

	debug("%s: num_keys = %d\n", __func__, num_keys);
	keymap = config->plain_keycode;
	for (valid = upto = 0; upto < num_keys; upto++) {
		struct key_matrix_key *key = &keys[upto];

		debug("  valid=%d, row=%d, col=%d\n", key->valid, key->row,
		      key->col);
		if (!key->valid)
			continue;
		pos = key->row * config->num_cols + key->col;
		if (config->fn_keycode && pos == config->fn_pos)
			keymap = config->fn_keycode;

		/* Convert the (row, col) values into a keycode */
		if (valid < max_keycodes)
			keycode[valid++] = keymap[pos];
		debug("    keycode=%d\n", keymap[pos]);
	}

	/* For a ghost key config, ignore the keypresses for this iteration. */
	if (has_ghosting(config, keys, valid)) {
		valid = 0;
		debug("    ghosting detected!\n");
	}
	debug("  %d valid keycodes found\n", valid);

	return valid;
}

/**
 * Create a new keycode map from some provided data
 *
 * This decodes a keycode map in the format used by the fdt, which is one
 * word per entry, with the row, col and keycode encoded in that word.
 *
 * We create a (row x col) size byte array with each entry containing the
 * keycode for that (row, col). We also search for map_keycode and return
 * its position if found (this is used for finding the Fn key).
 *
 * @param config        Key matrix dimensions structure
 * @param data          Keycode data
 * @param len           Number of entries in keycode table
 * @param map_keycode   Key code to find in the map
 * @param pos           Returns position of map_keycode, if found, else -1
 * @return map  Pointer to allocated map
 */
static uchar *create_keymap(struct key_matrix *config, const u32 *data, int len,
			    int map_keycode, int *pos)
{
	uchar *map;

	if (pos)
		*pos = -1;
	map = (uchar *)calloc(1, config->key_count);
	if (!map) {
		debug("%s: failed to malloc %d bytes\n", __func__,
			config->key_count);
		return NULL;
	}

	for (; len >= sizeof(u32); data++, len -= 4) {
		u32 tmp = fdt32_to_cpu(*data);
		int key_code, row, col;
		int entry;

		row = (tmp >> 24) & 0xff;
		col = (tmp >> 16) & 0xff;
		key_code = tmp & 0xffff;
		entry = row * config->num_cols + col;
		map[entry] = key_code;
		debug("   map %d, %d: pos=%d, keycode=%d\n", row, col,
		      entry, key_code);
		if (pos && map_keycode == key_code)
			*pos = entry;
	}

	return map;
}

int key_matrix_decode_fdt(struct udevice *dev, struct key_matrix *config)
{
	const u32 *prop;
	int proplen;
	uchar *plain_keycode;

	prop = dev_read_prop(dev, "linux,keymap", &proplen);
	/* Basic keymap is required */
	if (!prop) {
		debug("%s: cannot find keycode-plain map\n", __func__);
		return -1;
	}

	plain_keycode = create_keymap(config, prop, proplen, KEY_FN,
				      &config->fn_pos);
	config->plain_keycode = plain_keycode;
	/* Conversion error -> fail */
	if (!config->plain_keycode)
		return -1;

	prop = dev_read_prop(dev, "linux,fn-keymap", &proplen);
	/* fn keymap is optional */
	if (!prop)
		goto done;

	config->fn_keycode = create_keymap(config, prop, proplen, -1, NULL);
	/* Conversion error -> fail */
	if (!config->fn_keycode) {
		free(plain_keycode);
		return -1;
	}

done:
	debug("%s: Decoded key maps %p, %p from fdt\n", __func__,
	      config->plain_keycode, config->fn_keycode);
	return 0;
}

int key_matrix_init(struct key_matrix *config, int rows, int cols,
		    int ghost_filter)
{
	memset(config, '\0', sizeof(*config));
	config->num_rows = rows;
	config->num_cols = cols;
	config->key_count = rows * cols;
	config->ghost_filter = ghost_filter;
	assert(config->key_count > 0);

	return 0;
}