1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
|
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/input.h>
#include "keymap.h"
/*
* defines whether to access the gpios so other parts of the kernel module can
* be tested on systems w/o the prerequisite GPIOs
*/
#define USE_GPIO 1
/*
* On the prototype model, the diodes were soldered backwards, so that the
* negative ends were facing the cpins.
* as outputs they seem to opendrain(transistor to high, resistor to low)
* this means that the cpins can not pull the rlabels down. we can not
* work around this by using the pins as active_low
* we now have to use rpins as the output and cpins as the input because
* we can only use active_high, and the positive side of the diodes are
* facing the rpins. This means we don't have short-circuit since the diodes
* are still connected to the cpins. however, when shorting them in
* production it seems that the problem is not bad enough to cause the board
* to shut down.
*/
/*
* GPIO pins
* Unsigned ints containing the BCM pin numbers for the pins used, __initdata
* helps with the loading and unloading of data in memory
*/
static unsigned int column_pins[10] = {10, 24, 23, 22, 27, 18, 17, 15, 14, 4};
static unsigned int row_pins[3] = {3, 6, 5};
/*
* GPIO labels
*/
static char * clabels[10] = {"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",};
static char * rlabels[3] = {"R0", "R1", "R2"};
/*
* task struct for setting up the thread
*/
static struct task_struct *update_task;
/*
* input.h input device
*/
struct input_dev * skey_dev;
/*
* Name and ID, for use with input.h
*/
struct input_id skey_id;
char * skey_name = "Switch Keyboard for the Omnicom";
/*
* Array to hold the keystate for the board
* a char is assumed to be atleast 8 bits, the bit KEYSTATE_PHYS holds the previous physical
* states for detecting rising and falling edges whereas the other ones hold the previous states
* of the 3 keymaps when stickymode is turned on.
*/
char keystate[3][10]= {0};
#define KEYSTATE_PHYS 0x1
#define KEYSTATE_ALPHA 0x2
#define KEYSTATE_NUMBER 0x4
#define KEYSTATE_SPECIAL 0x8
/*
* current keymapping,\
* 0 - ALPHA
* 1 - NUM
* 2 - SPECIAL
* 3 - SPECIAL with mouse
*/
int current_keymap = 0;
/*
* Stickymode
*/
int stickymode = 0;
//number of pixels mousemove buttons do
#define MOUSE_DIST 5
//same but for scrollwheel
#define WHEEL_DIST 5
/*
* clears the state of stickymode keystates, sends a key up event if it was previously pressed
*
*/
void clearkeystate(void) {
int row_index = 0;
int column_index = 0;
int map_index = 0; //range of 0 to 2, which are KEYSTATE_ALPHA to KEYSTATE_SPECIAL
int keystate_mask; // the actual map used
while (map_index < 3) {
//gets mask from map_index
switch(map_index) {
case 0:
keystate_mask = KEYSTATE_ALPHA;
break;
case 1:
keystate_mask = KEYSTATE_NUMBER;
break;
case 2:
keystate_mask = KEYSTATE_SPECIAL;
break;
}
row_index = 0;
while (row_index < 3) {
column_index = 0;
while (column_index < 10) {
//sends a button released event if it is not sticky mode
if (keystate[row_index][column_index] & keystate_mask) {
//reports key up
input_report_key(skey_dev, skey_keymap[map_index][row_index][column_index], 0);
input_sync(skey_dev);
//sets the bit to low.
keystate[row_index][column_index] = (~keystate_mask) & keystate[row_index][column_index];
}
column_index++;
}
row_index++;
}
map_index++;
}
return;
}
/*
* The keystates are processed as following, the current read value is compared with the
* previous one stored in KEYSTATE_PHYS to detect a rising or falling edge. for regular keys,
* the corresponding keycode from skey_keymap is sent, with the current_keymap. if stickymode
* is on, then it toggles the value stored in KEYSTATE_ALPHA, *NUMBER or *SPECIAL depending on
* current_keymap. special keys that change the keymap and the mouse position are only triggered
* on rising level, when current_keymap is 3, or special map with mouse, the KEYSTATE_SPECIAL
* mask is used.
*/
/*
* This function is called by skey_update_thread every time it reads the keyboard, and
* implements the above. returns -EINVAL if the column and row are out of bouns, but that's
* not going to happen.
*/
int processkey(int row, int column, int state) {
//sanity checks
if ((column < 10) && (column >= 0) && (row < 3) && (row >= 0)) {
} else {
/*if this happens, whatever is calling this function is not expected to handle
* this
*/
printk(KERN_ALERT "skey: keypress data sent to processkey() was invalid\n");
return -EINVAL;
}
//finds the KEYSTATE_* bitmask to use with the current keymapping
int keystate_bitmask;
switch (current_keymap) {
case 0:
keystate_bitmask = KEYSTATE_ALPHA;
break;
case 1:
keystate_bitmask = KEYSTATE_NUMBER;
break;
case 2:
keystate_bitmask = KEYSTATE_SPECIAL;
break;
case 3:
keystate_bitmask = KEYSTATE_SPECIAL;
break;
//if current_keymap is out of range [0,3]
default:
return -EINVAL;
break;
}
//falling edge
if ((KEYSTATE_PHYS & keystate[row][column]) && !state) {
//after the previous keystate is checked, it is updated
if (state) {
keystate[row][column] = KEYSTATE_PHYS | keystate[row][column];
} else {
keystate[row][column] = (~KEYSTATE_PHYS) & keystate[row][column];
}
if (!stickymode) {
//reports key up
input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 0);
input_sync(skey_dev);
}
//rising edge
}else if (!(KEYSTATE_PHYS & keystate[row][column]) && state) {
//after the previous keystate is checked, it is updated
if (state) {
keystate[row][column] = KEYSTATE_PHYS | keystate[row][column];
} else {
keystate[row][column] = (~KEYSTATE_PHYS) & keystate[row][column];
}
/*
*handles keys that have special behavior, and to which stickykey do not apply
* if sticky keys don't apply then only the falling edge applies
*/
switch (skey_keymap[current_keymap][row][column]) {
case KEY_ALPHAMAP:
current_keymap = 0;
return 0;
break;
case KEY_NUMBERMAP:
current_keymap = 1;
return 0;
break;
case KEY_SPECIALMAP:
current_keymap = 2;
return 0;
break;
case KEY_STICKYMAP:
stickymode = !stickymode;
if (!stickymode) {
clearkeystate();
}
return 0;
break;
case KEY_MS_2ND:
if (current_keymap == 2) {
current_keymap = 3;
} else if (current_keymap == 3) {
current_keymap = 2;
}
return 0;
break;
case MS_UP:
input_event(skey_dev, EV_REL, REL_Y, +MOUSE_DIST);
input_sync(skey_dev);
return 0;
break;
case MS_DOWN:
input_event(skey_dev, EV_REL, REL_Y, -MOUSE_DIST);
input_sync(skey_dev);
return 0;
break;
case MS_LEFT:
input_event(skey_dev, EV_REL, REL_X, -MOUSE_DIST);
input_sync(skey_dev);
return 0;
break;
case MS_RIGHT:
input_event(skey_dev, EV_REL, REL_X, +MOUSE_DIST);
input_sync(skey_dev);
return 0;
break;
case MS_SCRL_UP:
input_event(skey_dev, EV_REL, REL_WHEEL, +WHEEL_DIST);
input_sync(skey_dev);
return 0;
break;
case MS_SCRL_DOWN:
input_event(skey_dev, EV_REL, REL_WHEEL, -WHEEL_DIST);
input_sync(skey_dev);
return 0;
break;
}
//if it's stickymode, toggle the state stored and send corresponding vent
if (stickymode) {
//it was high before, so now it's being toggled off
if (keystate[row][column] & keystate_bitmask) {
input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 0);
input_sync(skey_dev);
keystate[row][column] = (~keystate_bitmask) & keystate[row][column];
//it was low before, so now it's being toggled on
} else {
input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 1);
input_sync(skey_dev);
keystate[row][column] = keystate_bitmask | keystate[row][column];
}
//otherwise just send a key down event
} else {
input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 1);
input_sync(skey_dev);
}
}
return 0;
}
/*
*This function is called every timer-period to actually read the keyboard
*/
int skey_update_thread (void *data) {
printk(KERN_INFO "skey: starting update thread\n");
//thread stuff
//thread priority
const struct sched_param PARAM = { .sched_priority = 45};
//sets the thread to be kinda realtime
sched_setscheduler(current, SCHED_FIFO, &PARAM);
printk(KERN_INFO "skey: started update thread\n");
if (!USE_GPIO) {
printk(KERN_ALERT "skey: currently in test mode with gpio use disabled\n");
}
while (1) {
if (USE_GPIO) {
//reads the keyboard
int row_index = 0;
int column_index = 0;
while (row_index < 3) {
//sets the column pin high for reading
gpio_set_value_cansleep(row_pins[row_index], 1);
//gives it some buffer time
usleep_range(10,10);
//reads with all the rows
column_index = 0;
while (column_index < 10) {
if (gpio_get_value_cansleep(column_pins[column_index])) {
processkey(row_index, column_index, 1);
} else {
processkey(row_index, column_index, 0);
}
column_index++;
}
//sets the row pin low
gpio_set_value_cansleep(row_pins[row_index], 0);
usleep_range(10,10);
row_index++;
}
}
//sleep 500us= .5ms = 0.0005 seconds
// 1 / 0.0005 = 2000 hz fastest, but it's really 1/0.00052
usleep_range(500,500);
//for when the thread has to stop
if (kthread_should_stop()) {
break;
}
}
return 0;
}
/*
* This function is called when the module is loaded
* __init functions similar to __initdata
*/
static int __init skey_init (void) {
printk(KERN_INFO "skey: module initiating...\n");
if (USE_GPIO) {
int i = 0;
//claims the column pins
while (i < 10) {
//checks to see if the gpios are valid
if (!gpio_is_valid(column_pins[i])) {
printk(KERN_ALERT "skey: column_pins[%d], BCM %d request invalid\n", i, column_pins[i]);
return -EINVAL;
}
//requests access of the GPIO
if (gpio_request_one(column_pins[i], GPIOF_IN ,clabels[i])) {
printk(KERN_ALERT "skey: column_pins[%d], BCM %d request failed\n", i, column_pins[i]);
return -EINVAL;
}
if (gpio_direction_input(column_pins[i])) {
printk(KERN_ALERT "skey: column_pins[%d], BCM %d, set output failed\n",i, column_pins[i]);
}
i++;
}
//ditto for row pins
int j = 0;
while (j < 3) {
//checks to see if the gpios are valid
if (!gpio_is_valid(row_pins[j])) {
printk(KERN_ALERT "skey: row_pins[%d], BCM %d request invalid\n", j, row_pins[j]);
return -EINVAL;
}
//requests access of the GPIO
if (gpio_request_one(row_pins[j], GPIOF_OUT_INIT_LOW ,rlabels[j])) {
printk(KERN_ALERT "skey: row_pins[%d], BCM %d request failed\n", j, row_pins[j]);
return -EINVAL;
}
if (gpio_direction_output(row_pins[j], 0)) {
printk(KERN_ALERT "skey: row_pins[%d], BCM %d, set output failed\n",j, row_pins[j]);
}
j++;
}
} else {
printk(KERN_ALERT "skey: currently in test mode with gpio use disabled\n");
}
/*
* sets up input.h device
*/
skey_dev = input_allocate_device();
if (!skey_dev) {
printk(KERN_ALERT "skey: unable to allocate input device\n");
return -ENOMEM;
}
// declares the event code and typesthe device emmits
set_bit(EV_KEY, skey_dev->evbit);
set_bit(EV_REL, skey_dev->evbit);
set_bit(KEY_CAPSLOCK, skey_dev->keybit);
/*
* skey_dev->name and skey_dev->id are metadata that are required for X11
* and libinput to use skey_dev
*/
//sets the name of skey_dev
skey_dev->name = skey_name;
//sets ids
skey_id.bustype = BUS_HOST;
skey_id.vendor = 13398;
skey_id.product = 13398;
skey_id.version = 1;
skey_dev->id = skey_id;
// registers the device
if (input_register_device(skey_dev)) {
input_free_device(skey_dev);
return -EINVAL;
}
//sets up update thread
printk("skey: setting up update thread...\n");
update_task = kthread_run(skey_update_thread, NULL, "skey_update_thread");
printk(KERN_INFO "skey: module finished initiating\n");
//explicitly sets pin 4 and 15 to be inputs.
gpio_direction_input(4);
gpio_direction_input(15);
gpio_set_value_cansleep(4, 0);
gpio_set_value_cansleep(15, 0);
return 0;
}
/*
* This function is called when the module is unloaded
*
*/
static void __exit skey_exit (void) {
printk(KERN_INFO "skey: module exiting...\n");
if (USE_GPIO) {
//frees column and row pins
int i = 0;
while (i < 10) {
gpio_free(column_pins[i]);
i++;
}
int j = 0;
while (j < 3) {
gpio_free(row_pins[j]);
j++;
}
} else {
printk(KERN_ALERT "skey: currently in test mode with gpio use disabled\n");
}
//deletes the thread
kthread_stop(update_task);
//frees input device
input_free_device(skey_dev);
printk(KERN_INFO "skey: module exited\n");
return;
}
/*
* Registers the init and exit functions
*/
module_init(skey_init);
module_exit(skey_exit);
/*
* registers module metadata
*/
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haoran S. Diao <1339802534.kk@gmail.com>");
MODULE_DESCRIPTION("Driver for the omnicom switch keyboard");
/*
*this sets the name of device files used by this module, this is used
*when the kernel makes devices automatically
*/
MODULE_SUPPORTED_DEVICE("skeydev");
|