summaryrefslogtreecommitdiff
path: root/skey.c
blob: 7d1e055805370474e6610bbd7d1ca6abd4b43443 (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
#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
/*
 * 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
 */
struct input_id skey_id;
char * skey_name = "Switch Keyboard for the Omnicom";
/*
 *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
	const struct sched_param PARAM = { .sched_priority = 45};
	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");
	}
	//a counter for testing prints
	int i = 0;
	int j = 0;
	while (1) {
		i++;
		i = i % 500;
		if (!i) {
			printk(KERN_INFO "skey: update thread printed to kernel log every 1s\n");
			//reports the a button pressed
			input_report_key(skey_dev,KEY_CAPSLOCK,j);
			input_sync(skey_dev);
			j = !j;
		}
		if (USE_GPIO) {
			//reads the keyboard
			int row_index = 0;
			int column_index = 0;
			while (column_index < 10) {
				//sets the column pin high for reading
				gpio_set_value(column_pins[column_index], 1);
				//reads with all the rows
				row_index = 0;
				while (row_index < 3) {
					if (gpio_get_value(row_pins[row_index])) {
						printk(KERN_INFO "skey: key pressed at column %d, row %d\n", column_index, row_index);
					} else {
						printk(KERN_INFO "skey: key not pressed at column %d, row %d\n", column_index, row_index);
					}
					row_index++;
				}
				//sets the row pin low
				gpio_set_value(column_pins[column_index], 0);
				column_index++;
			}
		}
		//sleep 2000us= 2ms = 0.002 seconds
		usleep_range(2000, 2000);	
		//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_OUT_INIT_LOW ,clabels[i])) {
				printk(KERN_ALERT "skey: column_pins[%d], BCM %d request failed\n", i, column_pins[i]);
				return -EINVAL;
			}
			if (gpio_direction_output(column_pins[i], 0)) {
				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_DIR_IN ,rlabels[j])) {
				printk(KERN_ALERT "skey: row_pins[%d], BCM %d request failed\n", j, row_pins[j]);
				return -EINVAL;
			}
			if (gpio_direction_input(row_pins[j])) {
				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");
	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");