162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Cirrus Logic CLPS711X Keypad driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/input.h>
962306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1262306a36Sopenharmony_ci#include <linux/platform_device.h>
1362306a36Sopenharmony_ci#include <linux/property.h>
1462306a36Sopenharmony_ci#include <linux/regmap.h>
1562306a36Sopenharmony_ci#include <linux/sched.h>
1662306a36Sopenharmony_ci#include <linux/input/matrix_keypad.h>
1762306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1862306a36Sopenharmony_ci#include <linux/mfd/syscon/clps711x.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define CLPS711X_KEYPAD_COL_COUNT	8
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct clps711x_gpio_data {
2362306a36Sopenharmony_ci	struct gpio_desc *desc;
2462306a36Sopenharmony_ci	DECLARE_BITMAP(last_state, CLPS711X_KEYPAD_COL_COUNT);
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct clps711x_keypad_data {
2862306a36Sopenharmony_ci	struct regmap			*syscon;
2962306a36Sopenharmony_ci	int				row_count;
3062306a36Sopenharmony_ci	unsigned int			row_shift;
3162306a36Sopenharmony_ci	struct clps711x_gpio_data	*gpio_data;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic void clps711x_keypad_poll(struct input_dev *input)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	const unsigned short *keycodes = input->keycode;
3762306a36Sopenharmony_ci	struct clps711x_keypad_data *priv = input_get_drvdata(input);
3862306a36Sopenharmony_ci	bool sync = false;
3962306a36Sopenharmony_ci	int col, row;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	for (col = 0; col < CLPS711X_KEYPAD_COL_COUNT; col++) {
4262306a36Sopenharmony_ci		/* Assert column */
4362306a36Sopenharmony_ci		regmap_update_bits(priv->syscon, SYSCON_OFFSET,
4462306a36Sopenharmony_ci				   SYSCON1_KBDSCAN_MASK,
4562306a36Sopenharmony_ci				   SYSCON1_KBDSCAN(8 + col));
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci		/* Scan rows */
4862306a36Sopenharmony_ci		for (row = 0; row < priv->row_count; row++) {
4962306a36Sopenharmony_ci			struct clps711x_gpio_data *data = &priv->gpio_data[row];
5062306a36Sopenharmony_ci			bool state, state1;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci			/* Read twice for protection against fluctuations */
5362306a36Sopenharmony_ci			do {
5462306a36Sopenharmony_ci				state = gpiod_get_value_cansleep(data->desc);
5562306a36Sopenharmony_ci				cond_resched();
5662306a36Sopenharmony_ci				state1 = gpiod_get_value_cansleep(data->desc);
5762306a36Sopenharmony_ci			} while (state != state1);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci			if (test_bit(col, data->last_state) != state) {
6062306a36Sopenharmony_ci				int code = MATRIX_SCAN_CODE(row, col,
6162306a36Sopenharmony_ci							    priv->row_shift);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci				if (state) {
6462306a36Sopenharmony_ci					set_bit(col, data->last_state);
6562306a36Sopenharmony_ci					input_event(input,
6662306a36Sopenharmony_ci						    EV_MSC, MSC_SCAN, code);
6762306a36Sopenharmony_ci				} else {
6862306a36Sopenharmony_ci					clear_bit(col, data->last_state);
6962306a36Sopenharmony_ci				}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci				if (keycodes[code])
7262306a36Sopenharmony_ci					input_report_key(input,
7362306a36Sopenharmony_ci							 keycodes[code], state);
7462306a36Sopenharmony_ci				sync = true;
7562306a36Sopenharmony_ci			}
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci		/* Set all columns to low */
7962306a36Sopenharmony_ci		regmap_update_bits(priv->syscon, SYSCON_OFFSET,
8062306a36Sopenharmony_ci				   SYSCON1_KBDSCAN_MASK, SYSCON1_KBDSCAN(1));
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (sync)
8462306a36Sopenharmony_ci		input_sync(input);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int clps711x_keypad_probe(struct platform_device *pdev)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct clps711x_keypad_data *priv;
9062306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
9162306a36Sopenharmony_ci	struct input_dev *input;
9262306a36Sopenharmony_ci	u32 poll_interval;
9362306a36Sopenharmony_ci	int i, err;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
9662306a36Sopenharmony_ci	if (!priv)
9762306a36Sopenharmony_ci		return -ENOMEM;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	priv->syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
10062306a36Sopenharmony_ci	if (IS_ERR(priv->syscon))
10162306a36Sopenharmony_ci		return PTR_ERR(priv->syscon);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	priv->row_count = gpiod_count(dev, "row");
10462306a36Sopenharmony_ci	if (priv->row_count < 1)
10562306a36Sopenharmony_ci		return -EINVAL;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	priv->gpio_data = devm_kcalloc(dev,
10862306a36Sopenharmony_ci				priv->row_count, sizeof(*priv->gpio_data),
10962306a36Sopenharmony_ci				GFP_KERNEL);
11062306a36Sopenharmony_ci	if (!priv->gpio_data)
11162306a36Sopenharmony_ci		return -ENOMEM;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	priv->row_shift = get_count_order(CLPS711X_KEYPAD_COL_COUNT);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	for (i = 0; i < priv->row_count; i++) {
11662306a36Sopenharmony_ci		struct clps711x_gpio_data *data = &priv->gpio_data[i];
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		data->desc = devm_gpiod_get_index(dev, "row", i, GPIOD_IN);
11962306a36Sopenharmony_ci		if (IS_ERR(data->desc))
12062306a36Sopenharmony_ci			return PTR_ERR(data->desc);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	err = device_property_read_u32(dev, "poll-interval", &poll_interval);
12462306a36Sopenharmony_ci	if (err)
12562306a36Sopenharmony_ci		return err;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	input = devm_input_allocate_device(dev);
12862306a36Sopenharmony_ci	if (!input)
12962306a36Sopenharmony_ci		return -ENOMEM;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	input_set_drvdata(input, priv);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	input->name		= pdev->name;
13462306a36Sopenharmony_ci	input->dev.parent	= dev;
13562306a36Sopenharmony_ci	input->id.bustype	= BUS_HOST;
13662306a36Sopenharmony_ci	input->id.vendor	= 0x0001;
13762306a36Sopenharmony_ci	input->id.product	= 0x0001;
13862306a36Sopenharmony_ci	input->id.version	= 0x0100;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	err = matrix_keypad_build_keymap(NULL, NULL, priv->row_count,
14162306a36Sopenharmony_ci					 CLPS711X_KEYPAD_COL_COUNT,
14262306a36Sopenharmony_ci					 NULL, input);
14362306a36Sopenharmony_ci	if (err)
14462306a36Sopenharmony_ci		return err;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	input_set_capability(input, EV_MSC, MSC_SCAN);
14762306a36Sopenharmony_ci	if (device_property_read_bool(dev, "autorepeat"))
14862306a36Sopenharmony_ci		__set_bit(EV_REP, input->evbit);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	/* Set all columns to low */
15162306a36Sopenharmony_ci	regmap_update_bits(priv->syscon, SYSCON_OFFSET, SYSCON1_KBDSCAN_MASK,
15262306a36Sopenharmony_ci			   SYSCON1_KBDSCAN(1));
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	err = input_setup_polling(input, clps711x_keypad_poll);
15662306a36Sopenharmony_ci	if (err)
15762306a36Sopenharmony_ci		return err;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	input_set_poll_interval(input, poll_interval);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	err = input_register_device(input);
16262306a36Sopenharmony_ci	if (err)
16362306a36Sopenharmony_ci		return err;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	return 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic const struct of_device_id clps711x_keypad_of_match[] = {
16962306a36Sopenharmony_ci	{ .compatible = "cirrus,ep7209-keypad", },
17062306a36Sopenharmony_ci	{ }
17162306a36Sopenharmony_ci};
17262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, clps711x_keypad_of_match);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic struct platform_driver clps711x_keypad_driver = {
17562306a36Sopenharmony_ci	.driver	= {
17662306a36Sopenharmony_ci		.name		= "clps711x-keypad",
17762306a36Sopenharmony_ci		.of_match_table	= clps711x_keypad_of_match,
17862306a36Sopenharmony_ci	},
17962306a36Sopenharmony_ci	.probe	= clps711x_keypad_probe,
18062306a36Sopenharmony_ci};
18162306a36Sopenharmony_cimodule_platform_driver(clps711x_keypad_driver);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ciMODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
18462306a36Sopenharmony_ciMODULE_DESCRIPTION("Cirrus Logic CLPS711X Keypad driver");
18562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
186