Source to src/kms.c


Enter a symbol's name here to quickly find it.

/*  Previous - kms.c
 
 This file is distributed under the GNU Public License, version 2 or at
 your option any later version. Read the file gpl.txt for details.
 
 Keyboard, Mouse and Sound logic Emulation.
 
 In real hardware this logic is located in the NeXT Megapixel Display 
 or Soundbox
 
 */

#include "ioMem.h"
#include "ioMemTables.h"
#include "m68000.h"
#include "kms.h"
#include "sysReg.h"
#include "dma.h"
#include "rtcnvram.h"
#include "snd.h"
#include "video.h"
#include "host.h"

#define LOG_KMS_LEVEL LOG_DEBUG
#define IO_SEG_MASK	0x1FFFF


struct {
    struct {
        Uint8 snd_dma;
        Uint8 km;
        Uint8 transmit;
        Uint8 cmd;
    } status;
    
    Uint32 data;
    Uint32 km_data;
} kms;

void KMS_Reset() {
    kms.status.snd_dma  = 0;
    kms.status.km       = 0;
    kms.status.transmit = 0;
    kms.status.cmd      = 0;
    kms.data            = 0;
    kms.km_data         = 0;
}

/* KMS control and status register (0x0200E000) 
 *
 * x--- ---- ---- ---- ---- ---- ---- ----  sound out enable (r/w)
 * -x-- ---- ---- ---- ---- ---- ---- ----  sound output request (r)
 * --x- ---- ---- ---- ---- ---- ---- ----  sound output underrun detected (r/w)
 * ---- x--- ---- ---- ---- ---- ---- ----  sound in enable (r/w)
 * ---- -x-- ---- ---- ---- ---- ---- ----  sound input request (r)
 * ---- --x- ---- ---- ---- ---- ---- ----  sound input overrun detected (r/w)
 *
 * ---- ---- x--- ---- ---- ---- ---- ----  keyboard interrupt (r)
 * ---- ---- -x-- ---- ---- ---- ---- ----  keyboard data received (r)
 * ---- ---- --x- ---- ---- ---- ---- ----  keyboard data overrun detected (r/w)
 * ---- ---- ---x ---- ---- ---- ---- ----  non-maskable interrupt received (tilde and left or right cmd key) (r/w)
 * ---- ---- ---- x--- ---- ---- ---- ----  kms interrupt (r)
 * ---- ---- ---- -x-- ---- ---- ---- ----  kms data received (r)
 * ---- ---- ---- --x- ---- ---- ---- ----  kms data overrun detected (r/w)
 *
 * ---- ---- ---- ---- x--- ---- ---- ----  dma sound out transmit pending (r)
 * ---- ---- ---- ---- -x-- ---- ---- ----  dma sound out transmit in progress (r)
 * ---- ---- ---- ---- --x- ---- ---- ----  cpu data transmit pending (r)
 * ---- ---- ---- ---- ---x ---- ---- ----  cpu data transmit in progress (r)
 * ---- ---- ---- ---- ---- x--- ---- ----  rtx_pend ???
 * ---- ---- ---- ---- ---- -x-- ---- ----  rtx ???
 * ---- ---- ---- ---- ---- --x- ---- ----  kms enable (return from reset state) (r/w)
 * ---- ---- ---- ---- ---- ---x ---- ----  loop back transmitter data (r/w)
 *
 * ---- ---- ---- ---- ---- ---- xxxx xxxx  command to append on kms data (r/w)
 *
 * ---x ---x ---- ---x ---- ---- ---- ----  zero bits
 */


#define SNDOUT_DMA_ENABLE   0x80
#define SNDOUT_DMA_REQUEST  0x40
#define SNDOUT_DMA_UNDERRUN 0x20
#define SNDIN_DMA_ENABLE    0x08
#define SNDIN_DMA_REQUEST   0x04
#define SNDIN_DMA_OVERRUN   0x02

#define KBD_INT             0x80
#define KBD_RECEIVED        0x40
#define KBD_OVERRUN         0x20
#define NMI_RECEIVED        0x10
#define KMS_INT             0x08
#define KMS_RECEIVED        0x04
#define KMS_OVERRUN         0x02

#define TX_DMA_PENDING      0x80
#define TX_DMA              0x40
#define TX_CPU_PENDING      0x20
#define TX_CPU              0x10
#define RTX_PEND            0x08
#define RTX                 0x04
#define KMS_ENABLE          0x02
#define TX_LOOP             0x01


/* KMS commands */

/* Host commands */
#define KMSCMD_RESET    0xFF
#define KMSCMD_ASNDOUT  0xC7    /* analog sound out */
#define KMSCMD_KMREG    0xC5    /* access keyboard or mouse register */
#define KMSCMD_CTRLOUT  0xC4    /* access volume control logic */
#define KMSCMD_VOLCTRL  0xC2    /* simplified access to volume control */

#define KMSCMD_SND_IN   0x03    /* sound in */
#define KMSCMD_SND_OUT  0x07    /* sound out */
#define KMSCMD_SIO_MASK 0xC7    /* mask for sound in/out */

#define SIO_ENABLE      0x08    /* 1=enable, 0=disable sound */
#define SIO_DBL_SMPL    0x10    /* 1=double sample, 0=normal */
#define SIO_ZERO        0x20    /* double sample by 1=zero filling, 0=repetition */

/* Commands from KMS board */
#define KMSCMD_CODEC_IN 0xC7    /* CODEC sound in */
#define KMSCMD_KBD_RECV 0xC6    /* receive data from keyboard/mouse */
#define KMSCMD_SO_REQ   0x07    /* sound out request */
#define KMSCMD_SO_UNDR  0x0F    /* sound out underrun */

void KMS_command(Uint8 command, Uint32 data);


/* Keyboard Registers */
#define KM_REG_MASK     0xE0
#define KM_READ         0x10
#define KM_RESET        0x0F
#define KM_SET_ADDR     0xEF
#define KM_ADDR_MASK    0x0E
#define KM_ADDR_MOUSE   0x01


/* Device mask
 *
 * xxxx xxxx xxxx xxxx xxxx xxxx xxxx ----  polled devices (7 device addresses, 4 bit each)
 * ---- ---- ---- ---- ---- ---- ---- xxxx  poll speed
 */

Uint32 km_address = 0;
Uint32 km_dev_msk = 0;

static void access_km_reg(Uint32 data) {
    Uint8 reg_addr = (data>>24)&0xFF;
    Uint8 reg_data = (data>>16)&0xFF;
    
    if (reg_addr==KM_RESET) {
        Log_Printf(LOG_KMS_LEVEL, "Keyboard/Mouse: Reset");
        kms_response();
        return;
    }
    if (reg_addr==KM_SET_ADDR) {
        Log_Printf(LOG_KMS_LEVEL, "Keyboard/Mouse: Set address to %i",(reg_data&KM_ADDR_MASK)>>1);
        km_address = reg_data&KM_ADDR_MASK;
        kms_response();
        return;
    }
    
    bool device_kbd = (reg_addr&KM_ADDR_MOUSE) ? false : true;
    int device_addr = (reg_addr&KM_ADDR_MASK)>>1;
    int device_reg = (reg_addr&KM_REG_MASK)>>5;
    bool read_reg = (reg_addr&KM_READ) ? true : false;
    
    Log_Printf(LOG_KMS_LEVEL, "%s %s %i, register %i",read_reg?"Reading":"Writing",
               device_kbd?"keyboard":"mouse",device_addr,device_reg);
    
    if (reg_addr&KM_READ) {
        switch (device_reg) {
            case 0:
                Log_Printf(LOG_KMS_LEVEL, "Poll device");
                break;
            case 7:
                Log_Printf(LOG_KMS_LEVEL, "Request device revision");
                break;
            default:
                Log_Printf(LOG_WARN, "Unknown device register");
                break;
        }
    } else { // device write
        switch (device_reg) {
            case 0:
                if (device_kbd) {
                    Log_Printf(LOG_KMS_LEVEL, "Turn %s keyboard LED1",(reg_data&1)?"on":"off");
                    Log_Printf(LOG_KMS_LEVEL, "Turn %s keyboard LED2",(reg_data&2)?"on":"off");
                    break;
                }                
            default:
                Log_Printf(LOG_WARN, "Unknown device register");
                break;
        }
    }
    kms_response();
}

void KMS_command(Uint8 command, Uint32 data) {
    switch (command) {
        case KMSCMD_KBD_RECV: // keyboard poll
            km_dev_msk=data;
            return;

        case KMSCMD_RESET:
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Reset");
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Data = %08X",data);
            break;
        case KMSCMD_ASNDOUT:
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Analog sound out");
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Data = %08X",data);
            break;
        case KMSCMD_KMREG:
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Access keyboard/mouse register");
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Data = %08X",data);
            access_km_reg(data);
            break;
        case KMSCMD_CTRLOUT:
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Access volume control logic");
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Data = %08X",data);
            snd_gpo_access(data>>24);
            break;
        case KMSCMD_VOLCTRL:
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Access volume control (simple)");
            Log_Printf(LOG_KMS_LEVEL, "[KMS] Data = %08X",data);
            break;
            
        default: // commands without data
            if ((command&KMSCMD_SIO_MASK)==KMSCMD_SND_OUT) {
                Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out command:");

                if (command&SIO_ENABLE) {
                    Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out enable.");
                    if (command&SIO_DBL_SMPL) {
                        Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out double sample.");
                        if (command&SIO_ZERO) {
                            Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out double sample by zero filling.");
                        } else {
                            Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out double sample by repetition.");
                        }
                    } else {
                        Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out normal sample.");
                    }
                    snd_start_output(command&(SIO_DBL_SMPL|SIO_ZERO));
                } else {
                    Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound out disable.");
                    kms.status.snd_dma &= ~(SNDOUT_DMA_UNDERRUN|SNDOUT_DMA_REQUEST);
                    snd_stop_output();
                }
            } else if ((command&KMSCMD_SIO_MASK)==KMSCMD_SND_IN) {
                Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound in command");
                
                if (command&SIO_ENABLE) {
                    Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound in enable.");
                    snd_start_input(command);
                } else {
                    Log_Printf(LOG_KMS_LEVEL, "[KMS] Sound in disable.");
                    kms.status.snd_dma &= ~(SNDIN_DMA_OVERRUN|SNDIN_DMA_REQUEST);
                    snd_stop_input();
                }
            } else {
                Log_Printf(LOG_WARN, "[KMS] Unknown command!");
            }
            return;
    }
}


void KMS_Ctrl_Snd_Write(void) {
    Uint8 val = IoMem[IoAccessCurrentAddress&IO_SEG_MASK];
    
    kms.status.snd_dma &= ~(SNDOUT_DMA_ENABLE|SNDIN_DMA_ENABLE);
    kms.status.snd_dma |= (val&(SNDOUT_DMA_ENABLE|SNDIN_DMA_ENABLE));
    
    if (val&SNDOUT_DMA_UNDERRUN && (!snd_output_active())) {
        kms.status.snd_dma &= ~(SNDOUT_DMA_UNDERRUN|SNDOUT_DMA_REQUEST);
        set_interrupt(INT_SOUND_OVRUN, RELEASE_INT);
    }
    if (val&SNDIN_DMA_OVERRUN && (!snd_input_active())) {
        kms.status.snd_dma &= ~(SNDIN_DMA_OVERRUN|SNDIN_DMA_REQUEST);
        set_interrupt(INT_SOUND_OVRUN, RELEASE_INT);
    }
}

void KMS_Stat_Snd_Read(void) {
    IoMem[IoAccessCurrentAddress&IO_SEG_MASK] = kms.status.snd_dma;
}

void kms_sndout_underrun() {
    kms.status.snd_dma |=  SNDOUT_DMA_UNDERRUN|SNDOUT_DMA_REQUEST;
    set_interrupt(INT_SOUND_OVRUN, SET_INT);
}

void kms_sndin_overrun() {
    kms.status.snd_dma |=  SNDIN_DMA_OVERRUN|SNDIN_DMA_REQUEST;
    set_interrupt(INT_SOUND_OVRUN, SET_INT);
}

void KMS_Ctrl_KM_Write(void) {
    Uint8 val = IoMem[IoAccessCurrentAddress&IO_SEG_MASK];
    
    if (val&KBD_OVERRUN) {
        kms.status.km &= ~(KBD_RECEIVED|KBD_OVERRUN|KBD_INT);
        set_interrupt(INT_KEYMOUSE, RELEASE_INT);
    }
    if (val&NMI_RECEIVED) {
        kms.status.km &= ~NMI_RECEIVED;
        set_interrupt(INT_NMI, RELEASE_INT);
    }
    if (val&KMS_OVERRUN) {
        kms.status.km &= ~(KMS_RECEIVED|KMS_OVERRUN|KMS_INT);
        set_interrupt(INT_MONITOR, RELEASE_INT);
    }
}

void KMS_Stat_KM_Read(void) {
    IoMem[IoAccessCurrentAddress&IO_SEG_MASK] = kms.status.km;
}

void KMS_Ctrl_TX_Write(void) {
    Uint8 val = IoMem[IoAccessCurrentAddress&IO_SEG_MASK];
    
    kms.status.transmit &= ~(KMS_ENABLE|TX_LOOP);
    kms.status.transmit |= (val&(KMS_ENABLE|TX_LOOP));
}

void KMS_Stat_TX_Read(void) {
    IoMem[IoAccessCurrentAddress&IO_SEG_MASK] = kms.status.transmit;
}

void KMS_Ctrl_Cmd_Write(void) {
    kms.status.cmd = IoMem[IoAccessCurrentAddress&IO_SEG_MASK];
}

void KMS_Stat_Cmd_Read(void) {
    IoMem[IoAccessCurrentAddress&IO_SEG_MASK] = kms.status.cmd;
}


/* KMS data register (0x0200E004) */

void KMS_Data_Write(void) {
    kms.data = IoMem_ReadLong(IoAccessCurrentAddress&IO_SEG_MASK);
    KMS_command(kms.status.cmd, kms.data);
}

void KMS_Data_Read(void) {
    IoMem_WriteLong(IoAccessCurrentAddress&IO_SEG_MASK, kms.data);
}


/* KMS keyboard and mouse data register (0x0200E008) *
 *
 * x--- ---- ---- ---- ---- ---- ---- ----  always 0
 * -x-- ---- ---- ---- ---- ---- ---- ----  1 = no response error, 0 = normal event
 * --x- ---- ---- ---- ---- ---- ---- ----  1 = user poll, 0 = internal poll
 * ---x ---- ---- ---- ---- ---- ---- ----  1 = invalid/master, 0 = valid/slave (user/internal)
 * ---- xxxx ---- ---- ---- ---- ---- ----  device address (lowest bit 1 = mouse, 0 = keyboard)
 * ---- ---- xxxx xxxx ---- ---- ---- ----  chip revision: 0 = old, 1 = new, 2 = digital
 *
 * Mouse data:
 * ---- ---- ---- ---- xxxx xxx- ---- ----  mouse y
 * ---- ---- ---- ---- ---- ---x ---- ----  right button up (1) or down (0)
 * ---- ---- ---- ---- ---- ---- xxxx xxx-  mouse x
 * ---- ---- ---- ---- ---- ---- ---- ---x  left button up (1) or down (0)
 *
 * Keyboard data:
 * ---- ---- ---- ---- x--- ---- ---- ----  valid (1) or invalid (0)
 * ---- ---- ---- ---- -x-- ---- ---- ----  right alt
 * ---- ---- ---- ---- --x- ---- ---- ----  left alt
 * ---- ---- ---- ---- ---x ---- ---- ----  right command
 * ---- ---- ---- ---- ---- x--- ---- ----  left command
 * ---- ---- ---- ---- ---- -x-- ---- ----  right shift
 * ---- ---- ---- ---- ---- --x- ---- ----  left shift
 * ---- ---- ---- ---- ---- ---x ---- ----  control
 * ---- ---- ---- ---- ---- ---- x--- ----  key up (1) or down (0)
 * ---- ---- ---- ---- ---- ---- -xxx xxxx  keycode 
 */

#define NO_RESPONSE_ERR 0x40000000
#define USER_POLL       0x20000000
#define DEVICE_INVALID  0x10000000
#define DEVICE_MASTER   0x10000000

#define DEVICE_ADDR_MSK 0x0E000000
#define DEVICE_MOUSE    0x01000000

#define MOUSE_Y         0x0000FE00
#define MOUSE_RIGHT_UP  0x00000100
#define MOUSE_X         0x000000FE
#define MOUSE_LEFT_UP   0x00000001

#define KBD_KEY_VALID   0x00008000
#define KBD_MOD_MASK    0x00007F00
#define KBD_KEY_UP      0x00000080
#define KBD_KEY_MASK    0x0000007F


bool m_button_right = false;
bool m_button_left  = false;
bool m_move_left    = false;
bool m_move_up      = false;
int  m_move_x       = 0;
int  m_move_y       = 0;
int  m_move_dx      = 0;
int  m_move_dy      = 0;
void kms_mouse_move_step(void);


void KMS_KM_Data_Read(void) {
    IoMem_WriteLong(IoAccessCurrentAddress & IO_SEG_MASK, kms.km_data);
    
    kms.status.km &= ~(KBD_RECEIVED|KBD_INT);
    set_interrupt(INT_KEYMOUSE, RELEASE_INT);
}

static void kms_interrupt(void) {
    kms.status.cmd = KMSCMD_KBD_RECV;
    
    if (kms.status.km&KBD_RECEIVED) {
        kms.status.km |= KBD_OVERRUN;
    }
    kms.status.km |= (KBD_RECEIVED|KBD_INT);
    set_interrupt(INT_KEYMOUSE, SET_INT);
}

static bool kms_device_enabled(int dev_addr) {
    int i,mask;

    for (i=28; i>4; i-=4) {
        mask=(km_dev_msk>>i)&0xF;
        if(mask==dev_addr && mask!=0xF)
            return true;
    }
    Log_Printf(LOG_KMS_LEVEL, "[KMS] Device %i disabled (mask: %08X)",dev_addr,km_dev_msk);
    return false;
}

void kms_keydown(Uint8 modkeys, Uint8 keycode) {
    if ((keycode==0x26)&&(modkeys&0x18)) { /* backquote and one or both command keys */
        Log_Printf(LOG_WARN, "Keyboard initiated NMI!");
        set_interrupt(INT_NMI, SET_INT);
        return;
    }
    
    if ((keycode==0x25)&&((modkeys&0x28)==0x28)) { /* asterisk and left alt and left command key */
        Log_Printf(LOG_WARN, "Keyboard initiated CPU reset!");
        host_darkmatter(false);
        M68000_Reset(false);
        return;
    }
    
    if (keycode==0x58) { /* Power key */
        rtc_request_power_down();
        return;
    }
    
    if (kms_device_enabled(km_address)) {
        kms.km_data = (km_address<<24)|DEVICE_MASTER; /* keyboard */

        kms.km_data |= (modkeys<<8)|keycode|KBD_KEY_VALID;
        
        kms_interrupt();
    }
}

void kms_keyup(Uint8 modkeys, Uint8 keycode) {
    if (keycode==0x58) {
        rtc_stop_pdown_request();
        return;
    }
    
    if (kms_device_enabled(km_address)) {
        kms.km_data = (km_address<<24)|DEVICE_MASTER; /* keyboard */

        kms.km_data |= (modkeys<<8)|keycode|KBD_KEY_VALID|KBD_KEY_UP;
        
        kms_interrupt();
    }
}

void kms_mouse_button(bool left, bool down) {
    if (left) {
        m_button_left = down;
    } else {
        m_button_right = down;
    }
    
    if (kms_device_enabled(km_address|KM_ADDR_MOUSE)) {
        kms.km_data = (km_address|KM_ADDR_MOUSE)<<24; /* mouse */
        
        kms.km_data |= m_button_left?0:MOUSE_LEFT_UP;
        kms.km_data |= m_button_right?0:MOUSE_RIGHT_UP;
        
        kms_interrupt();
    }
}

#define MOUSE_STEP_FREQ 1000

void kms_mouse_move(int x, bool left, int y, bool up) {
    if (x<0 || y<0) abort();
    
    m_move_left = left;
    m_move_up   = up;

    int xsteps = x / 8; if(xsteps == 0) xsteps = 1;
    int ysteps = y / 8; if(ysteps == 0) ysteps = 1;
    
    m_move_x  = x;
    m_move_dx = x / xsteps;
    
    m_move_y  = y;
    m_move_dy = y / ysteps;
    
    CycInt_AddRelativeInterruptCycles(10, INTERRUPT_MOUSE);
}

void kms_mouse_move_step(void) {
    
    int x = m_move_x > m_move_dx ? m_move_dx : m_move_x;
    int y = m_move_y > m_move_dy ? m_move_dy : m_move_y;

    m_move_x -= x;
    m_move_y -= y;
    
    if (!m_move_left && x>0)  /* right */
        x=(0x40-x)|0x40;
    if (!m_move_up && y>0)    /* down */
        y=(0x40-y)|0x40;
    
    if (kms_device_enabled(km_address|KM_ADDR_MOUSE)) {
        kms.km_data = (km_address|KM_ADDR_MOUSE)<<24; /* mouse */
        
        kms.km_data |= (x<<1)&MOUSE_X;
        kms.km_data |= (y<<9)&MOUSE_Y;
        
        kms.km_data |= m_button_left?0:MOUSE_LEFT_UP;
        kms.km_data |= m_button_right?0:MOUSE_RIGHT_UP;
        
        kms_interrupt();
    }
}

void kms_response(void) {
    kms.km_data = km_address<<24; /* keyboard */
    kms.km_data |= USER_POLL;
    kms.km_data |= (NO_RESPONSE_ERR|DEVICE_INVALID); /* checked on real hardware */
    
    kms_interrupt();
}

void Mouse_Handler(void) {
    CycInt_AcknowledgeInterrupt();
    
    if (m_move_x > 0 || m_move_y > 0) {
        kms_mouse_move_step();
        CycInt_AddRelativeInterruptUs((1000*1000)/MOUSE_STEP_FREQ, INTERRUPT_MOUSE);
    }
}