1
0
mirror of git://projects.qi-hardware.com/iris.git synced 2024-07-02 22:26:43 +03:00
iris/source/#gpio.ccp
2010-01-24 21:34:24 +01:00

324 lines
11 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"
#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.
enum codes:
KEYBOARD = 32
TOUCHPAD
LOCKLEDS
PWM
Kernel::Num start ():
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::Cap c = Kernel::my_receiver.create_capability (KEYBOARD)
Kernel::my_parent.provide_device <Keyboard> (c.copy (), 0)
Kernel::free_cap (c)
c = Kernel::my_receiver.create_capability (TOUCHPAD)
Kernel::my_parent.provide_device <Keyboard> (c.copy (), 1)
Kernel::free_cap (c)
Kernel::my_parent.init_done ()
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 KEYBOARD:
set_cb (KEYBOARD_EVENT)
Kernel::recv.reply.invoke ()
kbd.send_initial ()
event (KEYBOARD_EVENT, ~0)
break
case TOUCHPAD:
set_cb (TOUCHPAD_EVENT)
Kernel::recv.reply.invoke ()
tp.send_initial ()
event (TOUCHPAD_EVENT, ~0)
break
case LOCKLEDS:
leds.set (Kernel::recv.data[0].l)
Kernel::recv.reply.invoke ()
break
case 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