mirror of
git://projects.qi-hardware.com/iris.git
synced 2024-11-19 06:00:37 +02:00
314 lines
10 KiB
COBOL
314 lines
10 KiB
COBOL
#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 <wijnen@debian.org>
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include "devices.hh"
|
|
#include "init.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
|
|
|
|
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
|
|
|
|
((Init)Kernel::my_parent).register_gpio ()
|
|
|
|
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 Init::GPIO_KEYBOARD:
|
|
set_cb (KEYBOARD_EVENT)
|
|
Kernel::recv.reply.invoke ()
|
|
kbd.send_initial ()
|
|
event (KEYBOARD_EVENT, ~0)
|
|
break
|
|
case Init::GPIO_TOUCHPAD:
|
|
set_cb (TOUCHPAD_EVENT)
|
|
Kernel::recv.reply.invoke ()
|
|
tp.send_initial ()
|
|
event (TOUCHPAD_EVENT, ~0)
|
|
break
|
|
case Init::GPIO_LOCKLEDS:
|
|
leds.set (Kernel::recv.data[0].l)
|
|
Kernel::recv.reply.invoke ()
|
|
break
|
|
case Init::GPIO_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
|