#include #include #include #include #include #include #include #include #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) { //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; } printk("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) { printk(KERN_INFO "skey: falling edge detected for r:%d,c:%d\n". row. column); //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) { printk(KERN_INFO "skey: rising edge detected for r:%d,c:%d\n". row. column); //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) { //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); printk(KERN_INFO, "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 usleep_range(1000000,1000000); //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) { //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); 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; 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");