nvec_kbd.c (4535B)
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 if (!idev) 127 return -ENOMEM; 128 idev->name = "nvec keyboard"; 129 idev->phys = "nvec"; 130 idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED); 131 idev->ledbit[0] = BIT_MASK(LED_CAPSL); 132 idev->event = nvec_kbd_event; 133 idev->keycode = keycodes; 134 idev->keycodesize = sizeof(unsigned char); 135 idev->keycodemax = ARRAY_SIZE(keycodes); 136 137 for (i = 0; i < ARRAY_SIZE(keycodes); ++i) 138 set_bit(keycodes[i], idev->keybit); 139 140 clear_bit(0, idev->keybit); 141 err = input_register_device(idev); 142 if (err) 143 return err; 144 145 keys_dev.input = idev; 146 keys_dev.notifier.notifier_call = nvec_keys_notifier; 147 keys_dev.nvec = nvec; 148 nvec_register_notifier(nvec, &keys_dev.notifier, 0); 149 150 /* Enable keyboard */ 151 nvec_write_async(nvec, enable_kbd, 2); 152 153 /* configures wake on special keys */ 154 nvec_write_async(nvec, cnfg_wake, 4); 155 /* enable wake key reporting */ 156 nvec_write_async(nvec, cnfg_wake_key_reporting, 3); 157 158 /* Disable caps lock LED */ 159 nvec_write_async(nvec, clear_leds, sizeof(clear_leds)); 160 161 return 0; 162} 163 164static int nvec_kbd_remove(struct platform_device *pdev) 165{ 166 struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); 167 char disable_kbd[] = { NVEC_KBD, DISABLE_KBD }, 168 uncnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING, 169 false }; 170 nvec_write_async(nvec, uncnfg_wake_key_reporting, 3); 171 nvec_write_async(nvec, disable_kbd, 2); 172 nvec_unregister_notifier(nvec, &keys_dev.notifier); 173 174 return 0; 175} 176 177static struct platform_driver nvec_kbd_driver = { 178 .probe = nvec_kbd_probe, 179 .remove = nvec_kbd_remove, 180 .driver = { 181 .name = "nvec-kbd", 182 }, 183}; 184 185module_platform_driver(nvec_kbd_driver); 186 187MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); 188MODULE_DESCRIPTION("NVEC keyboard driver"); 189MODULE_ALIAS("platform:nvec-kbd"); 190MODULE_LICENSE("GPL");