372 lines
9.8 KiB
C
372 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Analog Devices ADP5585 Keys driver
|
|
*
|
|
* Copyright (C) 2025 Analog Devices, Inc.
|
|
*/
|
|
|
|
#include <linux/bitmap.h>
|
|
#include <linux/container_of.h>
|
|
#include <linux/device.h>
|
|
#include <linux/find.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/matrix_keypad.h>
|
|
#include <linux/mfd/adp5585.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/property.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/types.h>
|
|
|
|
/* As needed for the matrix parsing code */
|
|
#define ADP5589_MAX_KEYMAPSIZE 123
|
|
|
|
struct adp5585_kpad_chip {
|
|
u8 key_ev_min;
|
|
u8 key_ev_max;
|
|
u8 max_rows;
|
|
u8 max_cols;
|
|
};
|
|
|
|
struct adp5585_kpad {
|
|
const struct adp5585_kpad_chip *info;
|
|
struct notifier_block nb;
|
|
struct input_dev *input;
|
|
unsigned short keycode[ADP5589_MAX_KEYMAPSIZE];
|
|
struct device *dev;
|
|
unsigned long keypad;
|
|
int row_shift;
|
|
};
|
|
|
|
static int adp5585_keys_validate_events(const struct adp5585_kpad *kpad,
|
|
const u32 *events, u32 n_events)
|
|
{
|
|
unsigned int ev;
|
|
u32 row, col;
|
|
|
|
for (ev = 0; ev < n_events; ev++) {
|
|
if (events[ev] < kpad->info->key_ev_min ||
|
|
events[ev] > kpad->info->key_ev_max)
|
|
continue;
|
|
|
|
/*
|
|
* if the event is to be generated by the keymap, we need to make
|
|
* sure that the pins are part of it!
|
|
*/
|
|
row = (events[ev] - 1) / kpad->info->max_cols;
|
|
col = (events[ev] - 1) % kpad->info->max_cols;
|
|
|
|
if (test_bit(row, &kpad->keypad) &&
|
|
test_bit(col + kpad->info->max_rows, &kpad->keypad))
|
|
continue;
|
|
|
|
return dev_err_probe(kpad->dev, -EINVAL,
|
|
"Invalid unlock/reset event(%u) not used in the keypad\n",
|
|
events[ev]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adp5585_keys_check_special_events(const struct adp5585_dev *adp5585,
|
|
const struct adp5585_kpad *kpad)
|
|
{
|
|
int error;
|
|
|
|
error = adp5585_keys_validate_events(kpad, adp5585->unlock_keys,
|
|
adp5585->nkeys_unlock);
|
|
if (error)
|
|
return error;
|
|
|
|
error = adp5585_keys_validate_events(kpad, adp5585->reset1_keys,
|
|
adp5585->nkeys_reset1);
|
|
if (error)
|
|
return error;
|
|
|
|
return adp5585_keys_validate_events(kpad, adp5585->reset2_keys,
|
|
adp5585->nkeys_reset2);
|
|
}
|
|
|
|
static void adp5585_keys_pins_free(void *data)
|
|
{
|
|
struct adp5585_kpad *kpad = data;
|
|
struct adp5585_dev *adp5585 = dev_get_drvdata(kpad->dev->parent);
|
|
unsigned int pin;
|
|
|
|
for_each_set_bit(pin, &kpad->keypad, adp5585->n_pins)
|
|
clear_bit(pin, adp5585->pin_usage);
|
|
}
|
|
|
|
static int adp5585_keys_parse_fw(const struct adp5585_dev *adp5585,
|
|
struct adp5585_kpad *kpad)
|
|
{
|
|
struct device *dev = kpad->dev;
|
|
u32 cols = 0, rows = 0, pin;
|
|
int error, n_pins;
|
|
|
|
/*
|
|
* We do not check for errors (or no value) since the input device is
|
|
* only added if this property is present in the first place.
|
|
*/
|
|
n_pins = device_property_count_u32(dev, "adi,keypad-pins");
|
|
if (n_pins > adp5585->n_pins)
|
|
return dev_err_probe(dev, -EINVAL,
|
|
"Too many keypad pins (%d) defined (max=%d)\n",
|
|
n_pins, adp5585->n_pins);
|
|
|
|
unsigned int *keypad_pins __free(kfree) = kcalloc(n_pins, sizeof(*keypad_pins),
|
|
GFP_KERNEL);
|
|
if (!keypad_pins)
|
|
return -ENOMEM;
|
|
|
|
error = device_property_read_u32_array(dev, "adi,keypad-pins",
|
|
keypad_pins, n_pins);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* We can add the action here since it makes the code easier and nothing
|
|
* "bad" will happen out of it. Worst case, it will be a no-op and no
|
|
* bit will set.
|
|
*/
|
|
error = devm_add_action_or_reset(dev, adp5585_keys_pins_free, kpad);
|
|
if (error)
|
|
return error;
|
|
|
|
for (pin = 0; pin < n_pins; pin++) {
|
|
if (keypad_pins[pin] >= adp5585->n_pins)
|
|
return dev_err_probe(dev, -EINVAL,
|
|
"Invalid keypad pin(%u) defined\n",
|
|
keypad_pins[pin]);
|
|
|
|
if (test_and_set_bit(keypad_pins[pin], adp5585->pin_usage))
|
|
return dev_err_probe(dev, -EBUSY,
|
|
"Keypad pin(%u) already used\n",
|
|
keypad_pins[pin]);
|
|
|
|
__set_bit(keypad_pins[pin], &kpad->keypad);
|
|
}
|
|
|
|
/*
|
|
* Note that given that we get a mask (and the HW allows it), we
|
|
* can have holes in our keypad (eg: row0, row1 and row7 enabled).
|
|
* However, for the matrix parsing functions we need to pass the
|
|
* number of rows/cols as the maximum row/col used plus 1. This
|
|
* pretty much means we will also have holes in our SW keypad.
|
|
*/
|
|
|
|
rows = find_last_bit(&kpad->keypad, kpad->info->max_rows) + 1;
|
|
if (rows == kpad->info->max_rows + 1)
|
|
return dev_err_probe(dev, -EINVAL,
|
|
"Now rows defined in the keypad!\n");
|
|
|
|
cols = find_last_bit(&kpad->keypad, kpad->info->max_cols + kpad->info->max_rows);
|
|
if (cols < kpad->info->max_rows)
|
|
return dev_err_probe(dev, -EINVAL,
|
|
"No columns defined in the keypad!\n");
|
|
|
|
cols = cols + 1 - kpad->info->max_rows;
|
|
|
|
error = matrix_keypad_build_keymap(NULL, NULL, rows, cols,
|
|
kpad->keycode, kpad->input);
|
|
if (error)
|
|
return error;
|
|
|
|
kpad->row_shift = get_count_order(cols);
|
|
|
|
if (device_property_read_bool(kpad->dev, "autorepeat"))
|
|
__set_bit(EV_REP, kpad->input->evbit);
|
|
|
|
error = adp5585_keys_check_special_events(adp5585, kpad);
|
|
if (error)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adp5585_keys_setup(const struct adp5585_dev *adp5585,
|
|
struct adp5585_kpad *kpad)
|
|
{
|
|
unsigned long keys_bits, start = 0, nbits = kpad->info->max_rows;
|
|
const struct adp5585_regs *regs = adp5585->regs;
|
|
unsigned int i = 0, max_cols = kpad->info->max_cols;
|
|
int error;
|
|
|
|
/*
|
|
* Take care as the below assumes max_rows is always less or equal than
|
|
* 8 which is true for the supported devices. If we happen to add
|
|
* another device we need to make sure this still holds true. Although
|
|
* adding a new device is very unlikely.
|
|
*/
|
|
do {
|
|
keys_bits = bitmap_read(&kpad->keypad, start, nbits);
|
|
if (keys_bits) {
|
|
error = regmap_write(adp5585->regmap, regs->pin_cfg_a + i,
|
|
keys_bits);
|
|
if (error)
|
|
return error;
|
|
}
|
|
|
|
start += nbits;
|
|
if (max_cols > 8) {
|
|
nbits = 8;
|
|
max_cols -= nbits;
|
|
} else {
|
|
nbits = max_cols;
|
|
}
|
|
|
|
i++;
|
|
} while (start < kpad->info->max_rows + kpad->info->max_cols);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adp5585_keys_ev_handle(struct notifier_block *nb, unsigned long key,
|
|
void *data)
|
|
{
|
|
struct adp5585_kpad *kpad = container_of(nb, struct adp5585_kpad, nb);
|
|
unsigned long key_press = (unsigned long)data;
|
|
unsigned int row, col, code;
|
|
|
|
/* make sure the event is for us */
|
|
if (key < kpad->info->key_ev_min || key > kpad->info->key_ev_max)
|
|
return NOTIFY_DONE;
|
|
|
|
/*
|
|
* Unlikely but lets be on the safe side! We do not return any error
|
|
* because the event was indeed for us but with some weird value. So,
|
|
* we still want the caller know that the right handler was called.
|
|
*/
|
|
if (!key)
|
|
return NOTIFY_BAD;
|
|
|
|
row = (key - 1) / (kpad->info->max_cols);
|
|
col = (key - 1) % (kpad->info->max_cols);
|
|
code = MATRIX_SCAN_CODE(row, col, kpad->row_shift);
|
|
|
|
dev_dbg_ratelimited(kpad->dev, "report key(%lu) r(%d) c(%d) code(%d)\n",
|
|
key, row, col, kpad->keycode[code]);
|
|
|
|
input_report_key(kpad->input, kpad->keycode[code], key_press);
|
|
input_sync(kpad->input);
|
|
|
|
return NOTIFY_STOP;
|
|
}
|
|
|
|
static void adp5585_keys_unreg_notifier(void *data)
|
|
{
|
|
struct adp5585_kpad *kpad = data;
|
|
struct adp5585_dev *adp5585 = dev_get_drvdata(kpad->dev->parent);
|
|
|
|
blocking_notifier_chain_unregister(&adp5585->event_notifier,
|
|
&kpad->nb);
|
|
}
|
|
|
|
static int adp5585_keys_probe(struct platform_device *pdev)
|
|
{
|
|
const struct platform_device_id *id = platform_get_device_id(pdev);
|
|
struct adp5585_dev *adp5585 = dev_get_drvdata(pdev->dev.parent);
|
|
struct device *dev = &pdev->dev;
|
|
struct adp5585_kpad *kpad;
|
|
unsigned int revid;
|
|
const char *phys;
|
|
int error;
|
|
|
|
kpad = devm_kzalloc(dev, sizeof(*kpad), GFP_KERNEL);
|
|
if (!kpad)
|
|
return -ENOMEM;
|
|
|
|
if (!adp5585->irq)
|
|
return dev_err_probe(dev, -EINVAL,
|
|
"IRQ is mandatory for the keypad\n");
|
|
|
|
kpad->dev = dev;
|
|
|
|
kpad->input = devm_input_allocate_device(dev);
|
|
if (!kpad->input)
|
|
return -ENOMEM;
|
|
|
|
kpad->info = (const struct adp5585_kpad_chip *)id->driver_data;
|
|
if (!kpad->info)
|
|
return -ENODEV;
|
|
|
|
error = regmap_read(adp5585->regmap, ADP5585_ID, &revid);
|
|
if (error)
|
|
return dev_err_probe(dev, error, "Failed to read device ID\n");
|
|
|
|
phys = devm_kasprintf(dev, GFP_KERNEL, "%s/input0", pdev->name);
|
|
if (!phys)
|
|
return -ENOMEM;
|
|
|
|
kpad->input->name = pdev->name;
|
|
kpad->input->phys = phys;
|
|
|
|
kpad->input->id.bustype = BUS_I2C;
|
|
kpad->input->id.vendor = 0x0001;
|
|
kpad->input->id.product = 0x0001;
|
|
kpad->input->id.version = revid & ADP5585_REV_ID_MASK;
|
|
|
|
device_set_of_node_from_dev(dev, dev->parent);
|
|
|
|
error = adp5585_keys_parse_fw(adp5585, kpad);
|
|
if (error)
|
|
return error;
|
|
|
|
error = adp5585_keys_setup(adp5585, kpad);
|
|
if (error)
|
|
return error;
|
|
|
|
kpad->nb.notifier_call = adp5585_keys_ev_handle;
|
|
error = blocking_notifier_chain_register(&adp5585->event_notifier,
|
|
&kpad->nb);
|
|
if (error)
|
|
return error;
|
|
|
|
error = devm_add_action_or_reset(dev, adp5585_keys_unreg_notifier, kpad);
|
|
if (error)
|
|
return error;
|
|
|
|
error = input_register_device(kpad->input);
|
|
if (error)
|
|
return dev_err_probe(dev, error,
|
|
"Failed to register input device\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct adp5585_kpad_chip adp5585_kpad_chip_info = {
|
|
.max_rows = 6,
|
|
.max_cols = 5,
|
|
.key_ev_min = ADP5585_ROW5_KEY_EVENT_START,
|
|
.key_ev_max = ADP5585_ROW5_KEY_EVENT_END,
|
|
};
|
|
|
|
static const struct adp5585_kpad_chip adp5589_kpad_chip_info = {
|
|
.max_rows = 8,
|
|
.max_cols = 11,
|
|
.key_ev_min = ADP5589_KEY_EVENT_START,
|
|
.key_ev_max = ADP5589_KEY_EVENT_END,
|
|
};
|
|
|
|
static const struct platform_device_id adp5585_keys_id_table[] = {
|
|
{ "adp5585-keys", (kernel_ulong_t)&adp5585_kpad_chip_info },
|
|
{ "adp5589-keys", (kernel_ulong_t)&adp5589_kpad_chip_info },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, adp5585_keys_id_table);
|
|
|
|
static struct platform_driver adp5585_keys_driver = {
|
|
.driver = {
|
|
.name = "adp5585-keys",
|
|
},
|
|
.probe = adp5585_keys_probe,
|
|
.id_table = adp5585_keys_id_table,
|
|
};
|
|
module_platform_driver(adp5585_keys_driver);
|
|
|
|
MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
|
|
MODULE_DESCRIPTION("ADP5585 Keys Driver");
|
|
MODULE_LICENSE("GPL");
|