nkeynes@11: /** nkeynes@465: * $Id: aica.c,v 1.25 2007-10-27 05:47:21 nkeynes Exp $ nkeynes@11: * nkeynes@465: * This module implements the AICA's IO interfaces, as well nkeynes@465: * as providing the core AICA module to the system. nkeynes@11: * nkeynes@11: * Copyright (c) 2005 Nathan Keynes. nkeynes@11: * nkeynes@11: * This program is free software; you can redistribute it and/or modify nkeynes@11: * it under the terms of the GNU General Public License as published by nkeynes@11: * the Free Software Foundation; either version 2 of the License, or nkeynes@11: * (at your option) any later version. nkeynes@11: * nkeynes@11: * This program is distributed in the hope that it will be useful, nkeynes@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@11: * GNU General Public License for more details. nkeynes@11: */ nkeynes@11: nkeynes@35: #define MODULE aica_module nkeynes@35: nkeynes@131: #include nkeynes@11: #include "dream.h" nkeynes@66: #include "dreamcast.h" nkeynes@15: #include "mem.h" nkeynes@106: #include "aica/aica.h" nkeynes@61: #include "armcore.h" nkeynes@106: #include "aica/audio.h" nkeynes@11: #define MMIO_IMPL nkeynes@11: #include "aica.h" nkeynes@11: nkeynes@11: MMIO_REGION_READ_DEFFN( AICA0 ) nkeynes@11: MMIO_REGION_READ_DEFFN( AICA1 ) nkeynes@11: nkeynes@23: void aica_init( void ); nkeynes@23: void aica_reset( void ); nkeynes@23: void aica_start( void ); nkeynes@23: void aica_stop( void ); nkeynes@35: void aica_save_state( FILE *f ); nkeynes@35: int aica_load_state( FILE *f ); nkeynes@30: uint32_t aica_run_slice( uint32_t ); nkeynes@23: nkeynes@23: struct dreamcast_module aica_module = { "AICA", aica_init, aica_reset, nkeynes@23: aica_start, aica_run_slice, aica_stop, nkeynes@35: aica_save_state, aica_load_state }; nkeynes@15: nkeynes@465: struct aica_state_struct { nkeynes@465: uint32_t time_of_day; nkeynes@465: /** nkeynes@465: * Keep track of what we've done so far this second, to try to keep the nkeynes@465: * precision of samples/second. nkeynes@465: */ nkeynes@465: uint32_t samples_done; nkeynes@465: uint32_t nanosecs_done; nkeynes@465: /** nkeynes@465: * Event (IRQ) state nkeynes@465: */ nkeynes@465: int event_pending; nkeynes@465: int clear_count; nkeynes@465: }; nkeynes@465: nkeynes@465: static struct aica_state_struct aica_state; nkeynes@465: nkeynes@173: nkeynes@11: /** nkeynes@11: * Initialize the AICA subsystem. Note requires that nkeynes@11: */ nkeynes@11: void aica_init( void ) nkeynes@11: { nkeynes@11: register_io_regions( mmio_list_spu ); nkeynes@11: MMIO_NOTRACE(AICA0); nkeynes@11: MMIO_NOTRACE(AICA1); nkeynes@11: arm_mem_init(); nkeynes@66: aica_reset(); nkeynes@11: } nkeynes@11: nkeynes@11: void aica_reset( void ) nkeynes@11: { nkeynes@35: arm_reset(); nkeynes@465: aica_state.time_of_day = 0x5bfc8900; nkeynes@465: aica_state.samples_done = 0; nkeynes@465: aica_state.nanosecs_done = 0; nkeynes@465: aica_state.event_pending = 0; nkeynes@465: aica_state.clear_count = 0; nkeynes@66: aica_event(2); /* Pre-deliver a timer interrupt */ nkeynes@11: } nkeynes@11: nkeynes@23: void aica_start( void ) nkeynes@23: { nkeynes@23: nkeynes@23: } nkeynes@23: nkeynes@30: uint32_t aica_run_slice( uint32_t nanosecs ) nkeynes@23: { nkeynes@23: /* Run arm instructions */ nkeynes@35: int reset = MMIO_READ( AICA2, AICA_RESET ); nkeynes@66: if( (reset & 1) == 0 ) { /* Running */ nkeynes@465: int num_samples = (int)((uint64_t)AICA_SAMPLE_RATE * (aica_state.nanosecs_done + nanosecs) / 1000000000) - aica_state.samples_done; nkeynes@73: num_samples = arm_run_slice( num_samples ); nkeynes@73: audio_mix_samples( num_samples ); nkeynes@73: nkeynes@465: aica_state.samples_done += num_samples; nkeynes@465: aica_state.nanosecs_done += nanosecs; nkeynes@35: } nkeynes@465: if( aica_state.nanosecs_done > 1000000000 ) { nkeynes@465: aica_state.samples_done -= AICA_SAMPLE_RATE; nkeynes@465: aica_state.nanosecs_done -= 1000000000; nkeynes@465: aica_state.time_of_day++; nkeynes@73: } nkeynes@43: return nanosecs; nkeynes@23: } nkeynes@23: nkeynes@23: void aica_stop( void ) nkeynes@23: { nkeynes@23: nkeynes@23: } nkeynes@23: nkeynes@35: void aica_save_state( FILE *f ) nkeynes@35: { nkeynes@465: fwrite( &aica_state, sizeof(struct aica_state_struct), 1, f ); nkeynes@35: arm_save_state( f ); nkeynes@465: audio_save_state(f); nkeynes@35: } nkeynes@35: nkeynes@35: int aica_load_state( FILE *f ) nkeynes@35: { nkeynes@465: fread( &aica_state, sizeof(struct aica_state_struct), 1, f ); nkeynes@465: arm_load_state( f ); nkeynes@465: return audio_load_state(f); nkeynes@35: } nkeynes@35: nkeynes@61: /* Note: This is probably not necessarily technically correct but it should nkeynes@61: * work in the meantime. nkeynes@61: */ nkeynes@61: nkeynes@61: void aica_event( int event ) nkeynes@61: { nkeynes@465: if( aica_state.event_pending == 0 ) nkeynes@61: armr.int_pending |= CPSR_F; nkeynes@465: aica_state.event_pending |= (1<> 7, reg % 128, val ); nkeynes@37: // DEBUG( "AICA0 Write %08X => %08X", val, reg ); nkeynes@11: } nkeynes@11: nkeynes@11: /* Write to channels 32-64 */ nkeynes@11: void mmio_region_AICA1_write( uint32_t reg, uint32_t val ) nkeynes@11: { nkeynes@35: MMIO_WRITE( AICA1, reg, val ); nkeynes@66: aica_write_channel( (reg >> 7) + 32, reg % 128, val ); nkeynes@37: // DEBUG( "AICA1 Write %08X => %08X", val, reg ); nkeynes@11: } nkeynes@11: nkeynes@66: /** nkeynes@66: * AICA control registers nkeynes@66: */ nkeynes@11: void mmio_region_AICA2_write( uint32_t reg, uint32_t val ) nkeynes@11: { nkeynes@35: uint32_t tmp; nkeynes@465: nkeynes@35: switch( reg ) { nkeynes@35: case AICA_RESET: nkeynes@35: tmp = MMIO_READ( AICA2, AICA_RESET ); nkeynes@37: if( (tmp & 1) == 1 && (val & 1) == 0 ) { nkeynes@35: /* ARM enabled - execute a core reset */ nkeynes@37: DEBUG( "ARM enabled" ); nkeynes@35: arm_reset(); nkeynes@465: aica_state.samples_done = 0; nkeynes@465: aica_state.nanosecs_done = 0; nkeynes@37: } else if( (tmp&1) == 0 && (val&1) == 1 ) { nkeynes@37: DEBUG( "ARM disabled" ); nkeynes@35: } nkeynes@35: MMIO_WRITE( AICA2, AICA_RESET, val ); nkeynes@35: break; nkeynes@61: case AICA_IRQCLEAR: nkeynes@61: aica_clear_event(); nkeynes@61: break; nkeynes@35: default: nkeynes@35: MMIO_WRITE( AICA2, reg, val ); nkeynes@35: break; nkeynes@35: } nkeynes@11: } nkeynes@66: nkeynes@463: int32_t mmio_region_AICA2_read( uint32_t reg ) nkeynes@463: { nkeynes@463: audio_channel_t channel; nkeynes@463: uint32_t channo; nkeynes@463: int32_t val; nkeynes@463: switch( reg ) { nkeynes@463: case AICA_CHANSTATE: nkeynes@463: channo = (MMIO_READ( AICA2, AICA_CHANSEL ) >> 8) & 0x3F; nkeynes@463: channel = audio_get_channel(channo); nkeynes@463: if( channel->loop == LOOP_LOOPED ) { nkeynes@463: val = 0x8000; nkeynes@463: channel->loop = LOOP_ON; nkeynes@463: } else { nkeynes@463: val = 0; nkeynes@463: } nkeynes@463: return val; nkeynes@463: case AICA_CHANPOSN: nkeynes@463: channo = (MMIO_READ( AICA2, AICA_CHANSEL ) >> 8) & 0x3F; nkeynes@463: channel = audio_get_channel(channo); nkeynes@463: return channel->posn; nkeynes@463: default: nkeynes@463: return MMIO_READ( AICA2, reg ); nkeynes@463: } nkeynes@463: } nkeynes@131: nkeynes@131: int32_t mmio_region_AICARTC_read( uint32_t reg ) nkeynes@131: { nkeynes@173: int32_t rv = 0; nkeynes@131: switch( reg ) { nkeynes@131: case AICA_RTCHI: nkeynes@465: rv = (aica_state.time_of_day >> 16) & 0xFFFF; nkeynes@131: break; nkeynes@131: case AICA_RTCLO: nkeynes@465: rv = aica_state.time_of_day & 0xFFFF; nkeynes@131: break; nkeynes@131: } nkeynes@241: // DEBUG( "Read AICA RTC %d => %08X", reg, rv ); nkeynes@173: return rv; nkeynes@131: } nkeynes@131: nkeynes@131: nkeynes@301: void mmio_region_AICARTC_write( uint32_t reg, uint32_t val ) nkeynes@301: { nkeynes@301: switch( reg ) { nkeynes@301: case AICA_RTCEN: nkeynes@301: MMIO_WRITE( AICARTC, reg, val&0x01 ); nkeynes@301: break; nkeynes@301: case AICA_RTCLO: nkeynes@301: if( MMIO_READ( AICARTC, AICA_RTCEN ) & 0x01 ) { nkeynes@465: aica_state.time_of_day = (aica_state.time_of_day & 0xFFFF0000) | (val & 0xFFFF); nkeynes@301: } nkeynes@301: break; nkeynes@301: case AICA_RTCHI: nkeynes@301: if( MMIO_READ( AICARTC, AICA_RTCEN ) & 0x01 ) { nkeynes@465: aica_state.time_of_day = (aica_state.time_of_day & 0xFFFF) | (val<<16); nkeynes@301: MMIO_WRITE( AICARTC, AICA_RTCEN, 0 ); nkeynes@301: } nkeynes@301: break; nkeynes@301: } nkeynes@301: } nkeynes@301: nkeynes@66: /** nkeynes@66: * Translate the channel frequency to a sample rate. The frequency is a nkeynes@66: * 14-bit floating point number, where bits 0..9 is the mantissa, nkeynes@66: * 11..14 is the signed exponent (-8 to +7). Bit 10 appears to nkeynes@66: * be unused. nkeynes@66: * nkeynes@66: * @return sample rate in samples per second. nkeynes@66: */ nkeynes@66: uint32_t aica_frequency_to_sample_rate( uint32_t freq ) nkeynes@66: { nkeynes@66: uint32_t exponent = (freq & 0x3800) >> 11; nkeynes@66: uint32_t mantissa = freq & 0x03FF; nkeynes@82: uint32_t rate; nkeynes@66: if( freq & 0x4000 ) { nkeynes@66: /* neg exponent - rate < 44100 */ nkeynes@66: exponent = 8 - exponent; nkeynes@82: rate = (44100 >> exponent) + nkeynes@66: ((44100 * mantissa) >> (10+exponent)); nkeynes@66: } else { nkeynes@66: /* pos exponent - rate > 44100 */ nkeynes@82: rate = (44100 << exponent) + nkeynes@66: ((44100 * mantissa) >> (10-exponent)); nkeynes@66: } nkeynes@82: return rate; nkeynes@66: } nkeynes@66: nkeynes@434: void aica_start_stop_channels() nkeynes@434: { nkeynes@434: int i; nkeynes@434: for( i=0; i<32; i++ ) { nkeynes@434: uint32_t val = MMIO_READ( AICA0, i<<7 ); nkeynes@434: audio_start_stop_channel(i, val&0x4000); nkeynes@434: } nkeynes@434: for( ; i<64; i++ ) { nkeynes@434: uint32_t val = MMIO_READ( AICA1, (i-32)<<7 ); nkeynes@434: audio_start_stop_channel(i, val&0x4000); nkeynes@434: } nkeynes@434: } nkeynes@434: nkeynes@82: /** nkeynes@82: * Derived directly from Dan Potter's log table nkeynes@82: */ nkeynes@82: uint8_t aica_volume_table[256] = { nkeynes@82: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, nkeynes@82: 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, nkeynes@82: 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, nkeynes@82: 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, nkeynes@82: 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, nkeynes@82: 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, nkeynes@82: 37, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, nkeynes@82: 50, 51, 52, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, nkeynes@82: 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, nkeynes@82: 82, 83, 85, 86, 87, 88, 89, 90, 92, 93, 94, 95, 97, 98, 99, 100, nkeynes@82: 102, 103, 104, 105, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 121, nkeynes@82: 123, 124, 126, 127, 128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, nkeynes@82: 146, 148, 149, 151, 152, 154, 155, 157, 159, 160, 162, 163, 165, 167, 168, 170, nkeynes@82: 171, 173, 175, 176, 178, 180, 181, 183, 185, 187, 188, 190, 192, 194, 195, 197, nkeynes@82: 199, 201, 202, 204, 206, 208, 210, 211, 213, 215, 217, 219, 221, 223, 224, 226, nkeynes@82: 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 253, 254, 255 }; nkeynes@82: nkeynes@82: nkeynes@66: void aica_write_channel( int channelNo, uint32_t reg, uint32_t val ) nkeynes@66: { nkeynes@66: val &= 0x0000FFFF; nkeynes@66: audio_channel_t channel = audio_get_channel(channelNo); nkeynes@66: switch( reg ) { nkeynes@66: case 0x00: /* Config + high address bits*/ nkeynes@66: channel->start = (channel->start & 0xFFFF) | ((val&0x1F) << 16); nkeynes@66: if( val & 0x200 ) nkeynes@463: channel->loop = LOOP_ON; nkeynes@66: else nkeynes@463: channel->loop = LOOP_OFF; nkeynes@66: switch( (val >> 7) & 0x03 ) { nkeynes@66: case 0: nkeynes@66: channel->sample_format = AUDIO_FMT_16BIT; nkeynes@66: break; nkeynes@66: case 1: nkeynes@66: channel->sample_format = AUDIO_FMT_8BIT; nkeynes@66: break; nkeynes@66: case 2: nkeynes@66: case 3: nkeynes@66: channel->sample_format = AUDIO_FMT_ADPCM; nkeynes@66: break; nkeynes@66: } nkeynes@434: if( val & 0x8000 ) { nkeynes@434: aica_start_stop_channels(); nkeynes@66: } nkeynes@66: break; nkeynes@66: case 0x04: /* Low 16 address bits */ nkeynes@66: channel->start = (channel->start & 0x001F0000) | val; nkeynes@66: break; nkeynes@66: case 0x08: /* Loop start */ nkeynes@66: channel->loop_start = val; nkeynes@66: break; nkeynes@73: case 0x0C: /* End */ nkeynes@73: channel->end = val; nkeynes@66: break; nkeynes@66: case 0x10: /* Envelope register 1 */ nkeynes@66: break; nkeynes@66: case 0x14: /* Envelope register 2 */ nkeynes@66: break; nkeynes@66: case 0x18: /* Frequency */ nkeynes@66: channel->sample_rate = aica_frequency_to_sample_rate ( val ); nkeynes@66: break; nkeynes@66: case 0x1C: /* ??? */ nkeynes@66: case 0x20: /* ??? */ nkeynes@66: case 0x24: /* Volume? /pan */ nkeynes@82: val = val & 0x1F; nkeynes@82: if( val <= 0x0F ) nkeynes@82: val = 0x0F - val; /* Convert to smooth pan over 0..31 */ nkeynes@82: channel->pan = val; nkeynes@66: break; nkeynes@66: case 0x28: /* Volume */ nkeynes@82: channel->vol = aica_volume_table[val & 0xFF]; nkeynes@66: break; nkeynes@66: default: /* ??? */ nkeynes@66: break; nkeynes@66: } nkeynes@66: nkeynes@66: }