summaryrefslogtreecommitdiff
path: root/skey.c
blob: 321250485d504d48d69d942f423ac10b4c0e4219 (plain)
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
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
#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
/*
 * toggles debug mode,
 */
#define DEBUGMODE 0
/*
 * 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 1
//same but for scrollwheel
#define WHEEL_DIST 1
/*
 * 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;
}
/*
 * Updates KEYSTATE_PHYS
 */
void update_phys(int row, int column, int state) {
	if (state) {
		keystate[row][column] = KEYSTATE_PHYS | keystate[row][column];
	} else {
		keystate[row][column] = (~KEYSTATE_PHYS) & keystate[row][column];
	}
}
/*
 * 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) {
	//the bitmask to use for the current keymap mode
	int keystate_bitmask;
	//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	
	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:
			printk(KERN_ALERT "skey: current_keymap out of bounds\n");
			return -EINVAL;
		break;
	}
	if ((KEYSTATE_PHYS & keystate[row][column])) {
		if(DEBUGMODE){printk(KERN_DEBUG "skey: previous KEYSTATE_PHYS for r:%d,c:%d was %d\n", row, column, (KEYSTATE_PHYS & keystate[row][column]));}
	}
	//falling edge
	if ((KEYSTATE_PHYS & keystate[row][column]) && !state) {
		if(DEBUGMODE){printk(KERN_DEBUG "skey: falling edge detected for r:%d,c:%d\n", row, column);}
		if (!stickymode) {
			if(DEBUGMODE){printk(KERN_DEBUG "skey: stickymode off with falling edge, reporting key up with keycode %ld\n", skey_keymap[current_keymap][row][column]);}
			//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) {
		if(DEBUGMODE){printk(KERN_DEBUG "skey: rising edge detected for r:%d,c:%d\n", 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:
				if(DEBUGMODE){printk(KERN_DEBUG "skey: changing to keymap ALPHA\n");}
				current_keymap = 0;
				update_phys(row, column, state);
				return 0;
			break;
			case KEY_NUMBERMAP:
				if(DEBUGMODE){printk(KERN_DEBUG "skey: changing to keymap NUM\n");}
				current_keymap = 1;
				update_phys(row, column, state);
				return 0;
			break;
			case KEY_SPECIALMAP:
				if(DEBUGMODE){printk(KERN_DEBUG "skey: changing to keymap SPECIAL\n");}
				current_keymap = 2;
				update_phys(row, column, state);
				return 0;
			break;
			case KEY_STICKYMAP:
				if(DEBUGMODE){printk(KERN_DEBUG "skey: toggling stickymode\n");}
				stickymode = !stickymode;
				if (!stickymode) {
					clearkeystate();
					if(DEBUGMODE){printk(KERN_DEBUG "skey: cleaned stickmode states\n");}
				}
				update_phys(row, column, state);
				return 0;
			break;
			case KEY_MS_2ND:
				if(DEBUGMODE){printk(KERN_DEBUG "skey: toggling MS_2ND\n");}
				if (current_keymap == 2) {
					current_keymap = 3;
				} else if (current_keymap == 3) {
					current_keymap = 2;
				}
				update_phys(row, column, state);
				return 0;
			break;
			case MS_UP:
				input_event(skey_dev, EV_REL, REL_Y, -MOUSE_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;
			case MS_DOWN:
				input_event(skey_dev, EV_REL, REL_Y, +MOUSE_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;	
			case MS_LEFT:
				input_event(skey_dev, EV_REL, REL_X, -MOUSE_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;
			case MS_RIGHT:
				input_event(skey_dev, EV_REL, REL_X, +MOUSE_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;
			case MS_SCRL_UP:
				input_event(skey_dev, EV_REL, REL_WHEEL, +WHEEL_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;
			case MS_SCRL_DOWN:
				input_event(skey_dev, EV_REL, REL_WHEEL, -WHEEL_DIST);
				input_sync(skey_dev);
				update_phys(row, column, state);
				return 0;
			break;
		}
		//if it's stickymode, toggle the state  stored and send corresponding vent
		if (stickymode) {
			if(DEBUGMODE){printk(KERN_DEBUG "skey: stickymoe on with rising edge\n");}
			//it was high before, so now it's being toggled off
			if (keystate[row][column] & keystate_bitmask) {
				if(DEBUGMODE){printk(KERN_DEBUG "skey: stickymode sending key up with code %ld\n", skey_keymap[current_keymap][row][column]);}
				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 {
				if(DEBUGMODE){printk(KERN_DEBUG "skey: stickymode sending key down with code %ld\n", skey_keymap[current_keymap][row][column]);}
				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 {
			if(DEBUGMODE){printk(KERN_DEBUG "skey:skey rising edge w/o stickymode, sending key down with keycode %ld\n", skey_keymap[current_keymap][row][column]);}
			input_report_key(skey_dev, skey_keymap[current_keymap][row][column], 1);
			input_sync(skey_dev);
		}
	}
	//after the previous keystate is checked, it is updated
	update_phys(row, column, state);
	return 0;
}

/*
 *This function is called every timer-period to actually read the keyboard
 */
int skey_update_thread (void *data) {
	//thread stuff
	//thread priority
	const struct sched_param PARAM = { .sched_priority = 45};
	printk(KERN_INFO "skey: starting update thread\n");
	//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);
						if(DEBUGMODE){printk(KERN_DEBUG "skey:button pressed at r:%d,c:%d\n", row_index, column_index);}
					} 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
		if (DEBUGMODE) {
			usleep_range(1000000,1000000); // slower when debugging
		} else {
			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) {
	/*
	 * keycode register loop index vars, some keycodes have to be excluded because they 
	 * are custom, so we have to use a loop.
	 */
	int code_m = 0; //map
	int code_r = 0; //row
	int code_c = 0; //column
	unsigned long reg_keycode = 0; // the code to register
	printk(KERN_INFO "skey: module initiating...\n");
	if (USE_GPIO) {
		//indexes for row and column pin init loops
		int i = 0;
		int j = 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		
		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); //for the mousebuttons and keyboard keys
	set_bit(EV_REL, skey_dev->evbit); // for mouse and mousewheel movements
	//all the mouse stuff
	set_bit(REL_X, skey_dev->relbit);
	set_bit(REL_Y, skey_dev->relbit);
	set_bit(REL_WHEEL, skey_dev->relbit);
	//registers everything in skey_keymap except for the specal codes
	while (code_m < 4) {
		code_r = 0;
		while(code_r < 3) {
			code_c = 0;
			while (code_c < 10) {
				reg_keycode = skey_keymap[code_m][code_r][code_c];
				//excludes all the special codes not actually sent
				if (!(	reg_keycode == KEY_ALPHAMAP ||
					reg_keycode == KEY_NUMBERMAP ||
					reg_keycode == KEY_SPECIALMAP ||
					reg_keycode == KEY_STICKYMAP ||
					reg_keycode == KEY_MS_2ND ||
					reg_keycode == MS_UP ||
					reg_keycode == MS_LEFT||
					reg_keycode == MS_RIGHT ||
					reg_keycode == MS_DOWN ||
					reg_keycode == MS_SCRL_UP ||
					reg_keycode == MS_SCRL_DOWN )) {
					//registers the code
					set_bit(reg_keycode, skey_dev->keybit);
				}
				code_c++;
			}
			code_r++;
		}
		code_m++;
	}
	/*
	 * 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;
		int j = 0;
		while (i < 10) {
			gpio_free(column_pins[i]);
			i++;
		}
		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");