mii_emu/src/drivers/mii_mouse.c

288 lines
7.9 KiB
C

/*
* mii_mouse.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*
*/
#include "mii.h"
// https://github.com/ivanizag/izapple2/blob/master/cardMouse.go
// https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator/log/188017-entry-23-here-mousie-mousie-mousie
// https://github.com/ct6502/apple2ts/blob/main/src/emulator/mouse.ts
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mii.h"
#include "mii_bank.h"
/*
* Coded against this information
* http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html
*/
/*
* Screen holes
* $0478 + slot Low byte of absolute X position
* $04F8 + slot Low byte of absolute Y position
* $0578 + slot High byte of absolute X position
* $05F8 + slot High byte of absolute Y position
* $0678 + slot Reserved and used by the firmware
* $06F8 + slot Reserved and used by the firmware
* $0778 + slot Button 0/1 interrupt status byte
* $07F8 + slot Mode byte
*
* Interrupt status byte:
* Set by READMOUSE
* Bit 7 6 5 4 3 2 1 0
* | | | | | | | |
* | | | | | | | `--- Previously, button 1 was up (0) or down (1)
* | | | | | | `----- Movement interrupt
* | | | | | `------- Button 0/1 interrupt
* | | | | `--------- VBL interrupt
* | | | `----------- Currently, button 1 is up (0) or down (1)
* | | `------------- X/Y moved since last READMOUSE
* | `--------------- Previously, button 0 was up (0) or down (1)
* `----------------- Currently, button 0 is up (0) or down (1)
*
* Mode byte
* Valid after calling SERVEMOUSE, cleared with READMOUSE
* Bit 7 6 5 4 3 2 1 0
* | | | | | | | |
* | | | | | | | `--- Mouse off (0) or on (1)
* | | | | | | `----- Interrupt if mouse is moved
* | | | | | `------- Interrupt if button is pressed
* | | | | `--------- Interrupt on VBL
* | | | `----------- Reserved
* | | `------------- Reserved
* | `--------------- Reserved
* `----------------- Reserved
*/
enum {
CLAMP_MIN_LO = 0x478,
CLAMP_MIN_HI = 0x578,
CLAMP_MAX_LO = 0x4F8,
CLAMP_MAX_HI = 0x5F8,
// RAM Locations
// (Add $Cn where n is slot to these)
MOUSE_X_LO = 0x03B8,
MOUSE_X_HI = 0x04B8,
MOUSE_Y_LO = 0x0438,
MOUSE_Y_HI = 0x0538,
MOUSE_STATUS = 0x06B8,
MOUSE_MODE = 0x0738,
};
enum {
mouseEnabled = 1,
mouseIntMoveEnabled = 2,
mouseIntButtonEnabled = 4,
mouseIntVBlankEnabled = 8,
};
typedef struct mii_card_mouse_t {
struct mii_slot_t * slot;
mii_t * mii;
uint8_t timer_id; // 60hz timer
uint8_t slot_offset;
uint8_t mode; // cached mode byte
struct {
uint16_t x, y;
bool button;
} last;
} mii_card_mouse_t;
static uint64_t
_mii_mouse_vbl_handler(
mii_t * mii,
void *param)
{
mii_card_mouse_t *c = param;
/* this is not exact, the VBL interrupt should still work when
* the mouse is disabled, but it's not really important -- for the moment
*/
if (!mii->mouse.enabled)
return 1000000 / 60;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
uint8_t status = mii_bank_peek(main, MOUSE_STATUS + c->slot_offset);
uint8_t old = status;
if (c->mode & mouseIntMoveEnabled) {
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
mii->cpu_state.irq = 1;
status |= 1 << 1;
}
}
if (c->mode & mouseIntButtonEnabled) {
if (mii->mouse.button && !c->last.button) {
mii->cpu_state.irq = 1;
status |= 1 << 2;
}
}
if (c->mode & mouseIntVBlankEnabled) {
mii->cpu_state.irq = 1;
status |= 1 << 3;
}
// if (mii->cpu_state.irq) mii->trace_cpu = true;
if (status != old)
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
return 1000000 / 60;
}
static int
_mii_mouse_init(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_mouse_t *c = calloc(1, sizeof(*c));
c->slot = slot;
slot->drv_priv = c;
c->mii = mii;
printf("%s loading in slot %d\n", __func__, slot->id + 1);
c->slot_offset = slot->id + 1 + 0xc0;
uint8_t data[256] = {};
c->timer_id = mii_timer_register(mii,
_mii_mouse_vbl_handler, c,
1000000 / 60, __func__);
// Identification as a mouse card
// From Technical Note Misc #8, "Pascal 1.1 Firmware Protocol ID Bytes":
data[0x05] = 0x38;
data[0x07] = 0x18;
data[0x0b] = 0x01;
data[0x0c] = 0x20;
// From "AppleMouse // User's Manual", Appendix B:
//data[0x0c] = 0x20
data[0xfb] = 0xd6;
// Set 8 entrypoints to sofstwitches 2 to 1f
for (int i = 0; i < 14; i++) {
uint8_t base = 0x60 + 0x05 * i;
data[0x12+i] = base;
data[base+0] = 0x8D; // STA $C0x2
data[base+1] = 0x82 + i + ((slot->id + 1) << 4);
data[base+2] = 0xC0;
data[base+3] = 0x18; // CLC ;no error
data[base+4] = 0x60; // RTS
}
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, data, 256);
return 0;
}
static void
_mii_mouse_dispose(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_mouse_t *c = slot->drv_priv;
free(c);
slot->drv_priv = NULL;
}
static uint8_t
_mii_mouse_access(
mii_t * mii,
struct mii_slot_t *slot,
uint16_t addr,
uint8_t byte,
bool write)
{
mii_card_mouse_t *c = slot->drv_priv;
int psw = addr & 0x0F;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (psw) {
case 2: {
if (write) {
byte &= 0xf;
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, byte);
printf("%s: mouse mode %02x\n", __func__, byte);
mii->mouse.enabled = byte & mouseEnabled;
printf("Mouse %s\n", mii->mouse.enabled ? "enabled" : "disabled");
printf(" Interupt: %s\n", byte & mouseIntMoveEnabled ? "enabled" : "disabled");
printf(" Button: %s\n", byte & mouseIntButtonEnabled ? "enabled" : "disabled");
printf(" VBlank: %s\n", byte & mouseIntVBlankEnabled ? "enabled" : "disabled");
c->mode = byte;
}
} break;
case 3: // service mouse
// no need to handle that, the VBL handler does it
break;
case 4: {// read mouse
if (!mii->mouse.enabled)
break;
uint8_t status = 0;
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y))
status |= 1 << 5;
status |= c->last.button ? 1 << 6 : 0;
status |= mii->mouse.button ? 1 << 7 : 0;
mii_bank_poke(main, MOUSE_X_HI + c->slot_offset, mii->mouse.x >> 8);
mii_bank_poke(main, MOUSE_Y_HI + c->slot_offset, mii->mouse.y >> 8);
mii_bank_poke(main, MOUSE_X_LO + c->slot_offset, mii->mouse.x);
mii_bank_poke(main, MOUSE_Y_LO + c->slot_offset, mii->mouse.y);
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
// mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0xf); // already in place
c->last.x = mii->mouse.x;
c->last.y = mii->mouse.y;
c->last.button = mii->mouse.button;
} break;
case 5: // clear mouse
break;
case 7: // set mouse
if (byte == 0) {
mii->mouse.min_x = mii_bank_peek(main, CLAMP_MIN_LO) |
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
mii->mouse.max_x = mii_bank_peek(main, CLAMP_MAX_LO) |
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
} else if (byte == 1) {
mii->mouse.min_y = mii_bank_peek(main, CLAMP_MIN_LO) |
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
mii->mouse.max_y = mii_bank_peek(main, CLAMP_MAX_LO) |
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
}
printf("Mouse clamp to %d,%d - %d,%d\n",
mii->mouse.min_x, mii->mouse.min_y,
mii->mouse.max_x, mii->mouse.max_y);
break;
case 8: // home mouse
mii->mouse.x = mii->mouse.min_x;
mii->mouse.y = mii->mouse.min_y;
break;
case 0xc: // init mouse
mii->mouse.min_x = mii->mouse.min_y = 0;
mii->mouse.max_x = mii->mouse.max_y = 1023;
mii->mouse.enabled = 0;
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0x0);
break;
default:
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
break;
}
return 0;
}
static mii_slot_drv_t _driver = {
.name = "mouse",
.desc = "Mouse card",
.init = _mii_mouse_init,
.dispose = _mii_mouse_dispose,
.access = _mii_mouse_access,
};
MI_DRIVER_REGISTER(_driver);