]> www.infradead.org Git - users/willy/pagecache.git/commitdiff
Input: add driver for the input part of qnap-mcu devices
authorHeiko Stuebner <heiko@sntech.de>
Thu, 7 Nov 2024 11:47:09 +0000 (12:47 +0100)
committerLee Jones <lee@kernel.org>
Tue, 17 Dec 2024 13:14:42 +0000 (13:14 +0000)
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 <heiko@sntech.de>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Link: https://lore.kernel.org/r/20241107114712.538976-7-heiko@sntech.de
Signed-off-by: Lee Jones <lee@kernel.org>
MAINTAINERS
drivers/input/misc/Kconfig
drivers/input/misc/Makefile
drivers/input/misc/qnap-mcu-input.c [new file with mode: 0644]

index 3391026448e731d48955838ffdcad72e02e4d8d4..f19425af815add755f2df374a8e0f2614dc50bbc 100644 (file)
@@ -19108,6 +19108,7 @@ F:      drivers/media/tuners/qm1d1c0042*
 QNAP MCU DRIVER
 M:     Heiko Stuebner <heiko@sntech.de>
 S:     Maintained
+F:     drivers/input/misc/qnap-mcu-input.c
 F:     drivers/leds/leds-qnap-mcu.c
 F:     drivers/mfd/qnap-mcu.c
 F:     include/linux/qnap-mcu.h
index 6a852c76331b62b8a480875e56767940746922a8..13d135257e0601ed9b07777f0290a19f23abc7ca 100644 (file)
@@ -917,6 +917,18 @@ config INPUT_HISI_POWERKEY
          To compile this driver as a module, choose M here: the
          module will be called hisi_powerkey.
 
+config INPUT_QNAP_MCU
+       tristate "Input Support for QNAP MCU controllers"
+       depends on MFD_QNAP_MCU
+       help
+         This option enables support for input elements available on
+         embedded controllers used in QNAP NAS devices.
+
+         This includes a polled power-button as well as a beeper.
+
+         To compile this driver as a module, choose M here: the
+         module will be called qnap-mcu-input.
+
 config INPUT_RAVE_SP_PWRBUTTON
        tristate "RAVE SP Power button Driver"
        depends on RAVE_SP_CORE
index 4f7f736831ba84457a4d89593411d4ac2c5bd1f8..6d91804d0a6f761a094e6c380f878f74c3054d63 100644 (file)
@@ -68,6 +68,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY)   += pmic8xxx-pwrkey.o
 obj-$(CONFIG_INPUT_POWERMATE)          += powermate.o
 obj-$(CONFIG_INPUT_PWM_BEEPER)         += pwm-beeper.o
 obj-$(CONFIG_INPUT_PWM_VIBRA)          += pwm-vibra.o
+obj-$(CONFIG_INPUT_QNAP_MCU)           += qnap-mcu-input.o
 obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON)  += rave-sp-pwrbutton.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)       += rb532_button.o
 obj-$(CONFIG_INPUT_REGULATOR_HAPTIC)   += regulator-haptic.o
diff --git a/drivers/input/misc/qnap-mcu-input.c b/drivers/input/misc/qnap-mcu-input.c
new file mode 100644 (file)
index 0000000..76e62f0
--- /dev/null
@@ -0,0 +1,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");