diff --git a/Documentation/devicetree/bindings/input/rpmsg-keys.txt b/Documentation/devicetree/bindings/input/rpmsg-keys.txt new file mode 100644 index 000000000000..d9279802cc9c --- /dev/null +++ b/Documentation/devicetree/bindings/input/rpmsg-keys.txt @@ -0,0 +1,33 @@ +Device-Tree bindings for input/keyboard/rpmsg-keys.c keys driver over +rpmsg. On i.mx7ULP keys are connected on M4 side, so rpmsg-keys driver +needed to get the key status from M4 side by rpmsg. + +Required properties: + - compatible = "fsl,rpmsg-keys"; + +Each button/key looked as the sub node: +Required properties: + - label: the key name + - linux,code: the key value defined in + include/dt-bindings/input/input.h +Optional property: + - rpmsg-key,wakeup: wakeup feature, the keys can wakeup from + suspend if the keys with this property pressed. + +Example nodes: + rpmsg_keys: rpmsg-keys { + compatible = "fsl,rpmsg-keys"; + + volume-up { + label = "Volume Up"; + rpmsg-key,wakeup; + linux,code = ; + }; + + volume-down { + label = "Volume Down"; + rpmsg-key,wakeup; + linux,code = ; + }; + }; + diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 8911bc2ec42a..41e2e0dc3efb 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -448,6 +448,17 @@ config KEYBOARD_MPR121 To compile this driver as a module, choose M here: the module will be called mpr121_touchkey. +config KEYBOARD_RPMSG + tristate "i.MX Rpmsg Keys Driver" + depends on (SOC_IMX7ULP) + depends on RPMSG + depends on OF + help + This is rpmsg keys driver on i.mx7ulp, because some keys located + in M4 side. + + + config KEYBOARD_SNVS_PWRKEY tristate "IMX SNVS Power Key Driver" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 9510325c0c5d..e8aa02b4733c 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_QT1050) += qt1050.o obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o +obj-$(CONFIG_KEYBOARD_RPMSG) += rpmsg-keys.o obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o obj-$(CONFIG_KEYBOARD_SNVS_PWRKEY) += snvs_pwrkey.o diff --git a/drivers/input/keyboard/rpmsg-keys.c b/drivers/input/keyboard/rpmsg-keys.c new file mode 100644 index 000000000000..f6012e87f9a6 --- /dev/null +++ b/drivers/input/keyboard/rpmsg-keys.c @@ -0,0 +1,310 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RPMSG_TIMEOUT 1000 + +enum key_cmd_type { + KEY_RPMSG_SETUP, + KEY_RPMSG_REPLY, + KEY_RPMSG_NOTIFY, +}; + +enum keys_type { + KEY_PRESS = 1, + KEY_RELEASE, + KEY_BOTH, +}; + +struct key_rpmsg_data { + struct imx_rpmsg_head header; + u8 key_index; + union { + u8 event; + u8 retcode; + }; + u8 wakeup; +} __attribute__((packed)); + +struct rpmsg_keys_button { + unsigned int code; + enum keys_type type; + int wakeup; + struct input_dev *input; +}; + +struct rpmsg_keys_drvdata { + struct input_dev *input; + struct rpmsg_device *rpdev; + struct device *dev; + struct key_rpmsg_data *msg; + bool ack; + struct pm_qos_request pm_qos_req; + struct delayed_work keysetup_work; + struct completion cmd_complete; + int nbuttons; + struct rpmsg_keys_button buttons[0]; +}; + +static struct rpmsg_keys_drvdata *keys_rpmsg; + +static int key_send_message(struct key_rpmsg_data *msg, + struct rpmsg_keys_drvdata *info, bool ack) +{ + int err; + + if (!info->rpdev) { + dev_dbg(info->dev, + "rpmsg channel not ready, m4 image ready?\n"); + return -EINVAL; + } + + pm_qos_add_request(&info->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + + if (ack) { + info->ack = true; + reinit_completion(&info->cmd_complete); + } + + err = rpmsg_send(info->rpdev->ept, (void *)msg, + sizeof(struct key_rpmsg_data)); + if (err) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err); + goto err_out; + } + + if (ack) { + err = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!err) { + dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n"); + err = -ETIMEDOUT; + goto err_out; + } + + if (info->msg->retcode != 0) { + dev_err(&info->rpdev->dev, "rpmsg not ack %d!\n", + info->msg->retcode); + err = -EINVAL; + goto err_out; + } + + err = 0; + } + +err_out: + info->ack = true; + pm_qos_remove_request(&info->pm_qos_req); + + return err; +} + +static int keys_rpmsg_cb(struct rpmsg_device *rpdev, + void *data, int len, void *priv, u32 src) +{ + struct key_rpmsg_data *msg = (struct key_rpmsg_data *)data; + + if (msg->header.type == KEY_RPMSG_REPLY) { + keys_rpmsg->msg = msg; + complete(&keys_rpmsg->cmd_complete); + return 0; + } else if (msg->header.type == KEY_RPMSG_NOTIFY) { + keys_rpmsg->msg = msg; + keys_rpmsg->ack = false; + } else + dev_err(&keys_rpmsg->rpdev->dev, "wrong command type!\n"); + + input_event(keys_rpmsg->input, EV_KEY, msg->key_index, msg->event); + input_sync(keys_rpmsg->input); + + return 0; +} + +static void keys_init_handler(struct work_struct *work) +{ + struct key_rpmsg_data msg; + int i; + + /* setup keys */ + for (i = 0; i < keys_rpmsg->nbuttons; i++) { + struct rpmsg_keys_button *button = &keys_rpmsg->buttons[i]; + + msg.header.cate = IMX_RPMSG_KEY; + msg.header.major = IMX_RMPSG_MAJOR; + msg.header.minor = IMX_RMPSG_MINOR; + msg.header.type = KEY_RPMSG_SETUP; + msg.header.cmd = 0; + msg.key_index = button->code; + msg.wakeup = button->wakeup; + msg.event = button->type; + if (key_send_message(&msg, keys_rpmsg, true)) + dev_err(&keys_rpmsg->rpdev->dev, + "key %d setup failed!\n", button->code); + } +} + +static int keys_rpmsg_probe(struct rpmsg_device *rpdev) +{ + keys_rpmsg->rpdev = rpdev; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + init_completion(&keys_rpmsg->cmd_complete); + + INIT_DELAYED_WORK(&keys_rpmsg->keysetup_work, + keys_init_handler); + schedule_delayed_work(&keys_rpmsg->keysetup_work, + msecs_to_jiffies(100)); + + return 0; +} + +static struct rpmsg_device_id keys_rpmsg_id_table[] = { + { .name = "rpmsg-keypad-channel" }, + { }, +}; + +static struct rpmsg_driver keys_rpmsg_driver = { + .drv.name = "key_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = keys_rpmsg_id_table, + .probe = keys_rpmsg_probe, + .callback = keys_rpmsg_cb, +}; + +static struct rpmsg_keys_drvdata * +rpmsg_keys_get_devtree_pdata(struct device *dev) +{ + struct device_node *node, *pp; + struct rpmsg_keys_drvdata *ddata; + struct rpmsg_keys_button *button; + int nbuttons; + int i; + + node = dev->of_node; + if (!node) + return ERR_PTR(-ENODEV); + + nbuttons = of_get_child_count(node); + if (nbuttons == 0) + return ERR_PTR(-ENODEV); + + ddata = devm_kzalloc(dev, + sizeof(*ddata) + nbuttons * + sizeof(struct rpmsg_keys_button), + GFP_KERNEL); + if (!ddata) + return ERR_PTR(-ENOMEM); + + ddata->nbuttons = nbuttons; + + i = 0; + for_each_child_of_node(node, pp) { + button = &ddata->buttons[i++]; + + if (of_property_read_u32(pp, "linux,code", &button->code)) { + dev_err(dev, "Button without keycode: 0x%x\n", + button->code); + return ERR_PTR(-EINVAL); + } + + button->wakeup = !!of_get_property(pp, "rpmsg-key,wakeup", + NULL); + button->type = KEY_BOTH; + } + + return ddata; +} + +static int rpmsg_keys_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpmsg_keys_drvdata *ddata; + int i, error; + struct input_dev *input; + + ddata = rpmsg_keys_get_devtree_pdata(dev); + if (IS_ERR(ddata)) + return PTR_ERR(ddata); + + input = devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + ddata->input = input; + + keys_rpmsg = ddata; + platform_set_drvdata(pdev, ddata); + + input->name = pdev->name; + input->phys = "rpmsg-keys/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + + for (i = 0; i < ddata->nbuttons; i++) { + struct rpmsg_keys_button *button = &ddata->buttons[i]; + + input_set_capability(input, EV_KEY, button->code); + } + + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + goto err_out; + } + + return register_rpmsg_driver(&keys_rpmsg_driver); +err_out: + return error; +} + +static const struct of_device_id rpmsg_keys_of_match[] = { + { .compatible = "fsl,rpmsg-keys", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, rpmsg_keys_of_match); + +static struct platform_driver rpmsg_keys_device_driver = { + .probe = rpmsg_keys_probe, + .driver = { + .name = "rpmsg-keys", + .of_match_table = of_match_ptr(rpmsg_keys_of_match) + } +}; + +module_platform_driver(rpmsg_keys_device_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robin Gong "); +MODULE_DESCRIPTION("Keyboard driver based on rpmsg");