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
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for input events on QNAP-MCUs
*
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
*/
#include <linux/input.h>
#include <linux/mfd/qnap-mcu.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <uapi/linux/input-event-codes.h>
/*
* The power-key needs to be pressed for a while to create an event,
* so there is no use for overly frequent polling.
*/
#define POLL_INTERVAL 500
struct qnap_mcu_input_dev {
struct input_dev *input;
struct qnap_mcu *mcu;
struct device *dev;
struct work_struct beep_work;
int beep_type;
};
static void qnap_mcu_input_poll(struct input_dev *input)
{
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
static const u8 cmd[] = { '@', 'C', 'V' };
u8 reply[4];
int state, ret;
/* poll the power button */
ret = qnap_mcu_exec(idev->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
if (ret)
return;
/* First bytes must mirror the sent command */
if (memcmp(cmd, reply, sizeof(cmd))) {
dev_err(idev->dev, "malformed data received\n");
return;
}
state = reply[3] - 0x30;
input_event(input, EV_KEY, KEY_POWER, state);
input_sync(input);
}
static void qnap_mcu_input_beeper_work(struct work_struct *work)
{
struct qnap_mcu_input_dev *idev =
container_of(work, struct qnap_mcu_input_dev, beep_work);
const u8 cmd[] = { '@', 'C', (idev->beep_type == SND_TONE) ? '3' : '2' };
qnap_mcu_exec_with_ack(idev->mcu, cmd, sizeof(cmd));
}
static int qnap_mcu_input_event(struct input_dev *input, unsigned int type,
unsigned int code, int value)
{
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
if (type != EV_SND || (code != SND_BELL && code != SND_TONE))
return -EOPNOTSUPP;
if (value < 0)
return -EINVAL;
/* beep runtime is determined by the MCU */
if (value == 0)
return 0;
/* Schedule work to actually turn the beeper on */
idev->beep_type = code;
schedule_work(&idev->beep_work);
return 0;
}
static void qnap_mcu_input_close(struct input_dev *input)
{
struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
cancel_work_sync(&idev->beep_work);
}
static int qnap_mcu_input_probe(struct platform_device *pdev)
{
struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
struct qnap_mcu_input_dev *idev;
struct device *dev = &pdev->dev;
struct input_dev *input;
int ret;
idev = devm_kzalloc(dev, sizeof(*idev), GFP_KERNEL);
if (!idev)
return -ENOMEM;
input = devm_input_allocate_device(dev);
if (!input)
return dev_err_probe(dev, -ENOMEM, "no memory for input device\n");
idev->input = input;
idev->dev = dev;
idev->mcu = mcu;
input_set_drvdata(input, idev);
input->name = "qnap-mcu";
input->phys = "qnap-mcu-input/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->event = qnap_mcu_input_event;
input->close = qnap_mcu_input_close;
input_set_capability(input, EV_KEY, KEY_POWER);
input_set_capability(input, EV_SND, SND_BELL);
input_set_capability(input, EV_SND, SND_TONE);
INIT_WORK(&idev->beep_work, qnap_mcu_input_beeper_work);
ret = input_setup_polling(input, qnap_mcu_input_poll);
if (ret)
return dev_err_probe(dev, ret, "unable to set up polling\n");
input_set_poll_interval(input, POLL_INTERVAL);
ret = input_register_device(input);
if (ret)
return dev_err_probe(dev, ret, "unable to register input device\n");
return 0;
}
static struct platform_driver qnap_mcu_input_driver = {
.probe = qnap_mcu_input_probe,
.driver = {
.name = "qnap-mcu-input",
},
};
module_platform_driver(qnap_mcu_input_driver);
MODULE_ALIAS("platform:qnap-mcu-input");
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
MODULE_DESCRIPTION("QNAP MCU input driver");
MODULE_LICENSE("GPL");
|