linux/drivers/staging/nvec/nvec_kbd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * nvec_kbd: keyboard driver for a NVIDIA compliant embedded controller
   4 *
   5 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net>
   6 *
   7 * Authors:  Pierre-Hugues Husson <phhusson@free.fr>
   8 *           Marc Dietrich <marvin24@gmx.de>
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/slab.h>
  13#include <linux/input.h>
  14#include <linux/delay.h>
  15#include <linux/platform_device.h>
  16
  17#include "nvec-keytable.h"
  18#include "nvec.h"
  19
  20enum kbd_subcmds {
  21        CNFG_WAKE = 3,
  22        CNFG_WAKE_KEY_REPORTING,
  23        SET_LEDS = 0xed,
  24        ENABLE_KBD = 0xf4,
  25        DISABLE_KBD,
  26};
  27
  28static unsigned char keycodes[ARRAY_SIZE(code_tab_102us)
  29                              + ARRAY_SIZE(extcode_tab_us102)];
  30
  31struct nvec_keys {
  32        struct input_dev *input;
  33        struct notifier_block notifier;
  34        struct nvec_chip *nvec;
  35        bool caps_lock;
  36};
  37
  38static struct nvec_keys keys_dev;
  39
  40static void nvec_kbd_toggle_led(void)
  41{
  42        char buf[] = { NVEC_KBD, SET_LEDS, 0 };
  43
  44        keys_dev.caps_lock = !keys_dev.caps_lock;
  45
  46        if (keys_dev.caps_lock)
  47                /* should be BIT(0) only, firmware bug? */
  48                buf[2] = BIT(0) | BIT(1) | BIT(2);
  49
  50        nvec_write_async(keys_dev.nvec, buf, sizeof(buf));
  51}
  52
  53static int nvec_keys_notifier(struct notifier_block *nb,
  54                              unsigned long event_type, void *data)
  55{
  56        int code, state;
  57        unsigned char *msg = data;
  58
  59        if (event_type == NVEC_KB_EVT) {
  60                int _size = (msg[0] & (3 << 5)) >> 5;
  61
  62/* power on/off button */
  63                if (_size == NVEC_VAR_SIZE)
  64                        return NOTIFY_STOP;
  65
  66                if (_size == NVEC_3BYTES)
  67                        msg++;
  68
  69                code = msg[1] & 0x7f;
  70                state = msg[1] & 0x80;
  71
  72                if (code_tabs[_size][code] == KEY_CAPSLOCK && state)
  73                        nvec_kbd_toggle_led();
  74
  75                input_report_key(keys_dev.input, code_tabs[_size][code],
  76                                 !state);
  77                input_sync(keys_dev.input);
  78
  79                return NOTIFY_STOP;
  80        }
  81
  82        return NOTIFY_DONE;
  83}
  84
  85static int nvec_kbd_event(struct input_dev *dev, unsigned int type,
  86                          unsigned int code, int value)
  87{
  88        struct nvec_chip *nvec = keys_dev.nvec;
  89        char buf[] = { NVEC_KBD, SET_LEDS, 0 };
  90
  91        if (type == EV_REP)
  92                return 0;
  93
  94        if (type != EV_LED)
  95                return -1;
  96
  97        if (code != LED_CAPSL)
  98                return -1;
  99
 100        buf[2] = !!value;
 101        nvec_write_async(nvec, buf, sizeof(buf));
 102
 103        return 0;
 104}
 105
 106static int nvec_kbd_probe(struct platform_device *pdev)
 107{
 108        struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
 109        int i, j, err;
 110        struct input_dev *idev;
 111        char    clear_leds[] = { NVEC_KBD, SET_LEDS, 0 },
 112                enable_kbd[] = { NVEC_KBD, ENABLE_KBD },
 113                cnfg_wake[] = { NVEC_KBD, CNFG_WAKE, true, true },
 114                cnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING,
 115                                                true };
 116
 117        j = 0;
 118
 119        for (i = 0; i < ARRAY_SIZE(code_tab_102us); ++i)
 120                keycodes[j++] = code_tab_102us[i];
 121
 122        for (i = 0; i < ARRAY_SIZE(extcode_tab_us102); ++i)
 123                keycodes[j++] = extcode_tab_us102[i];
 124
 125        idev = devm_input_allocate_device(&pdev->dev);
 126        idev->name = "nvec keyboard";
 127        idev->phys = "nvec";
 128        idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED);
 129        idev->ledbit[0] = BIT_MASK(LED_CAPSL);
 130        idev->event = nvec_kbd_event;
 131        idev->keycode = keycodes;
 132        idev->keycodesize = sizeof(unsigned char);
 133        idev->keycodemax = ARRAY_SIZE(keycodes);
 134
 135        for (i = 0; i < ARRAY_SIZE(keycodes); ++i)
 136                set_bit(keycodes[i], idev->keybit);
 137
 138        clear_bit(0, idev->keybit);
 139        err = input_register_device(idev);
 140        if (err)
 141                return err;
 142
 143        keys_dev.input = idev;
 144        keys_dev.notifier.notifier_call = nvec_keys_notifier;
 145        keys_dev.nvec = nvec;
 146        nvec_register_notifier(nvec, &keys_dev.notifier, 0);
 147
 148        /* Enable keyboard */
 149        nvec_write_async(nvec, enable_kbd, 2);
 150
 151        /* configures wake on special keys */
 152        nvec_write_async(nvec, cnfg_wake, 4);
 153        /* enable wake key reporting */
 154        nvec_write_async(nvec, cnfg_wake_key_reporting, 3);
 155
 156        /* Disable caps lock LED */
 157        nvec_write_async(nvec, clear_leds, sizeof(clear_leds));
 158
 159        return 0;
 160}
 161
 162static int nvec_kbd_remove(struct platform_device *pdev)
 163{
 164        struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
 165        char disable_kbd[] = { NVEC_KBD, DISABLE_KBD },
 166             uncnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING,
 167                                                false };
 168        nvec_write_async(nvec, uncnfg_wake_key_reporting, 3);
 169        nvec_write_async(nvec, disable_kbd, 2);
 170        nvec_unregister_notifier(nvec, &keys_dev.notifier);
 171
 172        return 0;
 173}
 174
 175static struct platform_driver nvec_kbd_driver = {
 176        .probe  = nvec_kbd_probe,
 177        .remove = nvec_kbd_remove,
 178        .driver = {
 179                .name = "nvec-kbd",
 180        },
 181};
 182
 183module_platform_driver(nvec_kbd_driver);
 184
 185MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>");
 186MODULE_DESCRIPTION("NVEC keyboard driver");
 187MODULE_ALIAS("platform:nvec-kbd");
 188MODULE_LICENSE("GPL");
 189