-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <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>
- Loading branch information
Heiko Stuebner
authored and
Lee Jones
committed
Dec 17, 2024
1 parent
2ec8bb4
commit bb7e361
Showing
4 changed files
with
167 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); |