File Index
Files and directories
Sign in
FILE

qnap-mcu-input.c

File preview with syntax highlighting.

linux-6.19.14/drivers/input/misc/qnap-mcu-input.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2
  3/*
  4 * Driver for input events on QNAP-MCUs
  5 *
  6 * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
  7 */
  8
  9#include <linux/input.h>
 10#include <linux/mfd/qnap-mcu.h>
 11#include <linux/module.h>
 12#include <linux/platform_device.h>
 13#include <linux/slab.h>
 14#include <uapi/linux/input-event-codes.h>
 15
 16/*
 17 * The power-key needs to be pressed for a while to create an event,
 18 * so there is no use for overly frequent polling.
 19 */
 20#define POLL_INTERVAL		500
 21
 22struct qnap_mcu_input_dev {
 23	struct input_dev *input;
 24	struct qnap_mcu *mcu;
 25	struct device *dev;
 26
 27	struct work_struct beep_work;
 28	int beep_type;
 29};
 30
 31static void qnap_mcu_input_poll(struct input_dev *input)
 32{
 33	struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
 34	static const u8 cmd[] = { '@', 'C', 'V' };
 35	u8 reply[4];
 36	int state, ret;
 37
 38	/* poll the power button */
 39	ret = qnap_mcu_exec(idev->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
 40	if (ret)
 41		return;
 42
 43	/* First bytes must mirror the sent command */
 44	if (memcmp(cmd, reply, sizeof(cmd))) {
 45		dev_err(idev->dev, "malformed data received\n");
 46		return;
 47	}
 48
 49	state = reply[3] - 0x30;
 50	input_event(input, EV_KEY, KEY_POWER, state);
 51	input_sync(input);
 52}
 53
 54static void qnap_mcu_input_beeper_work(struct work_struct *work)
 55{
 56	struct qnap_mcu_input_dev *idev =
 57		container_of(work, struct qnap_mcu_input_dev, beep_work);
 58	const u8 cmd[] = { '@', 'C', (idev->beep_type == SND_TONE) ? '3' : '2' };
 59
 60	qnap_mcu_exec_with_ack(idev->mcu, cmd, sizeof(cmd));
 61}
 62
 63static int qnap_mcu_input_event(struct input_dev *input, unsigned int type,
 64				unsigned int code, int value)
 65{
 66	struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
 67
 68	if (type != EV_SND || (code != SND_BELL && code != SND_TONE))
 69		return -EOPNOTSUPP;
 70
 71	if (value < 0)
 72		return -EINVAL;
 73
 74	/* beep runtime is determined by the MCU */
 75	if (value == 0)
 76		return 0;
 77
 78	/* Schedule work to actually turn the beeper on */
 79	idev->beep_type = code;
 80	schedule_work(&idev->beep_work);
 81
 82	return 0;
 83}
 84
 85static void qnap_mcu_input_close(struct input_dev *input)
 86{
 87	struct qnap_mcu_input_dev *idev = input_get_drvdata(input);
 88
 89	cancel_work_sync(&idev->beep_work);
 90}
 91
 92static int qnap_mcu_input_probe(struct platform_device *pdev)
 93{
 94	struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
 95	struct qnap_mcu_input_dev *idev;
 96	struct device *dev = &pdev->dev;
 97	struct input_dev *input;
 98	int ret;
 99
100	idev = devm_kzalloc(dev, sizeof(*idev), GFP_KERNEL);
101	if (!idev)
102		return -ENOMEM;
103
104	input = devm_input_allocate_device(dev);
105	if (!input)
106		return -ENOMEM;
107
108	idev->input = input;
109	idev->dev = dev;
110	idev->mcu = mcu;
111
112	input_set_drvdata(input, idev);
113
114	input->name		= "qnap-mcu";
115	input->phys		= "qnap-mcu-input/input0";
116	input->id.bustype	= BUS_HOST;
117	input->id.vendor	= 0x0001;
118	input->id.product	= 0x0001;
119	input->id.version	= 0x0100;
120	input->event		= qnap_mcu_input_event;
121	input->close		= qnap_mcu_input_close;
122
123	input_set_capability(input, EV_KEY, KEY_POWER);
124	input_set_capability(input, EV_SND, SND_BELL);
125	input_set_capability(input, EV_SND, SND_TONE);
126
127	INIT_WORK(&idev->beep_work, qnap_mcu_input_beeper_work);
128
129	ret = input_setup_polling(input, qnap_mcu_input_poll);
130	if (ret)
131		return dev_err_probe(dev, ret, "unable to set up polling\n");
132
133	input_set_poll_interval(input, POLL_INTERVAL);
134
135	ret = input_register_device(input);
136	if (ret)
137		return dev_err_probe(dev, ret, "unable to register input device\n");
138
139	return 0;
140}
141
142static struct platform_driver qnap_mcu_input_driver = {
143	.probe = qnap_mcu_input_probe,
144	.driver = {
145		.name = "qnap-mcu-input",
146	},
147};
148module_platform_driver(qnap_mcu_input_driver);
149
150MODULE_ALIAS("platform:qnap-mcu-input");
151MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
152MODULE_DESCRIPTION("QNAP MCU input driver");
153MODULE_LICENSE("GPL");
~/ Read-only file preview.