Skip to content

Commit bb7e361

Browse files
mmindlag-linaro
authored andcommitted
Input: add driver for the input part of qnap-mcu devices
The MCU controls the power-button and beeper, so expose them as input device. There is of course no interrupt line, so the status of the power-button needs to be polled. To generate an event the power-button also needs to be held for 1-2 seconds, so the polling interval does not need to be overly fast. Signed-off-by: Heiko Stuebner <[email protected]> Acked-by: Dmitry Torokhov <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Lee Jones <[email protected]>
1 parent 2ec8bb4 commit bb7e361

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19108,6 +19108,7 @@ F: drivers/media/tuners/qm1d1c0042*
1910819108
QNAP MCU DRIVER
1910919109
M: Heiko Stuebner <[email protected]>
1911019110
S: Maintained
19111+
F: drivers/input/misc/qnap-mcu-input.c
1911119112
F: drivers/leds/leds-qnap-mcu.c
1911219113
F: drivers/mfd/qnap-mcu.c
1911319114
F: include/linux/qnap-mcu.h

drivers/input/misc/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,18 @@ config INPUT_HISI_POWERKEY
917917
To compile this driver as a module, choose M here: the
918918
module will be called hisi_powerkey.
919919

920+
config INPUT_QNAP_MCU
921+
tristate "Input Support for QNAP MCU controllers"
922+
depends on MFD_QNAP_MCU
923+
help
924+
This option enables support for input elements available on
925+
embedded controllers used in QNAP NAS devices.
926+
927+
This includes a polled power-button as well as a beeper.
928+
929+
To compile this driver as a module, choose M here: the
930+
module will be called qnap-mcu-input.
931+
920932
config INPUT_RAVE_SP_PWRBUTTON
921933
tristate "RAVE SP Power button Driver"
922934
depends on RAVE_SP_CORE

drivers/input/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
6868
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
6969
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
7070
obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o
71+
obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o
7172
obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o
7273
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
7374
obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o

drivers/input/misc/qnap-mcu-input.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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 <[email protected]>
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+
22+
struct 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+
31+
static 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+
54+
static 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+
63+
static 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+
85+
static 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+
92+
static 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 dev_err_probe(dev, -ENOMEM, "no memory for input device\n");
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+
142+
static struct platform_driver qnap_mcu_input_driver = {
143+
.probe = qnap_mcu_input_probe,
144+
.driver = {
145+
.name = "qnap-mcu-input",
146+
},
147+
};
148+
module_platform_driver(qnap_mcu_input_driver);
149+
150+
MODULE_ALIAS("platform:qnap-mcu-input");
151+
MODULE_AUTHOR("Heiko Stuebner <[email protected]>");
152+
MODULE_DESCRIPTION("QNAP MCU input driver");
153+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)