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 | |
20 | enum kbd_subcmds { |
21 | CNFG_WAKE = 3, |
22 | CNFG_WAKE_KEY_REPORTING, |
23 | SET_LEDS = 0xed, |
24 | ENABLE_KBD = 0xf4, |
25 | DISABLE_KBD, |
26 | }; |
27 | |
28 | static unsigned char keycodes[ARRAY_SIZE(code_tab_102us) |
29 | + ARRAY_SIZE(extcode_tab_us102)]; |
30 | |
31 | struct nvec_keys { |
32 | struct input_dev *input; |
33 | struct notifier_block notifier; |
34 | struct nvec_chip *nvec; |
35 | bool caps_lock; |
36 | }; |
37 | |
38 | static struct nvec_keys keys_dev; |
39 | |
40 | static 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(nvec: keys_dev.nvec, data: buf, size: sizeof(buf)); |
51 | } |
52 | |
53 | static 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(dev: keys_dev.input, code: code_tabs[_size][code], |
76 | value: !state); |
77 | input_sync(dev: keys_dev.input); |
78 | |
79 | return NOTIFY_STOP; |
80 | } |
81 | |
82 | return NOTIFY_DONE; |
83 | } |
84 | |
85 | static 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, data: buf, size: sizeof(buf)); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int nvec_kbd_probe(struct platform_device *pdev) |
107 | { |
108 | struct nvec_chip *nvec = dev_get_drvdata(dev: 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(nr: keycodes[i], addr: idev->keybit); |
139 | |
140 | clear_bit(nr: 0, addr: 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, nb: &keys_dev.notifier, events: 0); |
149 | |
150 | /* Enable keyboard */ |
151 | nvec_write_async(nvec, data: enable_kbd, size: 2); |
152 | |
153 | /* configures wake on special keys */ |
154 | nvec_write_async(nvec, data: cnfg_wake, size: 4); |
155 | /* enable wake key reporting */ |
156 | nvec_write_async(nvec, data: cnfg_wake_key_reporting, size: 3); |
157 | |
158 | /* Disable caps lock LED */ |
159 | nvec_write_async(nvec, data: clear_leds, size: sizeof(clear_leds)); |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | static void nvec_kbd_remove(struct platform_device *pdev) |
165 | { |
166 | struct nvec_chip *nvec = dev_get_drvdata(dev: 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, data: uncnfg_wake_key_reporting, size: 3); |
171 | nvec_write_async(nvec, data: disable_kbd, size: 2); |
172 | nvec_unregister_notifier(dev: nvec, nb: &keys_dev.notifier); |
173 | } |
174 | |
175 | static struct platform_driver nvec_kbd_driver = { |
176 | .probe = nvec_kbd_probe, |
177 | .remove_new = nvec_kbd_remove, |
178 | .driver = { |
179 | .name = "nvec-kbd" , |
180 | }, |
181 | }; |
182 | |
183 | module_platform_driver(nvec_kbd_driver); |
184 | |
185 | MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>" ); |
186 | MODULE_DESCRIPTION("NVEC keyboard driver" ); |
187 | MODULE_ALIAS("platform:nvec-kbd" ); |
188 | MODULE_LICENSE("GPL" ); |
189 | |