/* 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 X disable mouse ? X disable joystick ? X Joysticks also generate Goldrunner X -X mouse button events! Pause/Resume PACMANIA_STE/Gembench X mouse keycode mode Goldrunner X X Games that have ikbd problems: PowerMonger/PP_106 fixed Stardust fixed M1 tank platoon/A_385 fixed */ #include #include #include "user_io.h" #include "spi.h" #include "ikbd.h" #include "debug.h" #include "hardware.h" #include "utils.h" #define IKBD_AUTO_MS 20 // 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_STATE_PAUSED 0x80 #define IKBD_STATE_MOUSE_KEYCODE 0x100 #define IKBD_DEFAULT IKBD_STATE_JOYSTICK_EVENT_REPORTING /* ------------------- transmit queue ------------------- */ #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; /* -------- main structure to keep track of ikbd state -------- */ static struct { unsigned short state; unsigned long auto_timer; // auto report timer (50hz/20ms) // ----- joystick state ------- struct { unsigned char state; // current state unsigned char prev; // last reported state } joy[2]; // ----- mouse state ------- struct { // current state unsigned char but, but_prev; short x, y; struct { // absolute mouse state unsigned char buttons; struct { unsigned short x, y; } max; struct { unsigned char x, y; } scale; struct { unsigned short x, y; } pos; } abs; } mouse; // ----- clock state ------ unsigned char date[7]; unsigned int tx_cnt; // tx byte counter for debugging // ----- buffer tp hold incoming commands ------ struct { char size; union { struct { unsigned char code; // cmd code // command specific structures union { unsigned char mouse_button_action; unsigned char reset; struct { unsigned short max_x, max_y; } __attribute__ ((packed)) abs_mouse_pos; struct { unsigned char dist_x, dist_y; } __attribute__ ((packed)) mouse_keycode; struct { unsigned char x, y; } __attribute__ ((packed)) mouse_threshold; struct { unsigned char x, y; } __attribute__ ((packed)) mouse_scale; struct { unsigned char f; unsigned short x, y; } __attribute__ ((packed)) load_mouse_pos; unsigned char date[6]; }; } __attribute__ ((packed)) command; unsigned char byte[0]; }; } buffer; } ikbd; // read a 16 bit word in big endian unsigned short be16(unsigned short in) { return ((in & 0xff)<<8) + ((in & 0xff00)>>8); } 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 char 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; } void ikbd_handler_mouse_button_action(void) { unsigned char action = ikbd.buffer.command.mouse_button_action; ikbd_debugf("mouse button action = %d", action); // bit 2: Mouse buttons act like keys (LEFT=0x74 & RIGHT=0x75) if(action & 0x04) ikbd.state |= IKBD_STATE_MOUSE_BUTTON_AS_KEY; else ikbd.state &= ~IKBD_STATE_MOUSE_BUTTON_AS_KEY; } void ikbd_handler_set_relative_mouse_pos(void) { ikbd_debugf("Set relative mouse positioning"); ikbd.state &= ~IKBD_STATE_MOUSE_DISABLED; ikbd.state &= ~IKBD_STATE_MOUSE_KEYCODE; ikbd.state &= ~IKBD_STATE_MOUSE_ABSOLUTE; ikbd.state &= ~IKBD_STATE_PAUSED; } void ikbd_handler_set_abs_mouse_pos(void) { ikbd.mouse.abs.max.x = be16(ikbd.buffer.command.abs_mouse_pos.max_x); ikbd.mouse.abs.max.y = be16(ikbd.buffer.command.abs_mouse_pos.max_y); ikbd_debugf("Set absolute mouse positioning, max = %u/%u", ikbd.mouse.abs.max.x, ikbd.mouse.abs.max.y); ikbd.state &= ~IKBD_STATE_MOUSE_DISABLED; ikbd.state &= ~IKBD_STATE_MOUSE_KEYCODE; ikbd.state |= IKBD_STATE_MOUSE_ABSOLUTE; ikbd.mouse.abs.buttons = 2 | 8; } void ikbd_handler_set_mouse_keycode_mode(void) { ikbd_debugf("Set mouse keycode mode dist %u/%u", ikbd.buffer.command.mouse_keycode.dist_x, ikbd.buffer.command.mouse_keycode.dist_y); ikbd.state |= IKBD_STATE_MOUSE_KEYCODE; } void ikbd_handler_set_mouse_threshold(void) { ikbd_debugf("Set mouse threshold %u/%u", ikbd.buffer.command.mouse_threshold.x, ikbd.buffer.command.mouse_threshold.y); } void ikbd_handler_set_mouse_scale(void) { ikbd_debugf("Set mouse scale %u/%u", ikbd.buffer.command.mouse_scale.x, ikbd.buffer.command.mouse_scale.y); ikbd.mouse.abs.scale.x = ikbd.buffer.command.mouse_scale.x; ikbd.mouse.abs.scale.y = ikbd.buffer.command.mouse_scale.y; } void ikbd_handler_interrogate_mouse_pos(void) { // ikbd_debugf("Interrogate Mouse Position"); if(ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE) { enqueue(0x8000 + 3); // 3ms delay, hatari uses 18000 cycles (~2.25ms) enqueue(0xf7); enqueue(ikbd.mouse.abs.buttons); enqueue(ikbd.mouse.abs.pos.x >> 8); enqueue(ikbd.mouse.abs.pos.x & 0xff); enqueue(ikbd.mouse.abs.pos.y >> 8); enqueue(ikbd.mouse.abs.pos.y & 0xff); ikbd.mouse.abs.buttons = 0; } } void ikbd_handler_load_mouse_pos(void) { ikbd.mouse.abs.pos.x = be16(ikbd.buffer.command.load_mouse_pos.x); ikbd.mouse.abs.pos.y = be16(ikbd.buffer.command.load_mouse_pos.y); ikbd_debugf("Load mouse position %u/%u", ikbd.mouse.abs.pos.x, ikbd.mouse.abs.pos.y); } void ikbd_handler_set_y_bottom(void) { ikbd_debugf("Set Y at bottom"); ikbd.state |= IKBD_STATE_MOUSE_Y_BOTTOM; } void ikbd_handler_set_y_top(void) { ikbd_debugf("Set Y at top"); ikbd.state &= ~IKBD_STATE_MOUSE_Y_BOTTOM; } void ikbd_handler_resume(void) { ikbd.state &= ~IKBD_STATE_PAUSED; } void ikbd_handler_disable_mouse(void) { ikbd_debugf("Disable mouse"); ikbd.state |= IKBD_STATE_MOUSE_DISABLED; } void ikbd_handler_pause(void) { ikbd_debugf("Pause"); ikbd.state |= IKBD_STATE_PAUSED; } void ikbd_handler_set_joystick_event_reporting(void) { ikbd_debugf("Set Joystick event reporting"); ikbd.state |= IKBD_STATE_JOYSTICK_EVENT_REPORTING; ikbd.state &= ~IKBD_STATE_PAUSED; } void ikbd_handler_set_joystick_interrogation_mode(void) { ikbd_debugf("Set Joystick interrogation mode"); ikbd.state &= ~IKBD_STATE_JOYSTICK_EVENT_REPORTING; ikbd.state &= ~IKBD_STATE_PAUSED; } void ikbd_handler_interrogate_joystick(void) { // send reply enqueue(0xfd); enqueue(ikbd.joy[0].state | ((ikbd.mouse.but & (1<<0))?0x80:0x00)); enqueue(ikbd.joy[1].state | ((ikbd.mouse.but & (1<<1))?0x80:0x00)); } void ikbd_handler_disable_joysticks(void) { ikbd_debugf("Disable joysticks"); ikbd.state &= ~IKBD_STATE_JOYSTICK_EVENT_REPORTING; } void ikbd_handler_time_set(void) { unsigned char c; for(c=0;c<6;c++) ikbd.date[c] = bcd2bin(ikbd.buffer.command.date[c]); // release SPI since it will be used by usb when // reading the time from the rtc DisableIO(); // try to set time on rtc if present SetRTC(ikbd.date); spi_uio_cmd_cont(UIO_IKBD_IN); ikbd_debugf("Time of day clock set: %u:%02u:%02u %u.%u.%u", ikbd.date[3], ikbd.date[4], ikbd.date[5], ikbd.date[2], ikbd.date[1], 1900 + ikbd.date[0]); } void ikbd_handler_interrogate_time(void) { unsigned char i; // release SPI since it will be used by usb when // reading the time from the rtc DisableIO(); // try to fetch time from rtc if present GetRTC(ikbd.date); spi_uio_cmd_cont(UIO_IKBD_IN); ikbd_debugf("Interrogate time of day %u:%02u:%02u %u.%u.%u", ikbd.date[3], ikbd.date[4], ikbd.date[5], ikbd.date[2], ikbd.date[1], 1900 + ikbd.date[0]); enqueue(0x8000 + 64); // wait 64ms enqueue(0xfc); for(i=0;i<6;i++) enqueue(bin2bcd(ikbd.date[i])); } void ikbd_handler_reset(void) { ikbd_debugf("Reset %x", ikbd.buffer.command.reset); if(ikbd.buffer.command.reset == 1) { ikbd.state = IKBD_DEFAULT; enqueue(0x8000 + 300); // wait 300ms enqueue(0xf0); } } // ---- list of supported ikbd commands ---- struct { unsigned char code; unsigned char length; void (*handler)(void); } ikbd_command_handler[] = { { 0x07, 2, ikbd_handler_mouse_button_action }, { 0x08, 1, ikbd_handler_set_relative_mouse_pos }, { 0x09, 5, ikbd_handler_set_abs_mouse_pos }, { 0x0a, 3, ikbd_handler_set_mouse_keycode_mode }, { 0x0b, 3, ikbd_handler_set_mouse_threshold }, { 0x0c, 3, ikbd_handler_set_mouse_scale }, { 0x0d, 1, ikbd_handler_interrogate_mouse_pos }, { 0x0e, 6, ikbd_handler_load_mouse_pos }, { 0x0f, 1, ikbd_handler_set_y_bottom }, { 0x10, 1, ikbd_handler_set_y_top }, { 0x11, 1, ikbd_handler_resume }, { 0x12, 1, ikbd_handler_disable_mouse }, { 0x13, 1, ikbd_handler_pause }, { 0x14, 1, ikbd_handler_set_joystick_event_reporting }, { 0x15, 1, ikbd_handler_set_joystick_interrogation_mode }, { 0x16, 1, ikbd_handler_interrogate_joystick }, { 0x1a, 1, ikbd_handler_disable_joysticks }, { 0x1c, 1, ikbd_handler_interrogate_time }, { 0x1b, 7, ikbd_handler_time_set }, { 0x80, 2, ikbd_handler_reset }, { 0, 0, NULL } // end of list }; 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_debugf("Init"); // init ikbd date to some default ikbd.date[0] = 113; ikbd.date[1] = 7; ikbd.date[2] = 20; ikbd.date[3] = 20; ikbd.date[4] = 58; // handle auto events ikbd.auto_timer = GetTimer(0); } void ikbd_reset(void) { ikbd.tx_cnt = 0; ikbd.state |= IKBD_STATE_WAIT4RESET; } // process inout from atari core into ikbd void ikbd_handle_input(unsigned char cmd) { // store byte in buffer ikbd.buffer.byte[ikbd.buffer.size++] = cmd; // check if there's a known command in the buffer char c; for(c=0;ikbd_command_handler[c].length && (ikbd_command_handler[c].code != ikbd.buffer.command.code);c++); // not a valid command? -> flush buffer if(!ikbd_command_handler[c].length) { ikbd_debugf("Unhandled command: %02x", ikbd.buffer.command.code); ikbd.buffer.size = 0; } else { // valid command and enough bytes? if(ikbd_command_handler[c].length == ikbd.buffer.size) { ikbd_command_handler[c].handler(); ikbd.buffer.size = 0; } } } 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 // do auto events every 20ms if(CheckTimer(ikbd.auto_timer)) { ikbd.auto_timer = GetTimer(IKBD_AUTO_MS); if(!(ikbd.state & IKBD_STATE_WAIT4RESET) && !(ikbd.state & IKBD_STATE_PAUSED)) { /* --------- joystick ---------- */ if(ikbd.state & IKBD_STATE_JOYSTICK_EVENT_REPORTING) { char i; for(i=0;i<2;i++) { unsigned char state = ikbd.joy[i].state; // left mouse button 1 is also joystick 0 fire button // right mouse button 0 is also joystick 1 fire button if(ikbd.mouse.but & (2>>i)) state |= 0x80; if(state != ikbd.joy[i].prev) { // iprintf("JOY%d: %x\n", i, state); enqueue(0xfe + i); enqueue(state); ikbd.joy[i].prev = state; } } } /* ----------- keyboard emu mouse ---------- */ if( !(ikbd.state & IKBD_STATE_MOUSE_DISABLED) && (ikbd.state & IKBD_STATE_MOUSE_KEYCODE)) { if (ikbd.mouse.x < -ikbd.buffer.command.mouse_keycode.dist_x) { ikbd_keyboard(0x4b); ikbd_keyboard(0x4b); ikbd_keyboard(0x4b); // ikbd_keyboard(0x4b); ikbd_keyboard(0x4b | 0x80); // Left ikbd.mouse.x = 0; } if (ikbd.mouse.x > ikbd.buffer.command.mouse_keycode.dist_x) { ikbd_keyboard(0x4d); ikbd_keyboard(0x4d); ikbd_keyboard(0x4d); // ikbd_keyboard(0x4d); ikbd_keyboard(0x4d | 0x80);// Right ikbd.mouse.x = 0; } if (ikbd.mouse.y < -ikbd.buffer.command.mouse_keycode.dist_y) { ikbd_keyboard(0x48); ikbd_keyboard(0x48); ikbd_keyboard(0x48); // ikbd_keyboard(0x48); ikbd_keyboard(0x48 | 0x80); // Up ikbd.mouse.y = 0; } if (ikbd.mouse.y > ikbd.buffer.command.mouse_keycode.dist_y) { ikbd_keyboard(0x50); // Down ikbd_keyboard(0x50); // Down ikbd_keyboard(0x50); // Down // ikbd_keyboard(0x50); ikbd_keyboard(0x50 | 0x80); // Down ikbd.mouse.y = 0; } unsigned char b = ikbd.mouse.but; if(b != ikbd.mouse.but_prev) { // handle left mouse button if((b ^ ikbd.mouse.but_prev) & 2) ikbd_keyboard(0x74 | ((b&2)?0x00:0x80)); // handle right mouse button if((b ^ ikbd.mouse.but_prev) & 1) ikbd_keyboard(0x75 | ((b&1)?0x00:0x80)); } ikbd.mouse.but_prev = b; } /* ----------- relative mouse ---------- */ if( !(ikbd.state & IKBD_STATE_MOUSE_DISABLED) && !(ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE) && !(ikbd.state & IKBD_STATE_MOUSE_KEYCODE)) { unsigned char b = ikbd.mouse.but; // include joystick buttons into mouse state if(ikbd.joy[0].state & 0x80) b |= 2; if(ikbd.joy[1].state & 0x80) b |= 1; if(ikbd.mouse.x || ikbd.mouse.y || (b != ikbd.mouse.but_prev)) { do { char x, y; if(ikbd.mouse.x < -128) x = -128; else if(ikbd.mouse.x > 127) x = 127; else x = ikbd.mouse.x; if(ikbd.mouse.y < -128) y = -128; else if(ikbd.mouse.y > 127) y = 127; else y = ikbd.mouse.y; // iprintf("RMOUSE: %x %x %x\n", b, x&0xff, y&0xff); enqueue(0xf8|b); enqueue(x & 0xff); enqueue(y & 0xff); ikbd.mouse.x -= x; ikbd.mouse.y -= y; } while(ikbd.mouse.x || ikbd.mouse.y); // check if mouse buttons are supposed to be treated like keys if(ikbd.state & IKBD_STATE_MOUSE_BUTTON_AS_KEY) { // check if mouse button state has changed if(b != ikbd.mouse.but_prev) { // Mouse buttons act like keys (LEFT=0x74 & RIGHT=0x75) // handle left mouse button if((b ^ ikbd.mouse.but_prev) & 2) ikbd_keyboard(0x74 | ((b&2)?0x00:0x80)); // handle right mouse button if((b ^ ikbd.mouse.but_prev) & 1) ikbd_keyboard(0x75 | ((b&1)?0x00:0x80)); } } ikbd.mouse.but_prev = b; } } } } static unsigned long mtimer = 0; if(CheckTimer(mtimer)) { mtimer = GetTimer(10); // check for incoming ikbd data spi_uio_cmd_cont(UIO_IKBD_IN); while(spi_in()) ikbd_handle_input(spi_in()); DisableIO(); } // everything below must not happen faster than 1kHz static unsigned long rtimer = 0; if(!CheckTimer(rtimer)) return; // next event 1 ms later rtimer = GetTimer(1); // timer active? if(ikbd_timer) { if(!CheckTimer(ikbd_timer)) return; ikbd_timer = 0; } if(rptr == wptr) return; if(tx_queue[rptr] & 0x8000) { // request to start timer? if(tx_queue[rptr] & 0x8000) ikbd_timer = GetTimer(tx_queue[rptr] & 0x3fff); rptr = (rptr+1)&(QUEUE_LEN-1); return; } // transmit data from queue spi_uio_cmd_cont(UIO_IKBD_OUT); spi8(tx_queue[rptr]); DisableIO(); ikbd.tx_cnt++; rptr = (rptr+1)&(QUEUE_LEN-1); } // called from external parts to report joystick states void ikbd_joystick(unsigned char joystick, unsigned char map) { ikbd.joy[joystick].state = joystick_map2ikbd(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) { // honour reversal of y axis if(ikbd.state & IKBD_STATE_MOUSE_Y_BOTTOM) y = -y; // update relative mouse state ikbd.mouse.but = ((b&1)?2:0)|((b&2)?1:0); ikbd.mouse.x += x; ikbd.mouse.y += y; // save button state for absolute mouse reports if((ikbd.state & IKBD_STATE_MOUSE_ABSOLUTE) && !(ikbd.state & IKBD_STATE_MOUSE_KEYCODE)) { // include joystick buttons into mouse state if(ikbd.joy[0].state & 0x80) b |= 2; if(ikbd.joy[1].state & 0x80) b |= 1; 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; if(ikbd.mouse.abs.scale.x > 1) x *= ikbd.mouse.abs.scale.x; if(ikbd.mouse.abs.scale.y > 1) y *= ikbd.mouse.abs.scale.y; // ikbd_debugf("abs inc %d %d -> ", x, y); if(x < 0) { x = -x; if(ikbd.mouse.abs.pos.x > x) ikbd.mouse.abs.pos.x -= x; else ikbd.mouse.abs.pos.x = 0; } else if(x > 0) { if(ikbd.mouse.abs.pos.x < ikbd.mouse.abs.max.x - x) ikbd.mouse.abs.pos.x += x; else ikbd.mouse.abs.pos.x = ikbd.mouse.abs.max.x; } if(y < 0) { y = -y; if(ikbd.mouse.abs.pos.y > y) ikbd.mouse.abs.pos.y -= y; else ikbd.mouse.abs.pos.y = 0; } else if(y > 0) { if(ikbd.mouse.abs.pos.y < ikbd.mouse.abs.max.y - y) ikbd.mouse.abs.pos.y += y; else ikbd.mouse.abs.pos.y = ikbd.mouse.abs.max.y; } } } // 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[0]; char is_leap = (!(year % 4) && (year % 100)) || !(year % 400); // advance seconds ikbd.date[5]++; if(ikbd.date[5] == 60) { ikbd.date[5] = 0; // advance minutes ikbd.date[4]++; if(ikbd.date[4] == 60) { ikbd.date[4] = 0; // advance hours ikbd.date[3]++; if(ikbd.date[3] == 24) { ikbd.date[3] = 0; // advance days ikbd.date[2]++; if((ikbd.date[2] == mdays[ikbd.date[1]-1]+1) || (is_leap && (ikbd.date[1] == 2) && (ikbd.date[2] == 29))) { ikbd.date[2] = 1; // advance month ikbd.date[1]++; if(ikbd.date[1] == 13) { ikbd.date[1] = 0; // advance year ikbd.date[0]++; } } } } } }