/******************************************************************************
*
* Copyright (C) 2005-2017  Brian Berlin
* 
* 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/>
*
******************************************************************************
*
* FILENAME: pokey.c
*
* DESCRIPTION:  This contains functions for emulating the Pokey sound chip
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.1     05/11/05  bberlin      Creation
* 0.2     03/09/06  bberlin      Added a timer to 'pokey_init_scanline' for 
*                                  use with the pokey sound module
* 0.2.3   03/17/06  bberlin      Change 'Pokey_sound_init' function to use 
*                                  sound_freq parameter.
* 0.2.3   03/18/06  bberlin      Change 'pokey_init_scanline' to only send irq
*                                  when 6502 is running
* 0.2.3   03/19/06  bberlin      Took out Pokey_sound_init from here and put
*                                  in sdl_if.c
* 0.4.0   04/16/06  bberlin      Added Pokey_sound_init back to here
* 0.5.0   08/25/06  bberlin      Added 'pokey_get_pc_pot_ptr' function
* 0.5.0   08/25/06  bberlin      Added 'pokey_set_controller_type' function
******************************************************************************/
#include <string.h>
#include "console.h"
#include "pokey.h"
#include "gtia.h"
#include "antic.h"
#include "pokeysound.h"
#include "debug.h"
#include "sio.h"

#define SER_PORT_IRQ_DELAY  114
#define RANDOM9_CYCLE_LEN   511
#define RANDOM9_START_INDEX 122
#define RANDOM17_CYCLE_LEN  131071
#define RANDOM17_START_OFFSET 164
#define POKEY_TIMER1_INDEX 0
#define POKEY_TIMER2_INDEX 1
#define POKEY_TIMER4_INDEX 2
#define POKEY_TIMER_IRQ_COUNT 3
#define POKEY_AUDCTL_CLK_15KHZ  0x01
#define POKEY_AUDCTL_CH3_CH4    0x08
#define POKEY_AUDCTL_CH1_CH2    0x10
#define POKEY_AUDCTL_CH3_179MHZ 0x20
#define POKEY_AUDCTL_CH1_179MHZ 0x40
t_pokey pokey;
extern t_gtia gtia;
extern t_antic antic;
extern t_sio_interface sio;
extern t_cpu cpu6502;
extern ps_chip pokeyChip[MAXPOKEYS];

unsigned int pokey_cpu_clock;
static int key_is_pressed = 0;
static int prev_key_state[0x40];   /* last-scan state per 800 keyboard code */
static int prev_keypad_state[4][0x10]; /* last-scan state per 5200 keypad/key  */
static KBYTE random9tbl[RANDOM9_CYCLE_LEN];
static KBYTE random9stateval[RANDOM9_CYCLE_LEN + 1];
static int random_tables_built = 0;

void pokey_check_SIO ( void );
void pokey_check_keys_5200 ( void );
void pokey_check_keys_800 ( void );
static void pokey_build_random_tables ( void );
static unsigned int pokey_get_current_clock ( void );
static KBYTE pokey_get_random_value ( unsigned int current_clock );
static void pokey_random_reset_state ( unsigned int current_clock );
static void pokey_random_advance_state ( unsigned int current_clock );
static void pokey_update_irq_line ( void );
static void pokey_update_serial_done_status ( void );
static unsigned int pokey_timer_irq_calc_period ( int timer_index );
static void pokey_timer_irq_update_reload_cycles ( void );
static void pokey_timer_irq_update_next_clock ( void );
static int pokey_timer_irq_uses_fast_clock ( int timer_index );
static unsigned int pokey_timer_irq_init_exit_delay ( int timer_index );
static void pokey_timer_irq_arm_from_init_exit ( unsigned int current_clock );
static void pokey_timer_irq_suspend_for_init ( void );
static void pokey_timer_irq_service ( unsigned int current_clock );
static unsigned int pokey_random_step_poly9 ( unsigned int state );
static KBYTE pokey_random_poly9_value ( void );
static KBYTE pokey_random_poly17_value ( void );
static unsigned int pokey_random_step_visible_poly17 ( unsigned int state );
static unsigned int pokey_random_polymul ( unsigned int x, unsigned int y,
                                           unsigned int base,
                                           unsigned int hibit );

static void pokey_update_irq_line ( void ) {

    if ( pokey.irq_enable & ((~pokey.irq_status) & 0xff) )
        cpu_irq_assert();
    else
        cpu_irq_clear();

} /* end pokey_update_irq_line */

static void pokey_update_serial_done_status ( void ) {

    if ( pokey.serial_hw_status & SER_REG_OCCUPIED )
        pokey.irq_status |= IRQ_SER_DONE;
    else
        pokey.irq_status &= NOT_IRQ_SER_DONE;

} /* end pokey_update_serial_done_status */

static unsigned int pokey_timer_irq_calc_period ( int timer_index ) {

    KBYTE audctl = pokey.w_reg[AUDCTL];
    unsigned int mult = (audctl & POKEY_AUDCTL_CLK_15KHZ) ? 114 : 28;

    switch ( timer_index ) {
        case POKEY_TIMER1_INDEX:
            if ( audctl & POKEY_AUDCTL_CH1_179MHZ )
                return pokey.w_reg[AUDF1] + 4;
            return (pokey.w_reg[AUDF1] + 1) * mult;

        case POKEY_TIMER2_INDEX:
            if ( audctl & POKEY_AUDCTL_CH1_CH2 ) {
                unsigned int value = (pokey.w_reg[AUDF2] << 8) + pokey.w_reg[AUDF1];
                if ( audctl & POKEY_AUDCTL_CH1_179MHZ )
                    return value + 7;
                return (value + 1) * mult;
            }
            return (pokey.w_reg[AUDF2] + 1) * mult;

        case POKEY_TIMER4_INDEX:
            if ( audctl & POKEY_AUDCTL_CH3_CH4 ) {
                unsigned int value = (pokey.w_reg[AUDF4] << 8) + pokey.w_reg[AUDF3];
                if ( audctl & POKEY_AUDCTL_CH3_179MHZ )
                    return value + 7;
                return (value + 1) * mult;
            }
            return (pokey.w_reg[AUDF4] + 1) * mult;
    }

    return 1;
}

static void pokey_timer_irq_update_reload_cycles ( void ) {

    pokey.timer_irq[POKEY_TIMER1_INDEX].reload_cycles =
        pokey_timer_irq_calc_period ( POKEY_TIMER1_INDEX );
    pokey.timer_irq[POKEY_TIMER2_INDEX].reload_cycles =
        pokey_timer_irq_calc_period ( POKEY_TIMER2_INDEX );
    pokey.timer_irq[POKEY_TIMER4_INDEX].reload_cycles =
        pokey_timer_irq_calc_period ( POKEY_TIMER4_INDEX );
}

static void pokey_timer_irq_update_next_clock ( void ) {

    int i;

    pokey.timer_irq_next_clock_valid = 0;
    pokey.timer_irq_next_clock = 0;

    for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
        if ( !pokey.timer_irq[i].armed )
            continue;
        if ( !(pokey.irq_enable & (IRQ_TIMER1 << i)) )
            continue;

        if ( !pokey.timer_irq_next_clock_valid ||
                pokey.timer_irq[i].next_fire_clock < pokey.timer_irq_next_clock ) {
            pokey.timer_irq_next_clock = pokey.timer_irq[i].next_fire_clock;
            pokey.timer_irq_next_clock_valid = 1;
        }
    }
}

static int pokey_timer_irq_uses_fast_clock ( int timer_index ) {

    KBYTE audctl = pokey.w_reg[AUDCTL];

    switch ( timer_index ) {
        case POKEY_TIMER1_INDEX:
            return audctl & POKEY_AUDCTL_CH1_179MHZ;
        case POKEY_TIMER2_INDEX:
            return (audctl & POKEY_AUDCTL_CH1_CH2) &&
                   (audctl & POKEY_AUDCTL_CH1_179MHZ);
        case POKEY_TIMER4_INDEX:
            return (audctl & POKEY_AUDCTL_CH3_CH4) &&
                   (audctl & POKEY_AUDCTL_CH3_179MHZ);
    }

    return 0;
}

static unsigned int pokey_timer_irq_init_exit_delay ( int timer_index ) {

    unsigned int delay = pokey.timer_irq[timer_index].reload_cycles;
    KBYTE audctl = pokey.w_reg[AUDCTL];

    if ( pokey_timer_irq_uses_fast_clock ( timer_index ) )
        return delay;

    if ( audctl & POKEY_AUDCTL_CLK_15KHZ )
        return delay > 31 ? delay - 31 : 1;

    return delay > 4 ? delay - 4 : 1;
}

static void pokey_timer_irq_arm_from_init_exit ( unsigned int current_clock ) {

    int i;

    pokey_timer_irq_update_reload_cycles();

    for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
        if ( pokey_timer_irq_uses_fast_clock ( i ) )
            continue;

        /*
         * Init freezes 15/64KHz clocks but does not reset their counters.
         * This approximates init exit by aligning to the documented first
         * slow-clock edge;
         */
        pokey.timer_irq[i].armed = 1;
        pokey.timer_irq[i].next_fire_clock =
            current_clock + pokey_timer_irq_init_exit_delay ( i );
    }

    pokey_timer_irq_update_next_clock();
}

static void pokey_timer_irq_suspend_for_init ( void ) {

    int i;

    for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
        if ( pokey_timer_irq_uses_fast_clock ( i ) )
            continue;

        pokey.timer_irq[i].armed = 0;
    }

    pokey_timer_irq_update_next_clock();
}

static void pokey_timer_irq_service ( unsigned int current_clock ) {

    static const KBYTE irq_bits[POKEY_TIMER_IRQ_COUNT] = {
        IRQ_TIMER1, IRQ_TIMER2, IRQ_TIMER4
    };
    static const KBYTE not_irq_bits[POKEY_TIMER_IRQ_COUNT] = {
        NOT_IRQ_TIMER1, NOT_IRQ_TIMER2, NOT_IRQ_TIMER4
    };
    int i;
    int irq_changed = 0;

    for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
        t_pokey_timer_irq *timer = &pokey.timer_irq[i];

        if ( !timer->armed || !timer->reload_cycles )
            continue;

        if ( timer->next_fire_clock <= current_clock ) {
            unsigned int elapsed = current_clock - timer->next_fire_clock;

            if ( pokey.irq_enable & irq_bits[i] ) {
                if ( pokey.irq_status & irq_bits[i] )
                    irq_changed = 1;
                pokey.irq_status &= not_irq_bits[i];
            }

            timer->next_fire_clock =
                current_clock + timer->reload_cycles -
                (elapsed % timer->reload_cycles);
        }
    }

    pokey_timer_irq_update_next_clock();

    if ( irq_changed )
        pokey_update_irq_line();
}

static unsigned int pokey_random_step_poly9 ( unsigned int state ) {

    unsigned int c;

    c = ((state >> 3) ^ (state >> 8)) & 0x1;
    return ((state << 1) & 0x1ff) | c;
}

static unsigned int pokey_random_step_visible_poly17 ( unsigned int state ) {

    state >>= 1;
    if ( state & 0x80 ) {
        state ^= (1u << (17 + 7)) | (1u << (12 + 7));
    }

    return state;
}

static unsigned int pokey_random_polymul ( unsigned int x, unsigned int y,
                                           unsigned int base,
                                           unsigned int hibit ) {

    unsigned int accum = 0;

    while ( y ) {
        if ( y & 0x1 )
            accum ^= x;

        y >>= 1;
        x <<= 1;
        if ( x & hibit )
            x ^= base;
    }

    return accum;
}

static void pokey_build_random_tables ( void ) {

    unsigned int poly9;
    unsigned int states9[RANDOM9_CYCLE_LEN];
    KBYTE bits[RANDOM9_CYCLE_LEN];
    int i, j;

    if ( random_tables_built )
        return;

    /*
     * RANDOM is an 8-bit window over the 9-bit polynomial bitstream.
     * This start index matches the established POKEY RANDOM sequence.
     */
    poly9 = 0x001;
    for ( i = 0; i < RANDOM9_CYCLE_LEN; ++i ) {
        states9[i] = poly9;
        bits[i] = poly9 & 0x1;
        poly9 = pokey_random_step_poly9 ( poly9 );
    }

    for ( i = 0; i < RANDOM9_CYCLE_LEN; ++i ) {
        KBYTE value = 0;

        for ( j = 0; j < 8; ++j ) {
            int index = (RANDOM9_START_INDEX + i + j) % RANDOM9_CYCLE_LEN;
            value |= bits[index] << j;
        }

        random9tbl[i] = value;
        random9stateval[states9[i]] = value;
    }

    random_tables_built = 1;
}

static void pokey_random_reset_state ( unsigned int current_clock ) {

    pokey_build_random_tables();
    pokey.random_last_clock = current_clock;
    pokey.random_poly9 = 0x001;
    pokey.random_poly17 = 1u << 16;

    {
        int i;
        for ( i = 0; i < RANDOM17_START_OFFSET; ++i ) {
            pokey.random_poly17 =
                pokey_random_step_visible_poly17 ( pokey.random_poly17 );
        }
    }
}

static void pokey_random_advance_state ( unsigned int current_clock ) {

    unsigned int delta;
    unsigned int steps9;
    unsigned int steps17;

    if ( !(pokey.sk_ctl & 0x3) )
        return;

    if ( current_clock == pokey.random_last_clock )
        return;

    delta = current_clock - pokey.random_last_clock;
    steps9 = delta % RANDOM9_CYCLE_LEN;
    steps17 = delta % RANDOM17_CYCLE_LEN;

    while ( steps9 ) {
        pokey.random_poly9 = pokey_random_step_poly9 ( pokey.random_poly9 );
        --steps9;
    }

    while ( steps17 ) {
        pokey.random_poly17 =
            pokey_random_step_visible_poly17 ( pokey.random_poly17 );
        --steps17;
    }

    pokey.random_last_clock = current_clock;
}

static KBYTE pokey_random_poly9_value ( void ) {

    return random9stateval[pokey.random_poly9 & 0x1ff];
}

static KBYTE pokey_random_poly17_value ( void ) {

    return pokey.random_poly17 & 0xff;
}

static unsigned int pokey_get_current_clock ( void ) {

    int elapsed = antic.sl_cycle_count - cpu6502.i_cnt - antic.refresh_cycles - 1;
    if (antic.refresh_cycles && elapsed >= 66)
        elapsed +=9;

    if ( elapsed < 0 )
        elapsed = 0;

    return pokey_cpu_clock + elapsed;
}

void pokey_check_timer_irq ( void ) {

    unsigned int current_clock;

    if ( !pokey.timer_irq_next_clock_valid )
        return;

    current_clock = pokey_get_current_clock();
    if ( pokey.timer_irq_next_clock <= current_clock )
        pokey_timer_irq_service ( current_clock );
}

static KBYTE pokey_get_random_value ( unsigned int current_clock ) {

    if ( !(pokey.sk_ctl & 0x3) )
        return 0xff;

    pokey_random_advance_state ( current_clock );

    if ( pokey.w_reg[AUDCTL] & 0x80 )
        return pokey_random_poly9_value();

    return pokey_random_poly17_value();
}

void pokey_set_pot_value ( int pot, KBYTE value ) {
    pokey.pc_pot[pot] = value;
}

void pokey_set_trackball_center_value ( int pot, KBYTE value ) {
    pokey.trackball_center_pot[pot] = value;
}

void pokey_set_keypad_down ( int player, int kbcode, int down ) {
    pokey.key_pressed[player][kbcode] = down;
}

void pokey_set_top_button_down ( int player, int down ) {
    pokey.shift_pressed[player] = down;
}

void pokey_set_shift_key_down ( int down ) {
    pokey.shift_pressed[0] = down;
}

void pokey_set_ctrl_key_down ( int down ) {
    pokey.ctrl_pressed = down;
}

void pokey_set_break_key_down ( int down ) {
    pokey.break_pressed = down;
}

void pokey_set_controller_type ( int player, int type ) {
    pokey.controller_type[player] = type;
}

/******************************************************************************
**  Function   :  pokey_reset
**                            
**  Objective  :  This function initializes pokey registers
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_reset ( void ) {

    int i;

    pokey_cpu_clock = 0;
    key_is_pressed = 0;
    memset ( prev_key_state, 0, sizeof(prev_key_state) );
    memset ( prev_keypad_state, 0, sizeof(prev_keypad_state) );
    pokey_build_random_tables();

    /*
     * Initialize Pot stuff
     */
    for ( i = 0; i < 8; ++i ) {
        pokey.pot[i] = POT_OPEN_VALUE;
        pokey.trackball_center_pot[i] = POT_CENTER_VALUE;
        if ( console_get_machine_type() == MACHINE_TYPE_5200 )
            pokey.pc_pot[i] = POT_CENTER_VALUE;
        else
            pokey.pc_pot[i] = POT_800_CENTER_VALUE;
        pokey.controller_type[i>>1] = 0;
    }

    memset ( pokey.w_reg, 0, sizeof(KBYTE)*0x10 );
    memset ( pokey.r_reg, 0, sizeof(KBYTE)*0x10 );
    pokey.kbcode = 0;
    pokey.irq_status = 0xff;
    pokey.sk_status = 0xff;

    pokey.irq_enable = 0x00;
    pokey.sk_ctl = 0x01;

    pokey.serial_in = 0x0;
    pokey.serial_out = 0x0;
    pokey.serial_hw_status = 0;
    pokey.serial_mode_status = 0;
    pokey.serial_irq_time = 1;
    pokey.serial_out_time = 1;
    pokey_random_reset_state ( 0 );
    memset ( pokey.timer_irq, 0, sizeof(pokey.timer_irq) );
    pokey.timer_irq_next_clock = 0;
    pokey.timer_irq_next_clock_valid = 0;
    if ( pokey.sk_ctl & 0x3 )
        pokey_timer_irq_arm_from_init_exit ( 0 );
    
    /*
     * Initialize Pokey Sound with default freq
     */
    pokey_sound_init (44100, 1);
    //Pokey_sound_init (FREQ_17_APPROX, (unsigned short)freq, 1);

    return;

} /* end pokey_reset */

/******************************************************************************
**  Function   :  pokey_check_clock_zero_cross
**                            
**  Objective  :  This function checks for an upcoming pokey_cpu_clock zero
**                crossing and shifts the clock to make up for it
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/
void pokey_check_clock_zero_cross ( void ) {

    unsigned int next_frame_clk, shift, lastClock;
    int i;

    next_frame_clk = console_get_ptr()->scanlines * 114 + pokeyChip[0].lastClock;

    if (next_frame_clk > pokeyChip[0].lastClock)
        return;

    /*
     * If we are still here, means a zero cross occurred
     */
    lastClock = pokeyChip[0].lastClock;
    shift = 0 - lastClock;
    shift += pokey_sound_shift_clock ( shift );

    if (console_get_ptr()->machine_type != MACHINE_TYPE_5200)
        sio_clock_shift ( shift );

    pokey.serial_irq_time += shift;
    pokey.serial_out_time += shift;
    if ( pokey.sk_ctl & 0x3 )
        pokey.random_last_clock += shift;
    for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
        if ( pokey.timer_irq[i].armed )
            pokey.timer_irq[i].next_fire_clock += shift;
    }
    if ( pokey.timer_irq_next_clock_valid )
        pokey.timer_irq_next_clock += shift;

    pokey_cpu_clock += shift;
}

/******************************************************************************
**  Function   :  pokey_read_register
**                            
**  Objective  :  This function reads from the input register
**
**  Parameters :  addr - 6502 memory address of the register
**                                                
**  return     :  value at register
**      
******************************************************************************/ 
KBYTE pokey_read_register ( KWORD addr ) {

    KBYTE new_address = addr & 0x0f;
    KBYTE value;
    unsigned int current_clock = pokey_get_current_clock();
    int i;

    switch ( new_address ) {
        case POT0:
        case POT1:
        case POT2:
        case POT3:
        case POT4:
        case POT5:
        case POT6:
        case POT7:
            if ( !gtia.pot_enable && console_get_machine_type() == MACHINE_TYPE_5200) {
                if ( pokey.controller_type[new_address>>1] )
                    return pokey.r_reg[new_address] =
                        pokey.trackball_center_pot[new_address];
                else
                    return pokey.r_reg[new_address] = POT_OPEN_VALUE;
            }
            if ( pokey.pot[new_address] < pokey.pot_scanline) {
                return pokey.r_reg[new_address] = pokey.pot[new_address];
            }
            else {
                return pokey.r_reg[new_address] = pokey.pot_scanline;
            }

            break;
        case ALLPOT:
            value = 0x00;
            for ( i = 0; i < 8; ++i ) {
                if ( pokey.pot[i] > pokey.pot_scanline )
                    value |= (1 << i);
            }
            return pokey.r_reg[ALLPOT] = value;
            break;
        case KBCODE:
            if ( console_get_machine_type() == MACHINE_TYPE_5200 ) {
                if ( pokey.sk_ctl & SK_KEY_DEB )
                    return 0xff;
                else
                    return pokey.kbcode;
            }
            else
                return pokey.kbcode;
            break;
        case RANDOM:
            return pokey.r_reg[RANDOM] = pokey_get_random_value(current_clock);
            break;
        case SERIN:
            return pokey.serial_in;
            break;
        case IRQST:
            pokey_timer_irq_service ( current_clock );
            return pokey.irq_status;
            break;
        case SKSTAT:
            pokey.sk_status &= NOT_SK_DIRECT_SERIN;
            pokey.sk_status |= sio_update_serial_in_bit();
            return pokey.sk_status;
            break;
    }

    return 0xff;

} /* end pokey_read_register */

KBYTE pokey_read_register_debug ( KWORD addr ) {

    debug_search_for_breakpoint (BK_MEM, addr );

    return pokey_read_register ( addr );
}

/******************************************************************************
**  Function   :  pokey_write_register
**                            
**  Objective  :  This function performs actions caused by writes to the 
**                registers the input register
**
**  Parameters :  addr - 6502 memory address of the register
**                value - value to write to register
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_write_register ( KWORD addr, KBYTE value ) {

    KBYTE new_address = addr & 0x0f;
    unsigned int current_clock = pokey_get_current_clock();
    KBYTE old_sk_ctl = pokey.sk_ctl;
    int i;

    switch ( new_address ) {

        /*
         * Use Pokey Sound for audio
         */
        case AUDF1:
        case AUDF2:
        case AUDF3:
        case AUDF4:
            pokey_timer_irq_service ( current_clock );
            pokey_sound_reg_update ( addr, value, 0, 2 );
            //Update_pokey_sound ((uint16) addr, (uint8) value, 0, 2 );
            pokey.w_reg[new_address] = value;
            pokey_timer_irq_update_reload_cycles();
            break;
        case AUDC1:
        case AUDC2:
        case AUDC3:
        case AUDC4:
            pokey_sound_reg_update ( addr, value, 0, 2 );
            //Update_pokey_sound ((uint16) addr, (uint8) value, 0, 2 );
            pokey.w_reg[new_address] = value;
            break;
        case STIMER:
            pokey_timer_irq_service ( current_clock );
            pokey_sound_reg_update ( addr, value, 0, 2 );
            //Update_pokey_sound ((uint16) addr, (uint8) value, 0, 2 );
            pokey.w_reg[new_address] = value;
            pokey_timer_irq_update_reload_cycles();
            for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
                if ( !(pokey.sk_ctl & 0x3) &&
                        !pokey_timer_irq_uses_fast_clock ( i ) )
                    continue;

                pokey.timer_irq[i].armed = 1;
                pokey.timer_irq[i].next_fire_clock =
                    current_clock + pokey.timer_irq[i].reload_cycles;
            }
            pokey_timer_irq_update_next_clock();
            break;
        case AUDCTL:
            pokey_timer_irq_service ( current_clock );
            pokey_random_advance_state ( current_clock );
            pokey_sound_reg_update ( addr, value, 0, 2 );
            pokey.w_reg[new_address] = value;
            pokey_timer_irq_update_reload_cycles();
            if ( !(pokey.sk_ctl & 0x3) ) {
                for ( i = 0; i < POKEY_TIMER_IRQ_COUNT; ++i ) {
                    if ( pokey_timer_irq_uses_fast_clock ( i ) ) {
                        if ( !pokey.timer_irq[i].armed ) {
                            pokey.timer_irq[i].armed = 1;
                            pokey.timer_irq[i].next_fire_clock =
                                current_clock + pokey.timer_irq[i].reload_cycles;
                        }
                    }
                    else {
                        pokey.timer_irq[i].armed = 0;
                    }
                }
                pokey_timer_irq_update_next_clock();
            }
            break;

        case SKREST:
            pokey.sk_status |= 0xe0;
            break;
        case POTGO:
            if ( pokey.sk_ctl & SK_FAST_POT )
                pokey.pot_scanline = 226;
            else
                pokey.pot_scanline = 1;
            break;
        case SEROUT:
            pokey.serial_out = value;
            pokey.serial_hw_status |= SER_OUT_OCCUPIED;
            if ( !(pokey.serial_hw_status & SER_REG_OCCUPIED) )
                pokey.serial_irq_time = pokey_cpu_clock + SER_PORT_IRQ_DELAY;
            break;
        case IRQEN:
            pokey_timer_irq_service ( current_clock );
            pokey.irq_enable = value;
            pokey.irq_status |= ~value & NOT_IRQ_SER_DONE;
            pokey_update_serial_done_status();
            pokey_timer_irq_update_next_clock();
            pokey_update_irq_line();

            break;
        case SKCTL:
            pokey_timer_irq_service ( current_clock );
            pokey_random_advance_state ( current_clock );
            pokey_sound_reg_update ( addr, value, 0, 2 );
            if ( (value & 0x70) && !(pokey.sk_ctl & 0x70) ) {
                if ( sio.current_device->status == SIO_STATUS_READ )
                    pokey.serial_mode_status = SER_MODE_QUEUED;
                else
                    pokey.serial_mode_status = SER_MODE_ON;
            }
            else if ( !(value & 0x70) ) {
                pokey.serial_mode_status = SER_MODE_OFF;
                /* Serial clock disabled — output hardware is idle */
                pokey.serial_hw_status = 0;
                pokey.serial_irq_time = 1;
                pokey.serial_out_time = 1;
                pokey_update_serial_done_status();
                pokey_update_irq_line();
            }

            pokey.sk_ctl = value;
            if ( !(value & 0x3) )
                pokey_timer_irq_suspend_for_init();
            if ( !(old_sk_ctl & 0x3) && (value & 0x3) ) {
                pokey_random_reset_state ( current_clock + 1 );
                pokey_timer_irq_arm_from_init_exit ( current_clock );
            }

            pokey.current_key_code = 0;
            break;
    }

    return;

} /* end pokey_write_register */

void pokey_write_register_debug ( KWORD addr, KBYTE value ) {

    debug_search_for_breakpoint (BK_MEM, addr );

    pokey_write_register ( addr, value );
}

/******************************************************************************
**  Function   :  pokey_init_scanline 
**                            
**  Objective  :  This function performs scanline actions required by pokey
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_init_scanline ( void ) {

    pokey_cpu_clock += 114;

    /*
     * Handle pot updates per scanline
     */
    if ( pokey.pot_scanline < 228 )
        pokey.pot_scanline++;

    memcpy ( pokey.pot, pokey.pc_pot, sizeof(KBYTE)*8 ); 

    /*
     * Reset keyboard scanning on counter reset
     */
    if ( pokey.current_key_code == 0 ) {
        if ( !key_is_pressed ) {
            pokey.sk_status |= SK_KEY_PRESS;
            pokey.irq_status |= IRQ_KEY_PRESS;
        }
        key_is_pressed = 0;
    }

    /*
     * Check for keys pressed and/or SIO ready
     */
    if ( console_get_machine_type() == MACHINE_TYPE_5200 ) {
        pokey_check_keys_5200 ( );
    }
    else {
        pokey_check_SIO ( );
        sio_check();
        pokey_check_keys_800 ( );
    }

    return;

} /* end pokey_init_scanline  */

/******************************************************************************
**  Function   :  pokey_receive_SIO
**                            
**  Objective  :  This function takes the byte from SIO and sets interupts
**
**  Parameters :  data - byte from SIO
**                                                
**  return     :  NONE
**      
******************************************************************************/
void pokey_receive_SIO ( KBYTE data ) {

    /*
     * Check if we just turned on the serial mode (SKCTL)
     *    Need this check for cassettes
     */
    if ( pokey.serial_mode_status == SER_MODE_QUEUED ) {
        pokey.serial_mode_status = SER_MODE_ON;
        return;
    }
    else if ( !pokey.serial_mode_status )
        return;

    /*
     * Check for Serial Byte already here and set overrun if so
     */
    if ( !(pokey.irq_status & IRQ_SER_RED) ) {
        pokey.sk_status &= NOT_SK_SERIN_OVERRUN;
    }

    /*
     * Get Next Byte and Interrupt if desired
     */
    pokey.serial_in = data;
    if ( (pokey.irq_enable & IRQ_SER_RED) ) {
        pokey.irq_status &= NOT_IRQ_SER_RED;
        pokey_update_irq_line();
    }

} /* end pokey_receive_SIO  */

/******************************************************************************
**  Function   :  pokey_check_SIO
**                            
**  Objective  :  This function checks for needed action on the Serial I/O
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_check_SIO ( void ) {

    if ( !pokey.serial_mode_status )
        return;

    /*
     * Data is started out the serial shift register, meaning the SEROUT
     *   is empty and ready for more data
     */
    if ( pokey_cpu_clock == pokey.serial_irq_time ) {
        if ( pokey.serial_hw_status & SER_OUT_OCCUPIED ) {
            pokey.serial_shift = pokey.serial_out;
            pokey.serial_out_time = pokey_cpu_clock + sio.current_device->baud - SER_PORT_IRQ_DELAY;
            pokey.serial_hw_status = SER_REG_OCCUPIED;
            pokey_update_serial_done_status();
            if ( (pokey.irq_enable & IRQ_SER_NEED) ) {
                pokey.irq_status &= NOT_IRQ_SER_NEED;
            }
            pokey_update_irq_line();
        }
        else {
            pokey.serial_hw_status = 0;
            pokey.serial_out_time = 1;
            pokey.serial_irq_time = 1;
            pokey_update_serial_done_status();
            pokey_update_irq_line();
        }

    } /* end if irq time */

    /*
     * Send data from the output shift register
     */
    if ( pokey_cpu_clock == pokey.serial_out_time ) {
        sio_receive ( pokey.serial_shift );
        pokey.serial_hw_status &= NOT_SER_REG_OCCUPIED;
        pokey.serial_irq_time = pokey_cpu_clock + SER_PORT_IRQ_DELAY;
        pokey_update_serial_done_status();
        pokey_update_irq_line();
    }

        /*
         * Send BREAK
         */
        if ( pokey.sk_ctl & SK_SER_BREAK ) {

        }

} /* end pokey_check_SIO  */

/******************************************************************************
**  Function   :  pokey_check_keys_5200
**                            
**  Objective  :  This function checks key interrupts on the 5200
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_check_keys_5200 ( void ) {

    int key_index;

    if ( pokey.sk_ctl & SK_KEY_SCAN ) {

        int code = pokey.current_key_code;
        int now, fire;
        key_index = (code & 0x1e) >> 1;
        now = pokey.key_pressed[gtia.keypad][key_index];

        if ( now ) {
            key_is_pressed = 1;                 /* keeps SKSTAT key-down asserted */
            pokey.sk_status &= NOT_SK_KEY_PRESS;

            /*
             * K0/K5 are unused, so each physical key aliases to four raw codes:
             * an adjacent even/odd pair ($04/$05) and a +0x20 pair ($24/$25).
             *
             * Debounce (SKCTL bit 0) selects how a held key reports:
             *   - disabled: POKEY free-runs, reporting once per adjacent alias
             *     pair, i.e. every 32 scan lines.  Fire on
             *     the odd code of the pair to avoid adjacent duplicate IRQs.
             *   - enabled:  report only a fresh press (rising edge), then wait
             *     for release.  5200 software enables debounce right after the
             *     first report to stop repeats
             */
            if ( pokey.sk_ctl & SK_KEY_DEB )
                fire = !prev_keypad_state[gtia.keypad][key_index];
            else
                fire = (code & 0x01);

            if ( fire ) {
                /* overrun if a keyboard IRQ is still pending (active-low) */
                if ( !(pokey.irq_status & IRQ_KEY_PRESS) )
                    pokey.sk_status &= NOT_SK_KEY_OVERRUN;

                pokey.kbcode = code;
                /* top button (KR2) reads as SHIFT+CONTROL -> KBCODE bits 6/7 */
                if ( pokey.shift_pressed[gtia.keypad] )
                    pokey.kbcode |= KB_SHIFT_KEY | KB_CTRL_KEY;

                if ( pokey.irq_enable & IRQ_KEY_PRESS ) {
                    pokey.irq_status &= NOT_IRQ_KEY_PRESS;
                    if ( antic.run_6502_ptr != antic_do_nothing )
                        pokey_update_irq_line();
                }
            }

        } /* end if normal key is pressed */

        prev_keypad_state[gtia.keypad][key_index] = now;

        /*
         * Check for Shift key (top button for 5200) at keycode 0x3d
         */
        if ( pokey.current_key_code == 0x3d ) {
            if ( pokey.shift_pressed[gtia.keypad] ) {
                pokey.sk_status &= NOT_SK_SHIFT_KEY; 
                if ( pokey.irq_enable & IRQ_BK_PRESS ) { 
                    pokey.irq_status &= NOT_IRQ_BK_PRESS;
                    if ( antic.run_6502_ptr != antic_do_nothing )
                        pokey_update_irq_line();
                }
            }
            else {
                pokey.sk_status |= SK_SHIFT_KEY;
                pokey.irq_status |= IRQ_BK_PRESS;
                pokey_update_irq_line();
            }

        } /* end if shift key checked */

        pokey.current_key_code++;
        pokey.current_key_code &= 0x3f;

    } /* end if scanning enabled */

} /* end pokey_check_keys_5200  */

/******************************************************************************
**  Function   :  pokey_check_keys_800
**                            
**  Objective  :  This function checks key interrupts on the 5200
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/ 
void pokey_check_keys_800 ( void ) {

    if ( pokey.sk_ctl & SK_KEY_SCAN ) {

        int code = pokey.current_key_code;
        int now  = pokey.key_pressed[0][code];

        /*
         * Normal Key - fire an IRQ on each up->down transition (rising edge),
         * independent of other held keys.  
         */
        if ( now ) {
            key_is_pressed = 1;                 /* keeps SKSTAT key-down asserted */
            pokey.sk_status &= NOT_SK_KEY_PRESS;

            if ( !prev_key_state[code] ) {      /* up -> down : a new key press */
                /* overrun if a keyboard IRQ is still pending (active-low) */
                if ( !(pokey.irq_status & IRQ_KEY_PRESS) )
                    pokey.sk_status &= NOT_SK_KEY_OVERRUN;

                pokey.kbcode = code;
                pokey.kbcode |= pokey.shift_pressed[0] ? KB_SHIFT_KEY : 0;
                pokey.kbcode |= pokey.ctrl_pressed ? KB_CTRL_KEY : 0;
                if ( pokey.irq_enable & IRQ_KEY_PRESS ) {
                    pokey.irq_status &= NOT_IRQ_KEY_PRESS;
                    if ( antic.run_6502_ptr != antic_do_nothing )
                        pokey_update_irq_line();
                }
            }

        } /* end if normal key is pressed */

        prev_key_state[code] = now;             /* track edge for this code */

        /*
         * Check for Shift key - detected in parallel during scan (code 0x10).
         * Only SKSTAT bit 3 tracks it here; KBCODE bits 6/7 are latched at key
         * press time above and must not be modified after the fact.
         */
        if ( pokey.current_key_code == 0x10 ) {
            if ( pokey.shift_pressed[0] )
                pokey.sk_status &= NOT_SK_SHIFT_KEY;
            else
                pokey.sk_status |= SK_SHIFT_KEY;

        } /* end if shift key checked */

        /*
         * Check for Break key - detected in parallel at scan code 0x30
         */
        if ( pokey.current_key_code == 0x30 ) {
            if ( pokey.break_pressed ) {
                if ( pokey.irq_enable & IRQ_BK_PRESS ) { 
                    pokey.irq_status &= NOT_IRQ_BK_PRESS;
                    if ( antic.run_6502_ptr != antic_do_nothing )
                        pokey_update_irq_line();
                }
            }
            else {
                pokey.irq_status |= IRQ_BK_PRESS;
                pokey_update_irq_line();
            }
        }

        pokey.current_key_code++;
        pokey.current_key_code &= 0x3f;

    } /* end if scanning enabled */

} /* end pokey_check_keys_800  */
