Re: [PATCH v9 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys

Svyatoslav Ryhel <[email protected]>
Newsgroups org.kernel.vger.linux-leds,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-input,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm
Message-ID <CAPVz0n1o+2NRQrLfbCMT0JdeFhhD4PS4UzSt3L6-QQ2ywHL+mA@mail.gmail.com>
чт, 25 черв. 2026 р. о 11:16 Svyatoslav Ryhel <[email protected]> пише:
>
> From: Michał Mirosław <[email protected]>
>
> Add support for multimedia top button row of ASUS Transformer's Mobile
> Dock keyboard. Driver is made that function keys (F1-F12) are used by
> default which suits average Linux use better and with pressing
> ScreenLock + AltGr function keys layout is switched to multimedia keys.
> Only Dock keyboard input events are tracked for AltGr pressing.
>
> Co-developed-by: Ion Agorria <[email protected]>
> Signed-off-by: Ion Agorria <[email protected]>
> Signed-off-by: Michał Mirosław <[email protected]>
> Signed-off-by: Svyatoslav Ryhel <[email protected]>
> ---
>  drivers/input/keyboard/Kconfig                |  10 +
>  drivers/input/keyboard/Makefile               |   1 +
>  .../input/keyboard/asus-transformer-ec-keys.c | 314 ++++++++++++++++++
>  3 files changed, 325 insertions(+)
>  create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
>
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index 9d1019ba0245..913cb4900565 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -89,6 +89,16 @@ config KEYBOARD_APPLESPI
>           To compile this driver as a module, choose M here: the
>           module will be called applespi.
>
> +config KEYBOARD_ASUS_TRANSFORMER_EC
> +       tristate "Asus Transformer's Mobile Dock multimedia keys"
> +       depends on MFD_ASUS_TRANSFORMER_EC
> +       help
> +         Say Y here if you want to use multimedia keys present on Asus
> +         Transformer's Mobile Dock.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called asus-transformer-ec-keys.
> +
>  config KEYBOARD_ATARI
>         tristate "Atari keyboard"
>         depends on ATARI
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 60bb7baf802f..0d81096887ad 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_ADP5585)                += adp5585-keys.o
>  obj-$(CONFIG_KEYBOARD_ADP5588)         += adp5588-keys.o
>  obj-$(CONFIG_KEYBOARD_AMIGA)           += amikbd.o
>  obj-$(CONFIG_KEYBOARD_APPLESPI)                += applespi.o
> +obj-$(CONFIG_KEYBOARD_ASUS_TRANSFORMER_EC)     += asus-transformer-ec-keys.o
>  obj-$(CONFIG_KEYBOARD_ATARI)           += atakbd.o
>  obj-$(CONFIG_KEYBOARD_ATKBD)           += atkbd.o
>  obj-$(CONFIG_KEYBOARD_BCM)             += bcm-keypad.o
> diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/input/keyboard/asus-transformer-ec-keys.c
> new file mode 100644
> index 000000000000..53aff3ce7146
> --- /dev/null
> +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
> @@ -0,0 +1,314 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/array_size.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/mfd/asus-transformer-ec.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#define ASUSEC_EXT_KEY_CODES           0x20
> +
> +struct asus_ec_keys_data {
> +       struct notifier_block nb;
> +       struct asusec_core *ec;
> +       struct input_dev *xidev;
> +       struct input_handler input_handler;
> +       unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2];
> +       const char *kbc_phys;
> +       bool special_key_pressed;
> +       bool special_key_mode;
> +};
> +
> +static void asus_ec_input_event(struct input_handle *handle,
> +                               unsigned int event_type,
> +                               unsigned int event_code, int value)
> +{
> +       struct asus_ec_keys_data *priv = handle->handler->private;
> +
> +       /* Store special key state */
> +       if (event_type == EV_KEY && event_code == KEY_RIGHTALT)
> +               priv->special_key_pressed = !!value;
> +}
> +
> +static int asus_ec_input_connect(struct input_handler *handler,
> +                                struct input_dev *dev,
> +                                const struct input_device_id *id)
> +{
> +       struct asus_ec_keys_data *priv = handler->private;
> +       struct input_handle *handle;
> +       int error;
> +
> +       if (!dev->phys || !strstr(dev->phys, priv->kbc_phys))
> +               return -ENODEV;
> +

Hello Dmitry!

Would this approach be acceptable? Handler links strictly to asus-ec keyboard.

> +       handle = kzalloc_obj(*handle);
> +       if (!handle)
> +               return -ENOMEM;
> +
> +       handle->dev = dev;
> +       handle->handler = handler;
> +       handle->name = handler->name;
> +
> +       error = input_register_handle(handle);
> +       if (error)
> +               goto err_free_handle;
> +
> +       error = input_open_device(handle);
> +       if (error)
> +               goto err_unregister_handle;
> +
> +       return 0;
> +
> + err_unregister_handle:
> +       input_unregister_handle(handle);
> + err_free_handle:
> +       kfree(handle);
> +
> +       return error;
> +}
> +
> +static void asus_ec_input_disconnect(struct input_handle *handle)
> +{
> +       input_close_device(handle);
> +       input_unregister_handle(handle);
> +       kfree(handle);
> +}
> +
> +static const struct input_device_id asus_ec_input_ids[] = {
> +       {
> +               .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
> +               .evbit = { BIT_MASK(EV_KEY) },
> +       },
> +       { }
> +};
> +
> +static const unsigned short asus_ec_dock_ext_keys[] = {
> +       /* Function keys [0x00 - 0x19] */
> +       [0x01] = KEY_DELETE,
> +       [0x02] = KEY_F1,
> +       [0x03] = KEY_F2,
> +       [0x04] = KEY_F3,
> +       [0x05] = KEY_F4,
> +       [0x06] = KEY_F5,
> +       [0x07] = KEY_F6,
> +       [0x08] = KEY_F7,
> +       [0x10] = KEY_F8,
> +       [0x11] = KEY_F9,
> +       [0x12] = KEY_F10,
> +       [0x13] = KEY_F11,
> +       [0x14] = KEY_F12,
> +       [0x15] = KEY_MUTE,
> +       [0x16] = KEY_VOLUMEDOWN,
> +       [0x17] = KEY_VOLUMEUP,
> +       /* Multimedia keys [0x20 - 0x39] */
> +       [0x21] = KEY_SCREENLOCK,
> +       [0x22] = KEY_WLAN,
> +       [0x23] = KEY_BLUETOOTH,
> +       [0x24] = KEY_TOUCHPAD_TOGGLE,
> +       [0x25] = KEY_BRIGHTNESSDOWN,
> +       [0x26] = KEY_BRIGHTNESSUP,
> +       [0x27] = KEY_BRIGHTNESS_AUTO,
> +       [0x28] = KEY_PRINT,
> +       [0x30] = KEY_WWW,
> +       [0x31] = KEY_CONFIG,
> +       [0x32] = KEY_PREVIOUSSONG,
> +       [0x33] = KEY_PLAYPAUSE,
> +       [0x34] = KEY_NEXTSONG,
> +       [0x35] = KEY_MUTE,
> +       [0x36] = KEY_VOLUMEDOWN,
> +       [0x37] = KEY_VOLUMEUP,
> +};
> +
> +static void asus_ec_keys_report_key(struct input_dev *dev, unsigned int code,
> +                                   unsigned int key, bool value)
> +{
> +       input_event(dev, EV_MSC, MSC_SCAN, code);
> +       input_report_key(dev, key, value);
> +       input_sync(dev);
> +}
> +
> +static int asus_ec_keys_process_key(struct input_dev *dev, u8 code)
> +{
> +       struct asus_ec_keys_data *priv = dev_get_drvdata(dev->dev.parent);
> +       unsigned int key = 0;
> +
> +       if (code == 0)
> +               return NOTIFY_DONE;
> +
> +       /* Flip special key mode state when pressing SCREEN LOCK + R ALT */
> +       if (priv->special_key_pressed && code == 1) {
> +               priv->special_key_mode = !priv->special_key_mode;
> +               return NOTIFY_DONE;
> +       }
> +
> +       /*
> +        * Relocate code to second "page" if pressed state XOR's mode state
> +        * This way special key will invert the current mode
> +        */
> +       if (priv->special_key_mode ^ priv->special_key_pressed)
> +               code += ASUSEC_EXT_KEY_CODES;
> +
> +       if (code < dev->keycodemax) {
> +               unsigned short *map = dev->keycode;
> +
> +               key = map[code];
> +       }
> +
> +       if (!key)
> +               key = KEY_UNKNOWN;
> +
> +       asus_ec_keys_report_key(dev, code, key, 1);
> +       asus_ec_keys_report_key(dev, code, key, 0);
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int asus_ec_keys_notify(struct notifier_block *nb,
> +                              unsigned long action, void *data_)
> +{
> +       struct asus_ec_keys_data *priv =
> +               container_of(nb, struct asus_ec_keys_data, nb);
> +       u8 *data = data_;
> +
> +       if (action & ASUSEC_SMI_MASK)
> +               return NOTIFY_DONE;
> +
> +       if (action & ASUSEC_SCI_MASK)
> +               return asus_ec_keys_process_key(priv->xidev, data[2]);
> +
> +       return NOTIFY_DONE;
> +}
> +
> +static void asus_ec_keys_setup_keymap(struct asus_ec_keys_data *priv)
> +{
> +       struct input_dev *dev = priv->xidev;
> +       unsigned int i;
> +
> +       BUILD_BUG_ON(ARRAY_SIZE(priv->keymap) < ARRAY_SIZE(asus_ec_dock_ext_keys));
> +
> +       dev->keycode = priv->keymap;
> +       dev->keycodesize = sizeof(*priv->keymap);
> +       dev->keycodemax = ARRAY_SIZE(priv->keymap);
> +
> +       input_set_capability(dev, EV_MSC, MSC_SCAN);
> +       input_set_capability(dev, EV_KEY, KEY_UNKNOWN);
> +
> +       for (i = 0; i < ARRAY_SIZE(asus_ec_dock_ext_keys); i++) {
> +               unsigned int code = asus_ec_dock_ext_keys[i];
> +
> +               if (!code)
> +                       continue;
> +
> +               __set_bit(code, dev->keybit);
> +               priv->keymap[i] = code;
> +       }
> +}
> +
> +static int asus_ec_keys_register_handler(struct device *dev,
> +                                        struct asus_ec_keys_data *priv)
> +{
> +       struct i2c_client *parent = to_i2c_client(dev->parent);
> +       int error;
> +
> +       priv->input_handler.event = asus_ec_input_event;
> +       priv->input_handler.connect = asus_ec_input_connect;
> +       priv->input_handler.disconnect = asus_ec_input_disconnect;
> +       priv->input_handler.id_table = asus_ec_input_ids;
> +       priv->input_handler.passive_observer = true;
> +       priv->input_handler.private = priv;
> +       priv->input_handler.name = devm_kasprintf(dev, GFP_KERNEL,
> +                                                 "%s-media-handler",
> +                                                 priv->ec->name);
> +       if (!priv->input_handler.name)
> +               return -ENOMEM;
> +
> +       priv->kbc_phys = devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x/serio0",
> +                                       i2c_adapter_id(parent->adapter),
> +                                       parent->addr);
> +       if (!priv->kbc_phys)
> +               return -ENOMEM;
> +
> +       error = input_register_handler(&priv->input_handler);
> +       if (error)
> +               return error;
> +
> +       return 0;
> +}
> +
> +static int asus_ec_keys_probe(struct platform_device *pdev)
> +{
> +       struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
> +       struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
> +       struct device *dev = &pdev->dev;
> +       struct asus_ec_keys_data *priv;
> +       int error;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, priv);
> +       priv->ec = ec;
> +
> +       priv->xidev = devm_input_allocate_device(dev);
> +       if (!priv->xidev)
> +               return -ENOMEM;
> +
> +       priv->xidev->name = devm_kasprintf(dev, GFP_KERNEL, "%s Keyboard Ext",
> +                                          ec->model);
> +       priv->xidev->phys = devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x",
> +                                          i2c_adapter_id(parent->adapter),
> +                                          parent->addr);
> +
> +       if (!priv->xidev->name || !priv->xidev->phys)
> +               return -ENOMEM;
> +
> +       asus_ec_keys_setup_keymap(priv);
> +
> +       error = input_register_device(priv->xidev);
> +       if (error)
> +               return dev_err_probe(dev, error,
> +                                    "failed to register extension keys\n");
> +
> +       error = asus_ec_keys_register_handler(dev, priv);
> +       if (error) {
> +               input_unregister_device(priv->xidev);
> +               return error;
> +       }
> +
> +       priv->nb.notifier_call = asus_ec_keys_notify;
> +
> +       error = blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
> +       if (error) {
> +               input_unregister_device(priv->xidev);
> +               input_unregister_handler(&priv->input_handler);
> +               return error;
> +       }
> +
> +       return 0;
> +}
> +
> +static void asus_ec_keys_remove(struct platform_device *pdev)
> +{
> +       struct asus_ec_keys_data *priv = platform_get_drvdata(pdev);
> +       struct asusec_core *ec = priv->ec;
> +
> +       blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
> +       input_unregister_handler(&priv->input_handler);
> +       input_unregister_device(priv->xidev);
> +}
> +
> +static struct platform_driver asus_ec_keys_driver = {
> +       .driver.name = "asus-transformer-ec-keys",
> +       .probe = asus_ec_keys_probe,
> +       .remove = asus_ec_keys_remove,
> +};
> +module_platform_driver(asus_ec_keys_driver);
> +
> +MODULE_ALIAS("platform:asus-transformer-ec-keys");
> +MODULE_AUTHOR("Michał Mirosław <[email protected]>");
> +MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver");
> +MODULE_LICENSE("GPL");
> --
> 2.53.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.