#pypp 0 // Iris: micro-kernel for a capability-based operating system. // boot-programs/gpio.ccp: GPIO driver, controlling all devices without special hardware. // Copyright 2009 Bas Wijnen // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "devices.hh" #define ARCH #include "arch.hh" // Interval between polls for keyboard events (when keys are pressed) #define ALARM_INTERVAL (HZ / 50) // GPIO pins for the devices (port.pin) // keyboard // Cols = 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.14, 3.15, 3.29 // Rows = 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7 // For some reason, it only works if the rows are input and the columns are output. // interrupts: yes, with all columns set to output 0, the first key press can be detected as an interrupt; some other events also trigger interrupts. // touchpad buttons // Left: 0.16 // Right: 0.13 // interrupts: yes, for any change. // Lock leds // Num lock: 2.22 // Caps lock: 0.27 // Scroll lock: 0.9 // interrupts: no, output only. // interrupt summary // Port 0: pin 0, 1, 2, 3, 4, 5, 6, 7: keyboard; 13, 16: touchpad // Port 1: None. // Port 2: None. // Port 3: None. enum event_type: KEYBOARD_EVENT TOUCHPAD_EVENT NUM_EVENTS enum cap_type: CAP_KEYBOARD = 32 CAP_TOUCHPAD CAP_LOCKLEDS CAP_PWM static Kernel::Cap events[NUM_EVENTS] static void event (event_type type, unsigned data): events[type].invoke (data) static void set_cb (event_type type): Kernel::free_cap (events[type]) events[type] = Kernel::get_arg () class DevKeyboard: static unsigned const encode[GPIO_KBD_NUM_COLS][GPIO_KBD_NUM_ROWS] unsigned keys[GPIO_KBD_NUM_COLS] bool scanning void parse (unsigned col, unsigned data): for unsigned row = 0; row < GPIO_KBD_NUM_ROWS; ++row: if (data ^ keys[col]) & (1 << row): unsigned code = encode[col][row] if data & (1 << row): code |= Keyboard::RELEASE event (KEYBOARD_EVENT, code) keys[col] = data // If any keys are pressed, scanning is required. if data != GPIO_KBD_ROW_MASK: scanning = true public: bool is_scanning (): return scanning void scan (): // Disable interrupts during scan. GPIO_GPIER (GPIO_KBD_ROW_PORT) &= ~GPIO_KBD_ROW_MASK // All columns are input. GPIO_GPDIR (GPIO_KBD_COL_PORT) &= ~GPIO_KBD_COL_MASK int const cols[GPIO_KBD_NUM_COLS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 29 } unsigned dir = GPIO_GPDIR (GPIO_KBD_COL_PORT) & ~GPIO_KBD_COL_MASK unsigned dat = GPIO_GPDR (GPIO_KBD_COL_PORT) & ~GPIO_KBD_COL_MASK // Set scanning to false before first parse. scanning = false // Clear all pins. This is only required once, because it's done at the end of the loop as well. GPIO_GPDR (GPIO_KBD_COL_PORT) = dat for unsigned col = 0; col < GPIO_KBD_NUM_COLS; ++col: // Set pin to output. GPIO_GPDIR (GPIO_KBD_COL_PORT) = dir | (1 << cols[col]) // Delay for stabalization. for unsigned i = 0; i < 100; ++i: GPIO_GPDIR (GPIO_KBD_COL_PORT) = dir | (1 << cols[col]) // Read the result. unsigned data = GPIO_GPDR (GPIO_KBD_ROW_PORT) & GPIO_KBD_ROW_MASK // Generate events. parse (col, data) // Set pin to get rid of capacitance effects. GPIO_GPDR (GPIO_KBD_COL_PORT) = dat | (1 << cols[col]) // Delay to make the above trick work. for unsigned i = 0; i < 100; ++i: GPIO_GPDIR (GPIO_KBD_COL_PORT) = dir | (1 << cols[col]) // Pin is set to input at the start of the loop. // set all to 0. GPIO_GPDR (GPIO_KBD_COL_PORT) = dat // set all to output. GPIO_GPDIR (GPIO_KBD_COL_PORT) = dir | GPIO_KBD_COL_MASK // Delay for stabalization. for unsigned i = 0; i < 100; ++i: GPIO_GPDIR (GPIO_KBD_COL_PORT) = dir | GPIO_KBD_COL_MASK // Set interrupts. unsigned data = GPIO_GPDR (GPIO_KBD_ROW_PORT) for unsigned i = 0; i < 8; ++i: if data & (1 << i): gpio_irq_low (GPIO_KBD_ROW_PORT, i) else: gpio_irq_high (GPIO_KBD_ROW_PORT, i) // Reenable interrupts. GPIO_GPIER (GPIO_KBD_ROW_PORT) |= GPIO_KBD_ROW_MASK DevKeyboard (): // Set all columns to output without pull-ups when set as input. GPIO_GPPUR (GPIO_KBD_COL_PORT) &= ~GPIO_KBD_COL_MASK GPIO_GPDIR (GPIO_KBD_COL_PORT) |= GPIO_KBD_COL_MASK // Set all rows to input and enable the pull-ups. GPIO_GPPUR (GPIO_KBD_ROW_PORT) |= GPIO_KBD_ROW_MASK GPIO_GPDIR (GPIO_KBD_ROW_PORT) &= ~GPIO_KBD_ROW_MASK // Initialize things in the same way as when a new callback is set up. send_initial () void send_initial (): for unsigned col = 0; col < GPIO_KBD_NUM_COLS; ++col: keys[col] = 0xff scan () enum Keys: N0, N1, N2, N3, N4, N5, N6, N7, N8, N9 A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z F1, F2, F3, F4, F5, F6, F7, F8, F9, F10 SPACE, TAB, ENTER, LBRACE, RBRACE, COMMA, PERIOD, MINUS, EQUAL, SLASH, BACKSLASH, SEMICOLON, EXTRA, BACKQUOTE, QUOTE UP, DOWN, LEFT, RIGHT ESC, INSERT, DELETE, BACKSPACE, PAUSE, FN, ZZZ, MENU, SYSRQ LSHIFT, RSHIFT, CTRL, ALT CAPS, NUM NONE = ~Keyboard::RELEASE unsigned const DevKeyboard::encode[GPIO_KBD_NUM_COLS][GPIO_KBD_NUM_ROWS] = { { PAUSE, NONE, NONE, NONE, NONE, NONE, CTRL, F5 }, { Q, TAB, A, ESC, Z, NONE, BACKQUOTE, N1 }, { W, CAPS, S, EXTRA, X, NONE, NONE, N2 }, { E, F3, D, F4, C, NONE, NONE, N3 }, { R, T, F, G, V, B, N5, N4 }, { U, Y, J, H, M, N, N6, N7 }, { I, RBRACE, K, F6, COMMA, NONE, EQUAL, N8 }, { O, F7, L, NONE, PERIOD, MENU, F8, N9 }, { NONE, NONE, NONE, SPACE, NUM, NONE, DELETE, NONE }, { NONE, BACKSPACE, NONE, NONE, ENTER, NONE, F9, NONE }, { NONE, NONE, NONE, ALT, NONE, NONE, NONE, SYSRQ }, { P, LBRACE, SEMICOLON, QUOTE, BACKSLASH, SLASH, MINUS, N0 }, { NONE, ZZZ, NONE, NONE, NONE, NONE, NONE, F10 }, { NONE, NONE, NONE, NONE, NONE, NONE, F2, NONE }, { NONE, NONE, NONE, NONE, NONE, NONE, INSERT, NONE }, { NONE, NONE, UP, DOWN, LEFT, RIGHT, NONE, NONE }, { NONE, LSHIFT, RSHIFT, NONE, NONE, NONE, F1, FN }} class Touchpad: unsigned old_state public: void check_events (): unsigned state = GPIO_GPDR (GPIO_TP_LEFT_PORT) if state & (1 << GPIO_TP_LEFT): gpio_irq_low (GPIO_TP_LEFT_PORT, GPIO_TP_LEFT) if (state ^ old_state) & (1 << GPIO_TP_LEFT): event (TOUCHPAD_EVENT, 0) else: gpio_irq_high (GPIO_TP_LEFT_PORT, GPIO_TP_LEFT) if (state ^ old_state) & (1 << GPIO_TP_LEFT): event (TOUCHPAD_EVENT, 0 | Keyboard::RELEASE) if state & (1 << GPIO_TP_RIGHT): gpio_irq_low (GPIO_TP_RIGHT_PORT, GPIO_TP_RIGHT) if (state ^ old_state) & (1 << GPIO_TP_RIGHT): event (TOUCHPAD_EVENT, 1) else: gpio_irq_high (GPIO_TP_RIGHT_PORT, GPIO_TP_RIGHT) if (state ^ old_state) & (1 << GPIO_TP_RIGHT): event (TOUCHPAD_EVENT, 1 | Keyboard::RELEASE) old_state = state // Ack interrupts. //GPIO_GPFR (GPIO_TP_LEFT_PORT) = (1 << GPIO_TP_LEFT) | (1 << GPIO_TP_RIGHT) Touchpad (): // Set pins to input with pull-ups. gpio_as_input (GPIO_TP_LEFT_PORT, GPIO_TP_LEFT) gpio_as_input (GPIO_TP_RIGHT_PORT, GPIO_TP_RIGHT) GPIO_GPPUR (0) |= (1 << GPIO_TP_LEFT) | (1 << GPIO_TP_RIGHT) // See if they are already pressed. Also set up interrupts. This is done like when a new callback is registered. send_initial () void send_initial (): old_state = 0 check_events () class Lockleds: // Note that num lock is in port 2. The others are in port 0. public: Lockleds (): gpio_as_output (GPIO_NUM_PORT, GPIO_NUM) gpio_as_output (GPIO_CAPS_PORT, GPIO_CAPS) gpio_as_output (GPIO_SCROLL_PORT, GPIO_SCROLL) GPIO_GPDR (GPIO_NUM_PORT) |= 1 << GPIO_NUM GPIO_GPDR (GPIO_CAPS_PORT) |= 1 << GPIO_CAPS GPIO_GPDR (GPIO_SCROLL_PORT) |= 1 << GPIO_SCROLL void set (unsigned state): if state & 4: GPIO_GPDR (GPIO_NUM_PORT) &= ~(1 << GPIO_NUM) else: GPIO_GPDR (GPIO_NUM_PORT) |= 1 << GPIO_NUM if state & 2: GPIO_GPDR (GPIO_CAPS_PORT) &= ~(1 << GPIO_CAPS) else: GPIO_GPDR (GPIO_CAPS_PORT) |= 1 << GPIO_CAPS if state & 1: GPIO_GPDR (GPIO_SCROLL_PORT) &= ~(1 << GPIO_SCROLL) else: GPIO_GPDR (GPIO_SCROLL_PORT) |= 1 << GPIO_SCROLL // Not really a gpio device, but it's so small, and uses gpio, so I include it here to avoid ipc. class Pwm: public: Pwm (): GPIO_GPDIR (GPIO_PWM_ENABLE_PORT) |= 1 << GPIO_PWM_ENABLE PWM_PER (0) = 300 void set_backlight (unsigned level): if level > 300: level = 300 PWM_DUT (0) = level if level: PWM_CTR (0) = 0x80 GPIO_GPDR (GPIO_PWM_ENABLE_PORT) |= 1 << GPIO_PWM_ENABLE else: PWM_CTR (0) = 0x00 GPIO_GPDR (GPIO_PWM_ENABLE_PORT) &= ~(1 << GPIO_PWM_ENABLE) // TODO: make it really work as a pwm instead of a switch; check if pwm1 is connected to anything. Kernel::Num start (): Kernel::schedule () map_gpio () map_pwm0 () for unsigned i = 0; i < NUM_EVENTS; ++i: events[i] = Kernel::alloc_cap () DevKeyboard kbd Touchpad tp Lockleds leds Pwm pwm Kernel::Caps c = Kernel::my_memory.create_caps (4) unsigned init_slot = c.use () Kernel::set_recv_arg (Kernel::Cap (init_slot, 0)) Kernel::my_receiver.create_capability (CAP_KEYBOARD) Kernel::set_recv_arg (Kernel::Cap (init_slot, 1)) Kernel::my_receiver.create_capability (CAP_TOUCHPAD) Kernel::set_recv_arg (Kernel::Cap (init_slot, 2)) Kernel::my_receiver.create_capability (CAP_LOCKLEDS) Kernel::set_recv_arg (Kernel::Cap (init_slot, 3)) Kernel::my_receiver.create_capability (CAP_PWM) Kernel::my_parent.ocall (c, INIT_SET_GPIO) free_cap (c) Kernel::free_slot (init_slot) if kbd.is_scanning (): Kernel::my_receiver.set_alarm (ALARM_INTERVAL) // Enable interrupts. All are in port 0. GPIO_GPIER (GPIO_KBD_ROW_PORT) = (1 << GPIO_TP_LEFT) | (1 << GPIO_TP_RIGHT) | GPIO_KBD_ROW_MASK Kernel::register_interrupt (IRQ_GPIO0) while true: Kernel::schedule () Kernel::wait () switch Kernel::recv.protected_data.l: case ~0: // Alarm. kbd.scan () if kbd.is_scanning (): Kernel::my_receiver.set_alarm (ALARM_INTERVAL) break case IRQ_GPIO0: // Always scan keyboard and touchpad on any interrupt. kbd.scan () tp.check_events () // Reregister the interrupt. Kernel::register_interrupt (IRQ_GPIO0) break case CAP_KEYBOARD: kdebug ("gpio: keyboard callback registered.\n") set_cb (KEYBOARD_EVENT) Kernel::recv.reply.invoke () kbd.send_initial () event (KEYBOARD_EVENT, ~0) break case CAP_TOUCHPAD: kdebug ("gpio: touchpad callback registered.\n") set_cb (TOUCHPAD_EVENT) Kernel::recv.reply.invoke () tp.send_initial () event (TOUCHPAD_EVENT, ~0) break case CAP_LOCKLEDS: leds.set (Kernel::recv.data[0].l) Kernel::recv.reply.invoke () break case CAP_PWM: pwm.set_backlight (Kernel::recv.data[0].l) Kernel::recv.reply.invoke () break default: kdebug ("invalid gpio operation ") kdebug_num (Kernel::recv.protected_data.l) kdebug ("\n") break