1
0
mirror of https://github.com/mist-devel/mist-firmware.git synced 2026-02-09 01:11:25 +00:00
Files
mist-devel.mist-firmware/ikbd.c
2013-10-07 20:02:31 +00:00

580 lines
15 KiB
C

/*
http://removers.free.fr/wikipendium/wakka.php?wiki=IntelligentKeyboardBible
https://www.kernel.org/doc/Documentation/input/atarikbd.txt
ikbd ToDo:
Feature Example using/needing it impl. tested
---------------------------------------------------------------------
mouse y at bottom Bolo X X
mouse button key events Goldrunner/A_008 X X
joystick interrogation mode Xevious/A_004 X X
Absolute mouse mode Addicataball/A_050 X (broken)
disable mouse ? X
disable joystick ? X
Joysticks also generate Goldrunner X X
mouse button events!
Pause (cmd 0x13) Wings of Death/A_427
pause/resume Gembench
mouse keycode mode
*/
#include <stdio.h>
#include <string.h>
#include "user_io.h"
#include "hardware.h"
#include "ikbd.h"
#include "debug.h"
// atari ikbd stuff
#define IKBD_STATE_JOYSTICK_EVENT_REPORTING 0x01
#define IKBD_STATE_MOUSE_Y_BOTTOM 0x02
#define IKBD_STATE_MOUSE_BUTTON_AS_KEY 0x04 // mouse buttons act like keys
#define IKBD_STATE_MOUSE_DISABLED 0x08
#define IKBD_STATE_MOUSE_ABSOLUTE 0x10
#define IKBD_STATE_MOUSE_ABSOLUTE_IN_PROGRESS 0x20
#define IKBD_STATE_WAIT4RESET 0x40
#define IKBD_DEFAULT IKBD_STATE_JOYSTICK_EVENT_REPORTING
#define QUEUE_LEN 16 // power of 2!
static unsigned short tx_queue[QUEUE_LEN];
static unsigned char wptr = 0, rptr = 0;
static unsigned long ikbd_timer = 0;
// structure to keep track of ikbd state
static struct {
unsigned char cmd;
unsigned char state;
unsigned char expect;
// joystick state
unsigned char joystick[2];
unsigned long joy_timer[2];
unsigned char joy_pending[2];
unsigned char date_buffer[6];
// mouse state
unsigned short mouse_abs_max_x, mouse_abs_max_y;
unsigned char mouse_abs_scale_x, mouse_abs_scale_y;
unsigned char mouse_abs_buttons;
unsigned short mouse_pos_x, mouse_pos_y;
unsigned int tx_cnt; // tx byte counter for debugging
} ikbd;
void ikbd_init() {
// reset ikbd state
memset(&ikbd, 0, sizeof(ikbd));
ikbd.state = IKBD_DEFAULT | IKBD_STATE_WAIT4RESET;
ikbd.mouse_abs_max_x = ikbd.mouse_abs_max_y = 65535;
ikbd.mouse_abs_scale_x = ikbd.mouse_abs_scale_y = 1;
ikbd.joy_timer[0] = ikbd.joy_timer[1] = 0;
ikbd.joy_pending[0] = ikbd.joy_pending[1] = 0;
// init ikbd date
ikbd.date_buffer[0] = 113;
ikbd.date_buffer[1] = 7;
ikbd.date_buffer[2] = 20;
ikbd.date_buffer[3] = 20;
ikbd.date_buffer[4] = 58;
ikbd.date_buffer[5] = 0;
}
void ikbd_reset(void) {
ikbd.tx_cnt = 0;
ikbd.state |= IKBD_STATE_WAIT4RESET;
}
static void enqueue(unsigned short b) {
if(((wptr + 1)&(QUEUE_LEN-1)) == rptr)
return;
tx_queue[wptr] = b;
wptr = (wptr+1)&(QUEUE_LEN-1);
}
// convert internal joystick format into atari ikbd format
static unsigned char joystick_map2ikbd(unsigned in) {
unsigned char out = 0;
if(in & JOY_UP) out |= 0x01;
if(in & JOY_DOWN) out |= 0x02;
if(in & JOY_LEFT) out |= 0x04;
if(in & JOY_RIGHT) out |= 0x08;
if(in & JOY_BTN1) out |= 0x80;
return out;
}
unsigned char bcd2bin(unsigned char in) {
return 10*(in >> 4) + (in & 0x0f);
}
unsigned char bin2bcd(unsigned char in) {
return 16*(in/10) + (in % 10);
}
// process inout from atari core into ikbd
void ikbd_handle_input(unsigned char cmd) {
// expecting a second byte for command
if(ikbd.expect) {
ikbd.expect--;
switch(ikbd.cmd) {
case 0x07: // set mouse button action
ikbd_debugf("mouse button action = %x", cmd);
// bit 2: Mouse buttons act like keys (LEFT=0x74 & RIGHT=0x75)
if(cmd & 0x04) ikbd.state |= IKBD_STATE_MOUSE_BUTTON_AS_KEY;
else ikbd.state &= ~IKBD_STATE_MOUSE_BUTTON_AS_KEY;
break;
case 0x09:
if(ikbd.expect == 3) ikbd.mouse_abs_max_x = cmd;
if(ikbd.expect == 2) ikbd.mouse_abs_max_x = (ikbd.mouse_abs_max_x & 0xff00) | cmd;
if(ikbd.expect == 1) ikbd.mouse_abs_max_y = cmd;
if(ikbd.expect == 0) ikbd.mouse_abs_max_y = (ikbd.mouse_abs_max_y & 0xff00) | cmd;
if(!ikbd.expect)
ikbd_debugf("new abs max = %u/%u", ikbd.mouse_abs_max_x, ikbd.mouse_abs_max_y);
break;
case 0x0e:
if(ikbd.expect == 3) ikbd.mouse_pos_x = cmd;
if(ikbd.expect == 2) ikbd.mouse_pos_x = (ikbd.mouse_pos_x & 0xff00) | cmd;
if(ikbd.expect == 1) ikbd.mouse_pos_y = cmd;
if(ikbd.expect == 0) ikbd.mouse_pos_y = (ikbd.mouse_pos_y & 0xff00) | cmd;
if(!ikbd.expect)
ikbd_debugf("new abs pos = %u/%u", ikbd.mouse_pos_x, ikbd.mouse_pos_y);
break;
case 0x0c:
if(ikbd.expect == 1) ikbd.mouse_abs_scale_x = cmd;
if(ikbd.expect == 0) ikbd.mouse_abs_scale_y = cmd;
if(!ikbd.expect)
ikbd_debugf("absolute scale = %u/%u", ikbd.mouse_abs_scale_x, ikbd.mouse_abs_scale_y);
break;
case 0x1b:
ikbd.date_buffer[5-ikbd.expect] = bcd2bin(cmd);
if(!ikbd.expect) {
ikbd_debugf("time/date = %u:%u:%u %u.%u.%u",
ikbd.date_buffer[3], ikbd.date_buffer[4], ikbd.date_buffer[5],
ikbd.date_buffer[2], ikbd.date_buffer[1], 1900 + ikbd.date_buffer[0]);
}
break;
case 0x80: // ibkd reset
// reply "everything is ok"
enqueue(0x8000 + 100); // wait 100ms
enqueue(0xf1);
break;
default:
break;
}
return;
}
ikbd.cmd = cmd;
switch(cmd) {
case 0x07:
ikbd_debugf("Set mouse button action");
ikbd.expect = 1;
break;
case 0x08:
ikbd_debugf("Set relative mouse positioning");
ikbd.state &= ~IKBD_STATE_MOUSE_DISABLED;
ikbd.state &= ~IKBD_STATE_MOUSE_ABSOLUTE;
break;
case 0x09:
ikbd_debugf("Set absolute mouse positioning");
ikbd.state &= ~IKBD_STATE_MOUSE_DISABLED;
ikbd.state |= IKBD_STATE_MOUSE_ABSOLUTE;
ikbd.expect = 4;
ikbd.mouse_abs_buttons = 2 | 8;
break;
case 0x0b:
ikbd_debugf("Set Mouse threshold");
ikbd.expect = 2;
break;
case 0x0c:
ikbd_debugf("Set Mouse scale");
ikbd.expect = 2;
break;
case 0x0d:
// ikbd_debugf("Interrogate Mouse Position");
if((ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE) &&
!(ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE_IN_PROGRESS)) {
ikbd.state |= IKBD_STATE_MOUSE_ABSOLUTE_IN_PROGRESS;
enqueue(0x8000 + 36);
enqueue(0xf7);
enqueue(ikbd.mouse_abs_buttons);
enqueue(ikbd.mouse_pos_x >> 8);
enqueue(ikbd.mouse_pos_x & 0xff);
enqueue(ikbd.mouse_pos_y >> 8);
enqueue(ikbd.mouse_pos_y & 0xff);
enqueue(0x4000 + 0xf7);
ikbd.mouse_abs_buttons = 0;
}
break;
case 0x0e:
ikbd_debugf("Load Mouse Position");
ikbd.expect = 5;
break;
case 0x0f:
ikbd_debugf("Set Y at bottom");
ikbd.state |= IKBD_STATE_MOUSE_Y_BOTTOM;
break;
case 0x10:
ikbd_debugf("Set Y at top");
ikbd.state &= ~IKBD_STATE_MOUSE_Y_BOTTOM;
break;
case 0x12:
ikbd_debugf("Disable mouse");
ikbd.state |= IKBD_STATE_MOUSE_DISABLED;
break;
case 0x14:
ikbd_debugf("Set Joystick event reporting");
ikbd.state |= IKBD_STATE_JOYSTICK_EVENT_REPORTING;
break;
case 0x15:
ikbd_debugf("Set Joystick interrogation mode");
ikbd.state &= ~IKBD_STATE_JOYSTICK_EVENT_REPORTING;
break;
case 0x16: // interrogate joystick
// send reply
enqueue(0x8000 + 70); // wait 70ms
enqueue(0xfd);
enqueue(joystick_map2ikbd(ikbd.joystick[0]));
enqueue(joystick_map2ikbd(ikbd.joystick[1]));
break;
case 0x1a:
ikbd_debugf("Disable joysticks");
ikbd.state &= ~IKBD_STATE_JOYSTICK_EVENT_REPORTING;
break;
case 0x1b:
ikbd_debugf("Time of day clock set");
ikbd.expect = 6;
break;
case 0x1c:
ikbd_debugf("Interrogate time of day");
ikbd_debugf("time/date = %u:%u:%u %u.%u.%u",
ikbd.date_buffer[3], ikbd.date_buffer[4], ikbd.date_buffer[5],
ikbd.date_buffer[2], ikbd.date_buffer[1], 1900 + ikbd.date_buffer[0]);
enqueue(0x8000 + 64); // wait 64ms
enqueue(0xfc);
{
int i;
for(i=0;i<6;i++)
enqueue(bin2bcd(ikbd.date_buffer[i]));
}
break;
case 0x80:
ikbd_debugf("Reset");
ikbd.expect = 1;
ikbd.state = IKBD_DEFAULT;
break;
default:
ikbd_debugf("unknown command: %x", cmd);
break;
}
}
void ikbd_poll(void) {
#ifdef IKBD_DEBUG
static unsigned long xtimer = 0;
static int last_cnt = 0;
if(CheckTimer(xtimer)) {
xtimer = GetTimer(2000);
if(ikbd.tx_cnt != last_cnt) {
ikbd_debugf("sent bytes: %d", ikbd.tx_cnt);
last_cnt = ikbd.tx_cnt;
}
}
#endif
// handle outstanding joystick events
char i;
for(i=0;i<2;i++) {
// check if timeout is still running
if((ikbd.joy_pending[i] & 0x40) && CheckTimer(ikbd.joy_timer[i]))
ikbd.joy_pending[i] &= ~0x40; // clear timeout flag
if((ikbd.joy_pending[i] & 0xc0) == 0x80) {
// iprintf("delayed %d\n", i);
ikbd_joystick(i, ikbd.joy_pending[i] & ~0xc0);
ikbd.joy_pending[i] = 0;
}
}
static unsigned long mtimer = 0;
if(CheckTimer(mtimer)) {
mtimer = GetTimer(10);
// check for incoming ikbd data
EnableIO();
SPI(UIO_IKBD_IN);
while(SPI(0))
ikbd_handle_input(SPI(0));
DisableIO();
}
// send data from queue if present
if(rptr == wptr) return;
// timer active?
if(ikbd_timer) {
if(!CheckTimer(ikbd_timer))
return;
// ikbd_debugf("timer done");
ikbd_timer = 0;
}
if(tx_queue[rptr] & 0xc000) {
// request to start timer?
if((tx_queue[rptr] & 0xc000) == 0x8000)
ikbd_timer = GetTimer(tx_queue[rptr] & 0x3fff);
// cmd ack
if((tx_queue[rptr] & 0xc000) == 0x4000)
if((tx_queue[rptr] & 0xff) == 0xf7)
ikbd.state &= ~IKBD_STATE_MOUSE_ABSOLUTE_IN_PROGRESS;
rptr = (rptr+1)&(QUEUE_LEN-1);
return;
}
// keep quiet as long has we haven't received a reset
if(!(ikbd.state & IKBD_STATE_WAIT4RESET)) {
// ikbd_debugf("TX[%x]", tx_queue[rptr]);
// transmit data from queue
EnableIO();
SPI(UIO_IKBD_OUT);
SPI(tx_queue[rptr]);
DisableIO();
ikbd.tx_cnt++;
}
rptr = (rptr+1)&(QUEUE_LEN-1);
}
void ikbd_joystick(unsigned char joystick, unsigned char map) {
// todo: suppress events for joystick 0 as long as mouse
// is enabled?
if(ikbd.state & IKBD_STATE_JOYSTICK_EVENT_REPORTING) {
// report rate is limited
// check if it's already time for a new joystick report
if(ikbd.joy_pending[joystick] & 0x40) {
// if(!CheckTimer(ikbd.joy_timer[joystick])) {
// iprintf("too fast on joy %d\n", joystick);
// bit 7 marks this entry as valid
ikbd.joy_pending[joystick] = 0xc0 | map;
return;
}
// next report for this joystick earliest in 50 ms
ikbd.joy_timer[joystick] = GetTimer(50);
ikbd.joy_pending[joystick] = 0x40; // 0x40 = "timeout is active" flag
// only report joystick data for joystick 0 if the mouse is disabled
if((ikbd.state & IKBD_STATE_MOUSE_DISABLED) || (joystick == 1)) {
// iprintf("tx for %d - %x\n", joystick, joystick_map2ikbd(map));
enqueue(0xfe + joystick);
enqueue(joystick_map2ikbd(map));
}
if(!(ikbd.state & IKBD_STATE_MOUSE_DISABLED)) {
// the fire button also generates a mouse event if
// mouse reporting is enabled
if((map & JOY_BTN1) != (ikbd.joystick[joystick] & JOY_BTN1)) {
// generate mouse event (ikbd_joystick_buttons is evaluated inside
// user_io_mouse)
ikbd.joystick[joystick] = map;
// enqueue(0x8000 + 5); // some small pause in between
ikbd_mouse(0, 0, 0);
}
}
}
// save state of joystick for interrogation mode
ikbd.joystick[joystick] = map;
}
void ikbd_keyboard(unsigned char code) {
#ifdef IKBD_DEBUG
ikbd_debugf("send keycode %x%s", code&0x7f, (code&0x80)?" BREAK":"");
#endif
enqueue(code);
}
void ikbd_mouse(unsigned char b, char x, char y) {
if(ikbd.state & IKBD_STATE_MOUSE_DISABLED)
return;
// joystick and mouse buttons are wired together in
// atari st
b |= (ikbd.joystick[0] & JOY_BTN1)?1:0;
b |= (ikbd.joystick[1] & JOY_BTN1)?2:0;
// save state for absolute mouse reports
if(b & 2) ikbd.mouse_abs_buttons |= 1;
else ikbd.mouse_abs_buttons |= 2;
if(b & 1) ikbd.mouse_abs_buttons |= 4;
else ikbd.mouse_abs_buttons |= 8;
static unsigned char b_old = 0;
// monitor state of two mouse buttons
if(b != b_old) {
// check if mouse buttons are supposed to be treated like keys
if(ikbd.state & IKBD_STATE_MOUSE_BUTTON_AS_KEY) {
// Mouse buttons act like keys (LEFT=0x74 & RIGHT=0x75)
// handle left mouse button
if((b ^ b_old) & 1) ikbd_keyboard(0x74 | ((b&1)?0x00:0x80));
// handle right mouse button
if((b ^ b_old) & 2) ikbd_keyboard(0x75 | ((b&2)?0x00:0x80));
}
b_old = b;
}
#if 0
if(ikbd.state & IKBD_STATE_MOUSE_BUTTON_AS_KEY) {
b = 0;
// if mouse position is 0/0 quit here
if(!x && !y) return;
}
#endif
if(ikbd.state & IKBD_STATE_MOUSE_Y_BOTTOM)
y = -y;
if(ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE) {
x /= ikbd.mouse_abs_scale_x;
y /= ikbd.mouse_abs_scale_y;
// ikbd_debugf("abs inc %d %d -> ", x, y);
if(x < 0) {
x = -x;
if(ikbd.mouse_pos_x > x) ikbd.mouse_pos_x -= x;
else ikbd.mouse_pos_x = 0;
} else if(x > 0) {
if(ikbd.mouse_pos_x < ikbd.mouse_abs_max_x - x)
ikbd.mouse_pos_x += x;
else
ikbd.mouse_pos_x = ikbd.mouse_abs_max_x;
}
if(y < 0) {
y = -y;
if(ikbd.mouse_pos_y > y) ikbd.mouse_pos_y -= y;
else ikbd.mouse_pos_y = 0;
} else if(y > 0) {
if(ikbd.mouse_pos_y < ikbd.mouse_abs_max_y - y)
ikbd.mouse_pos_y += y;
else
ikbd.mouse_pos_y = ikbd.mouse_abs_max_y;
}
// ikbd_debugf("%d %d\n", ikbd.mouse_pos_x, ikbd.mouse_pos_y);
} else {
// atari has mouse button bits swapped
enqueue(0xf8|((b&1)?2:0)|((b&2)?1:0));
enqueue(x & 0xff);
enqueue(y & 0xff);
}
}
// advance the ikbd time by one second
void ikbd_update_time(void) {
static const char mdays[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
short year = 1900 + ikbd.date_buffer[0];
char is_leap = (!(year % 4) && (year % 100)) || !(year % 400);
// ikbd_debugf("time update %u:%02u:%02u %u.%u.%u",
// ikbd.date_buffer[3], ikbd.date_buffer[4], ikbd.date_buffer[5],
// ikbd.date_buffer[2], ikbd.date_buffer[1], year);
// advance seconds
ikbd.date_buffer[5]++;
if(ikbd.date_buffer[5] == 60) {
ikbd.date_buffer[5] = 0;
// advance minutes
ikbd.date_buffer[4]++;
if(ikbd.date_buffer[4] == 60) {
ikbd.date_buffer[4] = 0;
// advance hours
ikbd.date_buffer[3]++;
if(ikbd.date_buffer[3] == 24) {
ikbd.date_buffer[3] = 0;
// advance days
ikbd.date_buffer[2]++;
if((ikbd.date_buffer[2] == mdays[ikbd.date_buffer[1]-1]+1) ||
(is_leap && (ikbd.date_buffer[1] == 2) && (ikbd.date_buffer[2] == 29))) {
ikbd.date_buffer[2] = 1;
// advance month
ikbd.date_buffer[1]++;
if(ikbd.date_buffer[1] == 13) {
ikbd.date_buffer[1] = 0;
// advance year
ikbd.date_buffer[0]++;
}
}
}
}
}
}