xemu/hw/xbox/mcpx_apu.c

1624 lines
66 KiB
C

/*
* QEMU MCPX Audio Processing Unit implementation
*
* Copyright (c) 2012 espes
* Copyright (c) 2018-2019 Jannik Vogel
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/i386/pc.h"
#include "hw/pci/pci.h"
#include "cpu.h"
#include "hw/xbox/dsp/dsp.h"
#include <math.h>
#define NUM_SAMPLES_PER_FRAME 32
#define NUM_MIXBINS 32
#include "hw/xbox/mcpx_apu.h"
#define NV_PAPU_ISTS 0x00001000
# define NV_PAPU_ISTS_GINTSTS (1 << 0)
# define NV_PAPU_ISTS_FETINTSTS (1 << 4)
#define NV_PAPU_IEN 0x00001004
#define NV_PAPU_FECTL 0x00001100
# define NV_PAPU_FECTL_FEMETHMODE 0x000000E0
# define NV_PAPU_FECTL_FEMETHMODE_FREE_RUNNING 0x00000000
# define NV_PAPU_FECTL_FEMETHMODE_HALTED 0x00000080
# define NV_PAPU_FECTL_FEMETHMODE_TRAPPED 0x000000E0
# define NV_PAPU_FECTL_FETRAPREASON 0x00000F00
# define NV_PAPU_FECTL_FETRAPREASON_REQUESTED 0x00000F00
#define NV_PAPU_FECV 0x00001110
#define NV_PAPU_FEAV 0x00001118
# define NV_PAPU_FEAV_VALUE 0x0000FFFF
# define NV_PAPU_FEAV_LST 0x00030000
#define NV_PAPU_FEDECMETH 0x00001300
#define NV_PAPU_FEDECPARAM 0x00001304
#define NV_PAPU_FEMEMADDR 0x00001324
#define NV_PAPU_FEMEMDATA 0x00001334
#define NV_PAPU_FETFORCE0 0x00001500
#define NV_PAPU_FETFORCE1 0x00001504
# define NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE (1 << 15)
#define NV_PAPU_SECTL 0x00002000
# define NV_PAPU_SECTL_XCNTMODE 0x00000018
# define NV_PAPU_SECTL_XCNTMODE_OFF 0
#define NV_PAPU_XGSCNT 0x0000200C
#define NV_PAPU_VPVADDR 0x0000202C
#define NV_PAPU_VPSGEADDR 0x00002030
#define NV_PAPU_GPSADDR 0x00002040
#define NV_PAPU_GPFADDR 0x00002044
#define NV_PAPU_EPSADDR 0x00002048
#define NV_PAPU_EPFADDR 0x0000204C
#define NV_PAPU_TVL2D 0x00002054
#define NV_PAPU_CVL2D 0x00002058
#define NV_PAPU_NVL2D 0x0000205C
#define NV_PAPU_TVL3D 0x00002060
#define NV_PAPU_CVL3D 0x00002064
#define NV_PAPU_NVL3D 0x00002068
#define NV_PAPU_TVLMP 0x0000206C
#define NV_PAPU_CVLMP 0x00002070
#define NV_PAPU_NVLMP 0x00002074
#define NV_PAPU_GPSMAXSGE 0x000020D4
#define NV_PAPU_GPFMAXSGE 0x000020D8
#define NV_PAPU_EPSMAXSGE 0x000020DC
#define NV_PAPU_EPFMAXSGE 0x000020E0
/* Each FIFO has the same fields */
#define NV_PAPU_GPOFBASE0 0x00003024
# define NV_PAPU_GPOFBASE0_VALUE 0x00FFFF00
#define NV_PAPU_GPOFEND0 0x00003028
# define NV_PAPU_GPOFEND0_VALUE 0x00FFFF00
#define NV_PAPU_GPOFCUR0 0x0000302C
# define NV_PAPU_GPOFCUR0_VALUE 0x00FFFFFC
#define NV_PAPU_GPOFBASE1 0x00003034
#define NV_PAPU_GPOFEND1 0x00003038
#define NV_PAPU_GPOFCUR1 0x0000303C
#define NV_PAPU_GPOFBASE2 0x00003044
#define NV_PAPU_GPOFEND2 0x00003048
#define NV_PAPU_GPOFCUR2 0x0000304C
#define NV_PAPU_GPOFBASE3 0x00003054
#define NV_PAPU_GPOFEND3 0x00003058
#define NV_PAPU_GPOFCUR3 0x0000305C
/* Fields are same as for the 4 output FIFOs, but only 2 input FIFOs */
#define NV_PAPU_GPIFBASE0 0x00003064
#define NV_PAPU_GPIFEND0 0x00003068
#define NV_PAPU_GPIFCUR0 0x0000306C
#define NV_PAPU_GPIFBASE1 0x00003074
#define NV_PAPU_GPIFEND1 0x00003078
#define NV_PAPU_GPIFCUR1 0x0000307C
/* Fields, strides and count is same as for GP FIFOs */
#define NV_PAPU_EPOFBASE0 0x00004024
#define NV_PAPU_EPOFEND0 0x00004028
#define NV_PAPU_EPOFCUR0 0x0000402C
#define NV_PAPU_EPIFBASE0 0x00004064
#define NV_PAPU_EPIFEND0 0x00004068
#define NV_PAPU_EPIFCUR0 0x0000406C
#define NV_PAPU_GPXMEM 0x00000000
#define NV_PAPU_GPMIXBUF 0x00005000
#define NV_PAPU_GPYMEM 0x00006000
#define NV_PAPU_GPPMEM 0x0000A000
#define NV_PAPU_GPRST 0x0000FFFC
#define NV_PAPU_GPRST_GPRST (1 << 0)
#define NV_PAPU_GPRST_GPDSPRST (1 << 1)
#define NV_PAPU_GPRST_GPNMI (1 << 2)
#define NV_PAPU_GPRST_GPABORT (1 << 3)
#define NV_PAPU_EPXMEM 0x00000000
#define NV_PAPU_EPYMEM 0x00006000
#define NV_PAPU_EPPMEM 0x0000A000
#define NV_PAPU_EPRST 0x0000FFFC
static const struct {
hwaddr top, current, next;
} voice_list_regs[] = {
{NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D}, //2D
{NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D}, //3D
{NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP}, //MP
};
/* audio processor object / front-end messages */
#define NV1BA0_PIO_FREE 0x00000010
#define NV1BA0_PIO_SET_ANTECEDENT_VOICE 0x00000120
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_HANDLE 0x0000FFFF
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST 0x00030000
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT 0
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_2D_TOP 1
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_3D_TOP 2
# define NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_MP_TOP 3
#define NV1BA0_PIO_VOICE_ON 0x00000124
# define NV1BA0_PIO_VOICE_ON_HANDLE 0x0000FFFF
# define NV1BA0_PIO_VOICE_ON_ENVF 0x0F000000
# define NV1BA0_PIO_VOICE_ON_ENVA 0xF0000000
#define NV1BA0_PIO_VOICE_OFF 0x00000128
# define NV1BA0_PIO_VOICE_OFF_HANDLE 0x0000FFFF
#define NV1BA0_PIO_VOICE_RELEASE 0x0000012C
# define NV1BA0_PIO_VOICE_RELEASE_HANDLE 0x0000FFFF
#define NV1BA0_PIO_VOICE_PAUSE 0x00000140
# define NV1BA0_PIO_VOICE_PAUSE_HANDLE 0x0000FFFF
# define NV1BA0_PIO_VOICE_PAUSE_ACTION (1 << 18)
#define NV1BA0_PIO_SET_CURRENT_VOICE 0x000002F8
#define NV1BA0_PIO_SET_VOICE_CFG_VBIN 0x00000300
#define NV1BA0_PIO_SET_VOICE_CFG_FMT 0x00000304
#define NV1BA0_PIO_SET_VOICE_CFG_ENV0 0x00000308
#define NV1BA0_PIO_SET_VOICE_CFG_ENVA 0x0000030C
#define NV1BA0_PIO_SET_VOICE_CFG_ENV1 0x00000310
#define NV1BA0_PIO_SET_VOICE_CFG_ENVF 0x00000314
#define NV1BA0_PIO_SET_VOICE_CFG_MISC 0x00000318
#define NV1BA0_PIO_SET_VOICE_TAR_VOLA 0x00000360
#define NV1BA0_PIO_SET_VOICE_TAR_VOLB 0x00000364
#define NV1BA0_PIO_SET_VOICE_TAR_VOLC 0x00000368
#define NV1BA0_PIO_SET_VOICE_LFO_ENV 0x0000036C
#define NV1BA0_PIO_SET_VOICE_TAR_PITCH 0x0000037C
# define NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP 0xFFFF0000
#define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE 0x000003A0
# define NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE_OFFSET 0x00FFFFFF
#define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO 0x000003A4
# define NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO_OFFSET 0x00FFFFFF
#define NV1BA0_PIO_SET_VOICE_BUF_CBO 0x000003D8
# define NV1BA0_PIO_SET_VOICE_BUF_CBO_OFFSET 0x00FFFFFF
#define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO 0x000003DC
# define NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO_OFFSET 0x00FFFFFF
#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE 0x00000804
# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE 0xFFFFFFFF
#define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET 0x00000808
# define NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
#define NV1BA0_PIO_SET_OUTBUF_BA 0x00001000 // 8 byte pitch, 4 entries
# define NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS 0x007FFF00
#define NV1BA0_PIO_SET_OUTBUF_LEN 0x00001004 // 8 byte pitch, 4 entries
# define NV1BA0_PIO_SET_OUTBUF_LEN_VALUE 0x007FFF00
#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE 0x00001800
# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE 0xFFFFFFFF
#define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET 0x00001808
# define NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER 0xFFFFF000
#define SE2FE_IDLE_VOICE 0x00008000
/* voice structure */
#define NV_PAVS_SIZE 0x00000080
#define NV_PAVS_VOICE_CFG_VBIN 0x00000000
# define NV_PAVS_VOICE_CFG_VBIN_V0BIN (0x1F << 0)
# define NV_PAVS_VOICE_CFG_VBIN_V1BIN (0x1F << 5)
# define NV_PAVS_VOICE_CFG_VBIN_V2BIN (0x1F << 10)
# define NV_PAVS_VOICE_CFG_VBIN_V3BIN (0x1F << 16)
# define NV_PAVS_VOICE_CFG_VBIN_V4BIN (0x1F << 21)
# define NV_PAVS_VOICE_CFG_VBIN_V5BIN (0x1F << 26)
#define NV_PAVS_VOICE_CFG_FMT 0x00000004
# define NV_PAVS_VOICE_CFG_FMT_V6BIN (0x1F << 0)
# define NV_PAVS_VOICE_CFG_FMT_V7BIN (0x1F << 5)
# define NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK (0x1F << 16)
# define NV_PAVS_VOICE_CFG_FMT_DATA_TYPE (1 << 24)
# define NV_PAVS_VOICE_CFG_FMT_LOOP (1 << 25)
# define NV_PAVS_VOICE_CFG_FMT_STEREO (1 << 27)
# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE (0x3 << 28)
# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8 0
# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16 1
# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24 2
# define NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32 3
# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE (0x3 << 30)
# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B8 0
# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B16 1
# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM 2
# define NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_B32 3
#define NV_PAVS_VOICE_CFG_ENV0 0x00000008
# define NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE (0xFFF << 0)
# define NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME (0xFFF << 12)
# define NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE (0xFF << 24)
#define NV_PAVS_VOICE_CFG_ENVA 0x0000000C
# define NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE (0xFFF << 0)
# define NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME (0xFFF << 12)
# define NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL (0xFF << 24)
#define NV_PAVS_VOICE_CFG_ENV1 0x00000010
# define NV_PAVS_VOICE_CFG_ENV1_EF_FCSCALE (0xFF << 24)
#define NV_PAVS_VOICE_CFG_ENVF 0x00000014
#define NV_PAVS_VOICE_CFG_MISC 0x00000018
# define NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE (0xFFF << 0)
#define NV_PAVS_VOICE_CUR_PSL_START 0x00000020
# define NV_PAVS_VOICE_CUR_PSL_START_BA 0x00FFFFFF
#define NV_PAVS_VOICE_CUR_PSH_SAMPLE 0x00000024
# define NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO 0x00FFFFFF
#define NV_PAVS_VOICE_CUR_ECNT 0x00000034
# define NV_PAVS_VOICE_CUR_ECNT_EACOUNT 0x0000FFFF
# define NV_PAVS_VOICE_CUR_ECNT_EFCOUNT 0xFFFF0000
#define NV_PAVS_VOICE_PAR_STATE 0x00000054
# define NV_PAVS_VOICE_PAR_STATE_PAUSED (1 << 18)
# define NV_PAVS_VOICE_PAR_STATE_NEW_VOICE (1 << 20)
# define NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE (1 << 21)
# define NV_PAVS_VOICE_PAR_STATE_EFCUR (0xF << 24)
# define NV_PAVS_VOICE_PAR_STATE_EACUR (0xF << 28)
#define NV_PAVS_VOICE_PAR_OFFSET 0x00000058
# define NV_PAVS_VOICE_PAR_OFFSET_CBO 0x00FFFFFF
# define NV_PAVS_VOICE_PAR_OFFSET_EALVL 0xFF000000
#define NV_PAVS_VOICE_PAR_NEXT 0x0000005C
# define NV_PAVS_VOICE_PAR_NEXT_EBO 0x00FFFFFF
# define NV_PAVS_VOICE_PAR_NEXT_EFLVL 0xFF000000
#define NV_PAVS_VOICE_TAR_VOLA 0x00000060
# define NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0 0x0000000F
# define NV_PAVS_VOICE_TAR_VOLA_VOLUME0 0x0000FFF0
# define NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0 0x000F0000
# define NV_PAVS_VOICE_TAR_VOLA_VOLUME1 0xFFF00000
#define NV_PAVS_VOICE_TAR_VOLB 0x00000064
# define NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4 0x0000000F
# define NV_PAVS_VOICE_TAR_VOLB_VOLUME2 0x0000FFF0
# define NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4 0x000F0000
# define NV_PAVS_VOICE_TAR_VOLB_VOLUME3 0xFFF00000
#define NV_PAVS_VOICE_TAR_VOLC 0x00000068
# define NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8 0x0000000F
# define NV_PAVS_VOICE_TAR_VOLC_VOLUME4 0x0000FFF0
# define NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8 0x000F0000
# define NV_PAVS_VOICE_TAR_VOLC_VOLUME5 0xFFF00000
#define NV_PAVS_VOICE_TAR_LFO_ENV 0x0000006C
# define NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE (0xFFF << 0)
#define NV_PAVS_VOICE_TAR_PITCH_LINK 0x0000007C
# define NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE 0x0000FFFF
# define NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH 0xFFFF0000
#define GP_DSP_MIXBUF_BASE 0x001400
#define GP_OUTPUT_FIFO_COUNT 4
#define GP_INPUT_FIFO_COUNT 2
#define EP_OUTPUT_FIFO_COUNT 4
#define EP_INPUT_FIFO_COUNT 2
#define MCPX_HW_MAX_VOICES 256
#define GET_MASK(v, mask) (((v) & (mask)) >> ctz32(mask))
#define SET_MASK(v, mask, val) \
do { \
(v) &= ~(mask); \
(v) |= ((val) << ctz32(mask)) & (mask); \
} while (0)
#define CASE_4(v, step) \
case (v): \
case (v)+(step): \
case (v)+(step)*2: \
case (v)+(step)*3
// #define MCPX_DEBUG
#ifdef MCPX_DEBUG
# define MCPX_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
#else
# define MCPX_DPRINTF(format, ...) do { } while (0)
#endif
/* More debug functionality */
#define GENERATE_MIXBIN_BEEP 0
typedef struct MCPXAPUState {
PCIDevice dev;
MemoryRegion *ram;
uint8_t *ram_ptr;
MemoryRegion mmio;
/* Setup Engine */
struct {
QEMUTimer *frame_timer;
} se;
/* Voice Processor */
struct {
MemoryRegion mmio;
} vp;
/* Global Processor */
struct {
MemoryRegion mmio;
DSPState *dsp;
uint32_t regs[0x10000];
} gp;
/* Encode Processor */
struct {
MemoryRegion mmio;
DSPState *dsp;
uint32_t regs[0x10000];
} ep;
uint32_t inbuf_sge_handle; //FIXME: Where is this stored?
uint32_t outbuf_sge_handle; //FIXME: Where is this stored?
uint32_t regs[0x20000];
} MCPXAPUState;
#define MCPX_APU_DEVICE(obj) \
OBJECT_CHECK(MCPXAPUState, (obj), "mcpx-apu")
static uint32_t voice_get_mask(MCPXAPUState *d,
unsigned int voice_handle,
hwaddr offset,
uint32_t mask)
{
assert(voice_handle < 0xFFFF);
hwaddr voice = d->regs[NV_PAPU_VPVADDR]
+ voice_handle * NV_PAVS_SIZE;
return (ldl_le_phys(&address_space_memory, voice + offset) & mask)
>> ctz32(mask);
}
static void voice_set_mask(MCPXAPUState *d,
unsigned int voice_handle,
hwaddr offset,
uint32_t mask,
uint32_t val)
{
assert(voice_handle < 0xFFFF);
hwaddr voice = d->regs[NV_PAPU_VPVADDR]
+ voice_handle * NV_PAVS_SIZE;
uint32_t v = ldl_le_phys(&address_space_memory, voice + offset) & ~mask;
stl_le_phys(&address_space_memory, voice + offset,
v | ((val << ctz32(mask)) & mask));
}
static void update_irq(MCPXAPUState *d)
{
if ((d->regs[NV_PAPU_IEN] & NV_PAPU_ISTS_GINTSTS)
&& ((d->regs[NV_PAPU_ISTS] & ~NV_PAPU_ISTS_GINTSTS)
& d->regs[NV_PAPU_IEN])) {
d->regs[NV_PAPU_ISTS] |= NV_PAPU_ISTS_GINTSTS;
MCPX_DPRINTF("mcpx irq raise\n");
pci_irq_assert(&d->dev);
} else {
d->regs[NV_PAPU_ISTS] &= ~NV_PAPU_ISTS_GINTSTS;
MCPX_DPRINTF("mcpx irq lower\n");
pci_irq_deassert(&d->dev);
}
}
static uint64_t mcpx_apu_read(void *opaque,
hwaddr addr, unsigned int size)
{
MCPXAPUState *d = opaque;
uint64_t r = 0;
switch (addr) {
case NV_PAPU_XGSCNT:
r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 100; //???
break;
default:
if (addr < 0x20000) {
r = d->regs[addr];
}
break;
}
MCPX_DPRINTF("mcpx apu: read [0x%llx] -> 0x%llx\n", addr, r);
return r;
}
static void mcpx_apu_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int size)
{
MCPXAPUState *d = opaque;
MCPX_DPRINTF("mcpx apu: [0x%llx] = 0x%llx\n", addr, val);
switch (addr) {
case NV_PAPU_ISTS:
/* the bits of the interrupts to clear are wrtten */
d->regs[NV_PAPU_ISTS] &= ~val;
update_irq(d);
break;
case NV_PAPU_SECTL:
if (((val & NV_PAPU_SECTL_XCNTMODE) >> 3)
== NV_PAPU_SECTL_XCNTMODE_OFF) {
timer_del(d->se.frame_timer);
} else {
timer_mod(d->se.frame_timer,
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
}
d->regs[addr] = val;
break;
case NV_PAPU_FEMEMDATA:
/* 'magic write'
* This value is expected to be written to FEMEMADDR on completion of
* something to do with notifies. Just do it now :/ */
stl_le_phys(&address_space_memory, d->regs[NV_PAPU_FEMEMADDR], val);
d->regs[addr] = val;
break;
default:
if (addr < 0x20000) {
d->regs[addr] = val;
}
break;
}
}
static const MemoryRegionOps mcpx_apu_mmio_ops = {
.read = mcpx_apu_read,
.write = mcpx_apu_write,
};
static void fe_method(MCPXAPUState *d,
uint32_t method, uint32_t argument)
{
#ifdef MCPX_DEBUG
unsigned int slot;
#endif
MCPX_DPRINTF("mcpx fe_method 0x%x 0x%x\n", method, argument);
//assert((d->regs[NV_PAPU_FECTL] & NV_PAPU_FECTL_FEMETHMODE) == 0);
d->regs[NV_PAPU_FEDECMETH] = method;
d->regs[NV_PAPU_FEDECPARAM] = argument;
unsigned int selected_handle, list;
switch (method) {
case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
d->regs[NV_PAPU_FEAV] = argument;
break;
case NV1BA0_PIO_VOICE_ON:
selected_handle = argument & NV1BA0_PIO_VOICE_ON_HANDLE;
list = GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_LST);
if (list != NV1BA0_PIO_SET_ANTECEDENT_VOICE_LIST_INHERIT) {
/* voice is added to the top of the selected list */
unsigned int top_reg = voice_list_regs[list - 1].top;
voice_set_mask(d, selected_handle,
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
d->regs[top_reg]);
d->regs[top_reg] = selected_handle;
} else {
unsigned int antecedent_voice =
GET_MASK(d->regs[NV_PAPU_FEAV], NV_PAPU_FEAV_VALUE);
/* voice is added after the antecedent voice */
assert(antecedent_voice != 0xFFFF);
uint32_t next_handle = voice_get_mask(d, antecedent_voice,
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
voice_set_mask(d, selected_handle,
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
next_handle);
voice_set_mask(d, antecedent_voice,
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE,
selected_handle);
}
unsigned int ea_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVA);
voice_set_mask(d, selected_handle,
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_EACUR,
ea_start);
if (ea_start == 1) { // Delay
uint16_t delay_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, delay_time * 16);
} else if (ea_start == 2) { // Attack
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, 0x0000);
} else if (ea_start == 3) { // Hold
uint16_t hold_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVA, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, hold_time * 16);
}
//FIXME: Will count be overwritten in other cases too?
unsigned int ef_start = GET_MASK(argument, NV1BA0_PIO_VOICE_ON_ENVF);
voice_set_mask(d, selected_handle,
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_EFCUR,
ef_start);
if (ef_start == 1) { // Delay
uint16_t delay_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENV0_EA_DELAYTIME);
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, delay_time * 16);
} else if (ef_start == 2) { // Attack
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, 0x0000);
} else if (ef_start == 3) { // Hold
uint16_t hold_time = voice_get_mask(d, selected_handle, NV_PAVS_VOICE_CFG_ENVF, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
voice_set_mask(d, selected_handle, NV_PAVS_VOICE_CUR_ECNT, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, hold_time * 16);
}
//FIXME: Will count be overwritten in other cases too?
voice_set_mask(d, selected_handle,
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE,
1);
break;
case NV1BA0_PIO_VOICE_OFF:
voice_set_mask(d, argument & NV1BA0_PIO_VOICE_OFF_HANDLE,
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE,
0);
break;
case NV1BA0_PIO_VOICE_PAUSE:
voice_set_mask(d, argument & NV1BA0_PIO_VOICE_PAUSE_HANDLE,
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_PAUSED,
(argument & NV1BA0_PIO_VOICE_PAUSE_ACTION) != 0);
break;
case NV1BA0_PIO_SET_CURRENT_VOICE:
d->regs[NV_PAPU_FECV] = argument;
break;
case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_VBIN,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_FMT:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_FMT,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_ENV0,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_ENVA,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_ENV1,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_ENVF,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_MISC:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CFG_MISC,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_TAR_VOLA,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_TAR_VOLB,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_TAR_VOLC,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_LFO_ENV:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_TAR_LFO_ENV,
0xFFFFFFFF,
argument);
break;
case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH,
(argument & NV1BA0_PIO_SET_VOICE_TAR_PITCH_STEP) >> 16);
break;
case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CUR_PSL_START,
NV_PAVS_VOICE_CUR_PSL_START_BA,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_CUR_PSH_SAMPLE,
NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO,
argument);
break;
case NV1BA0_PIO_SET_VOICE_BUF_CBO:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_PAR_OFFSET,
NV_PAVS_VOICE_PAR_OFFSET_CBO,
argument);
break;
case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
voice_set_mask(d, d->regs[NV_PAPU_FECV],
NV_PAVS_VOICE_PAR_NEXT,
NV_PAVS_VOICE_PAR_NEXT_EBO,
argument);
break;
case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
d->inbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_HANDLE;
break;
case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET: {
//FIXME: Is there an upper limit for the SGE table size?
//FIXME: NV_PAPU_VPSGEADDR is probably bad, as outbuf SGE use the same handle range (or that is also wrong)
hwaddr sge_address = d->regs[NV_PAPU_VPSGEADDR] + d->inbuf_sge_handle * 8;
stl_le_phys(&address_space_memory, sge_address, argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
MCPX_DPRINTF("Wrote inbuf SGE[0x%X] = 0x%08X\n", d->inbuf_sge_handle, argument & NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET_PARAMETER);
break;
}
CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
#ifdef MCPX_DEBUG
slot = (method - NV1BA0_PIO_SET_OUTBUF_BA) / 8;
//FIXME: Use NV1BA0_PIO_SET_OUTBUF_BA_ADDRESS = 0x007FFF00 ?
MCPX_DPRINTF("outbuf_ba[%d]: 0x%08X\n", slot, argument);
#endif
//assert(false); //FIXME: Enable assert! no idea what this reg does
break;
CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
#ifdef MCPX_DEBUG
slot = (method - NV1BA0_PIO_SET_OUTBUF_LEN) / 8;
//FIXME: Use NV1BA0_PIO_SET_OUTBUF_LEN_VALUE = 0x007FFF00 ?
MCPX_DPRINTF("outbuf_len[%d]: 0x%08X\n", slot, argument);
#endif
//assert(false); //FIXME: Enable assert! no idea what this reg does
break;
case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
d->outbuf_sge_handle = argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_HANDLE;
break;
case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET: {
//FIXME: Is there an upper limit for the SGE table size?
//FIXME: NV_PAPU_VPSGEADDR is probably bad, as inbuf SGE use the same handle range (or that is also wrong)
// NV_PAPU_EPFADDR EP outbufs
// NV_PAPU_GPFADDR GP outbufs
// But how does it know which outbuf is being written?!
hwaddr sge_address = d->regs[NV_PAPU_VPSGEADDR] + d->outbuf_sge_handle * 8;
stl_le_phys(&address_space_memory, sge_address, argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
MCPX_DPRINTF("Wrote outbuf SGE[0x%X] = 0x%08X\n", d->outbuf_sge_handle, argument & NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET_PARAMETER);
break;
}
case SE2FE_IDLE_VOICE:
if (d->regs[NV_PAPU_FETFORCE1] & NV_PAPU_FETFORCE1_SE2FE_IDLE_VOICE) {
d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FEMETHMODE;
d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FEMETHMODE_TRAPPED;
d->regs[NV_PAPU_FECTL] &= ~NV_PAPU_FECTL_FETRAPREASON;
d->regs[NV_PAPU_FECTL] |= NV_PAPU_FECTL_FETRAPREASON_REQUESTED;
d->regs[NV_PAPU_ISTS] |= NV_PAPU_ISTS_FETINTSTS;
update_irq(d);
} else {
assert(false);
}
break;
default:
assert(false);
break;
}
}
static uint64_t vp_read(void *opaque,
hwaddr addr, unsigned int size)
{
MCPX_DPRINTF("mcpx apu VP: read [0x%llx]\n", addr);
switch (addr) {
case NV1BA0_PIO_FREE:
/* we don't simulate the queue for now,
* pretend to always be empty */
return 0x80;
default:
break;
}
return 0;
}
static void vp_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int size)
{
MCPXAPUState *d = opaque;
MCPX_DPRINTF("mcpx apu VP: [0x%llx] = 0x%llx\n", addr, val);
switch (addr) {
case NV1BA0_PIO_SET_ANTECEDENT_VOICE:
case NV1BA0_PIO_VOICE_ON:
case NV1BA0_PIO_VOICE_OFF:
case NV1BA0_PIO_VOICE_PAUSE:
case NV1BA0_PIO_SET_CURRENT_VOICE:
case NV1BA0_PIO_SET_VOICE_CFG_VBIN:
case NV1BA0_PIO_SET_VOICE_CFG_FMT:
case NV1BA0_PIO_SET_VOICE_CFG_ENV0:
case NV1BA0_PIO_SET_VOICE_CFG_ENVA:
case NV1BA0_PIO_SET_VOICE_CFG_ENV1:
case NV1BA0_PIO_SET_VOICE_CFG_ENVF:
case NV1BA0_PIO_SET_VOICE_CFG_MISC:
case NV1BA0_PIO_SET_VOICE_TAR_VOLA:
case NV1BA0_PIO_SET_VOICE_TAR_VOLB:
case NV1BA0_PIO_SET_VOICE_TAR_VOLC:
case NV1BA0_PIO_SET_VOICE_LFO_ENV:
case NV1BA0_PIO_SET_VOICE_TAR_PITCH:
case NV1BA0_PIO_SET_VOICE_CFG_BUF_BASE:
case NV1BA0_PIO_SET_VOICE_CFG_BUF_LBO:
case NV1BA0_PIO_SET_VOICE_BUF_CBO:
case NV1BA0_PIO_SET_VOICE_CFG_BUF_EBO:
case NV1BA0_PIO_SET_CURRENT_INBUF_SGE:
case NV1BA0_PIO_SET_CURRENT_INBUF_SGE_OFFSET:
CASE_4(NV1BA0_PIO_SET_OUTBUF_BA, 8): // 8 byte pitch, 4 entries
CASE_4(NV1BA0_PIO_SET_OUTBUF_LEN, 8): // 8 byte pitch, 4 entries
case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE:
case NV1BA0_PIO_SET_CURRENT_OUTBUF_SGE_OFFSET:
/* TODO: these should instead be queueing up fe commands */
fe_method(d, addr, val);
break;
default:
break;
}
}
static const MemoryRegionOps vp_ops = {
.read = vp_read,
.write = vp_write,
};
static void scatter_gather_rw(MCPXAPUState *d,
hwaddr sge_base, unsigned int max_sge,
uint8_t *ptr, uint32_t addr, size_t len,
bool dir)
{
unsigned int page_entry = addr / TARGET_PAGE_SIZE;
unsigned int offset_in_page = addr % TARGET_PAGE_SIZE;
unsigned int bytes_to_copy = TARGET_PAGE_SIZE - offset_in_page;
while (len > 0) {
assert(page_entry <= max_sge);
uint32_t prd_address = ldl_le_phys(&address_space_memory,
sge_base + page_entry * 8 + 0);
/* uint32_t prd_control = ldl_le_phys(&address_space_memory,
sge_base + page_entry * 8 + 4); */
hwaddr paddr = prd_address + offset_in_page;
if (bytes_to_copy > len) {
bytes_to_copy = len;
}
assert(paddr + bytes_to_copy < memory_region_size(d->ram));
if (dir) {
memcpy(&d->ram_ptr[paddr], ptr, bytes_to_copy);
memory_region_set_dirty(d->ram, paddr, bytes_to_copy);
} else {
memcpy(ptr, &d->ram_ptr[paddr], bytes_to_copy);
}
ptr += bytes_to_copy;
len -= bytes_to_copy;
/* After the first iteration, we are page aligned */
page_entry += 1;
bytes_to_copy = TARGET_PAGE_SIZE;
offset_in_page = 0;
}
}
static void gp_scratch_rw(void *opaque,
uint8_t *ptr,
uint32_t addr,
size_t len,
bool dir)
{
MCPXAPUState *d = opaque;
scatter_gather_rw(d, d->regs[NV_PAPU_GPSADDR], d->regs[NV_PAPU_GPSMAXSGE],
ptr, addr, len, dir);
}
static void ep_scratch_rw(void *opaque,
uint8_t *ptr,
uint32_t addr,
size_t len,
bool dir)
{
MCPXAPUState *d = opaque;
scatter_gather_rw(d, d->regs[NV_PAPU_EPSADDR], d->regs[NV_PAPU_EPSMAXSGE],
ptr, addr, len, dir);
}
static uint32_t circular_scatter_gather_rw(MCPXAPUState *d,
hwaddr sge_base,
unsigned int max_sge,
uint8_t *ptr,
uint32_t base, uint32_t end,
uint32_t cur,
size_t len,
bool dir)
{
while (len > 0) {
unsigned int bytes_to_copy = end - cur;
if (bytes_to_copy > len) {
bytes_to_copy = len;
}
MCPX_DPRINTF("circular scatter gather %s in range 0x%x - 0x%x at 0x%x of length 0x%x / 0x%lx bytes\n",
dir ? "write" : "read", base, end, cur, bytes_to_copy, len);
assert((cur >= base) && ((cur + bytes_to_copy) <= end));
scatter_gather_rw(d, sge_base, max_sge, ptr, cur, bytes_to_copy, dir);
ptr += bytes_to_copy;
len -= bytes_to_copy;
/* After the first iteration we might have to wrap */
cur += bytes_to_copy;
if (cur >= end) {
assert(cur == end);
cur = base;
}
}
return cur;
}
static void gp_fifo_rw(void *opaque, uint8_t *ptr,
unsigned int index, size_t len,
bool dir)
{
MCPXAPUState *d = opaque;
uint32_t base;
uint32_t end;
hwaddr cur_reg;
if (dir) {
assert(index < GP_OUTPUT_FIFO_COUNT);
base = GET_MASK(d->regs[NV_PAPU_GPOFBASE0 + 0x10 * index],
NV_PAPU_GPOFBASE0_VALUE);
end = GET_MASK(d->regs[NV_PAPU_GPOFEND0 + 0x10 * index],
NV_PAPU_GPOFEND0_VALUE);
cur_reg = NV_PAPU_GPOFCUR0 + 0x10 * index;
} else {
assert(index < GP_INPUT_FIFO_COUNT);
base = GET_MASK(d->regs[NV_PAPU_GPIFBASE0 + 0x10 * index],
NV_PAPU_GPOFBASE0_VALUE);
end = GET_MASK(d->regs[NV_PAPU_GPIFEND0 + 0x10 * index],
NV_PAPU_GPOFEND0_VALUE);
cur_reg = NV_PAPU_GPIFCUR0 + 0x10 * index;
}
uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
/* DSP hangs if current >= end; but forces current >= base */
assert(cur < end);
if (cur < base) {
cur = base;
}
cur = circular_scatter_gather_rw(d,
d->regs[NV_PAPU_GPFADDR], d->regs[NV_PAPU_GPFMAXSGE],
ptr, base, end, cur, len, dir);
SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
}
static void ep_fifo_rw(void *opaque, uint8_t *ptr,
unsigned int index, size_t len,
bool dir)
{
MCPXAPUState *d = opaque;
uint32_t base;
uint32_t end;
hwaddr cur_reg;
if (dir) {
assert(index < EP_OUTPUT_FIFO_COUNT);
base = GET_MASK(d->regs[NV_PAPU_EPOFBASE0 + 0x10 * index],
NV_PAPU_GPOFBASE0_VALUE);
end = GET_MASK(d->regs[NV_PAPU_EPOFEND0 + 0x10 * index],
NV_PAPU_GPOFEND0_VALUE);
cur_reg = NV_PAPU_EPOFCUR0 + 0x10 * index;
} else {
assert(index < EP_INPUT_FIFO_COUNT);
base = GET_MASK(d->regs[NV_PAPU_EPIFBASE0 + 0x10 * index],
NV_PAPU_GPOFBASE0_VALUE);
end = GET_MASK(d->regs[NV_PAPU_EPIFEND0 + 0x10 * index],
NV_PAPU_GPOFEND0_VALUE);
cur_reg = NV_PAPU_EPIFCUR0 + 0x10 * index;
}
uint32_t cur = GET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE);
/* DSP hangs if current >= end; but forces current >= base */
assert(cur < end);
if (cur < base) {
cur = base;
}
cur = circular_scatter_gather_rw(d,
d->regs[NV_PAPU_EPFADDR], d->regs[NV_PAPU_EPFMAXSGE],
ptr, base, end, cur, len, dir);
SET_MASK(d->regs[cur_reg], NV_PAPU_GPOFCUR0_VALUE, cur);
}
static void proc_rst_write(DSPState *dsp, uint32_t oldval, uint32_t val)
{
if (!(val & NV_PAPU_GPRST_GPRST) || !(val & NV_PAPU_GPRST_GPDSPRST)) {
dsp_reset(dsp);
} else if (
(!(oldval & NV_PAPU_GPRST_GPRST) || !(oldval & NV_PAPU_GPRST_GPDSPRST))
&& ((val & NV_PAPU_GPRST_GPRST) && (val & NV_PAPU_GPRST_GPDSPRST))) {
dsp_bootstrap(dsp);
}
}
/* Global Processor - programmable DSP */
static uint64_t gp_read(void *opaque,
hwaddr addr,
unsigned int size)
{
MCPXAPUState *d = opaque;
assert(size == 4);
assert(addr % 4 == 0);
uint64_t r = 0;
switch (addr) {
case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
r = dsp_read_memory(d->gp.dsp, 'X', xaddr);
break;
}
case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
r = dsp_read_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr);
break;
}
case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
r = dsp_read_memory(d->gp.dsp, 'Y', yaddr);
break;
}
case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
r = dsp_read_memory(d->gp.dsp, 'P', paddr);
break;
}
default:
r = d->gp.regs[addr];
break;
}
MCPX_DPRINTF("mcpx apu GP: read [0x%llx] -> 0x%llx\n", addr, r);
return r;
}
static void gp_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int size)
{
MCPXAPUState *d = opaque;
assert(size == 4);
assert(addr % 4 == 0);
MCPX_DPRINTF("mcpx apu GP: [0x%llx] = 0x%llx\n", addr, val);
switch (addr) {
case NV_PAPU_GPXMEM ... NV_PAPU_GPXMEM + 0x1000 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_GPXMEM) / 4;
dsp_write_memory(d->gp.dsp, 'X', xaddr, val);
break;
}
case NV_PAPU_GPMIXBUF ... NV_PAPU_GPMIXBUF + 0x400 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_GPMIXBUF) / 4;
dsp_write_memory(d->gp.dsp, 'X', GP_DSP_MIXBUF_BASE + xaddr, val);
break;
}
case NV_PAPU_GPYMEM ... NV_PAPU_GPYMEM + 0x800 * 4 - 1: {
uint32_t yaddr = (addr - NV_PAPU_GPYMEM) / 4;
dsp_write_memory(d->gp.dsp, 'Y', yaddr, val);
break;
}
case NV_PAPU_GPPMEM ... NV_PAPU_GPPMEM + 0x1000 * 4 - 1: {
uint32_t paddr = (addr - NV_PAPU_GPPMEM) / 4;
dsp_write_memory(d->gp.dsp, 'P', paddr, val);
break;
}
case NV_PAPU_GPRST:
proc_rst_write(d->gp.dsp, d->gp.regs[NV_PAPU_GPRST], val);
d->gp.regs[NV_PAPU_GPRST] = val;
break;
default:
d->gp.regs[addr] = val;
break;
}
}
static const MemoryRegionOps gp_ops = {
.read = gp_read,
.write = gp_write,
};
/* Encode Processor - encoding DSP */
static uint64_t ep_read(void *opaque,
hwaddr addr,
unsigned int size)
{
MCPXAPUState *d = opaque;
assert(size == 4);
assert(addr % 4 == 0);
uint64_t r = 0;
switch (addr) {
case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
r = dsp_read_memory(d->ep.dsp, 'X', xaddr);
break;
}
case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
r = dsp_read_memory(d->ep.dsp, 'Y', yaddr);
break;
}
case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
r = dsp_read_memory(d->ep.dsp, 'P', paddr);
break;
}
default:
r = d->ep.regs[addr];
break;
}
MCPX_DPRINTF("mcpx apu EP: read [0x%llx] -> 0x%llx\n", addr, r);
return r;
}
static void ep_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int size)
{
MCPXAPUState *d = opaque;
assert(size == 4);
assert(addr % 4 == 0);
MCPX_DPRINTF("mcpx apu EP: [0x%llx] = 0x%llx\n", addr, val);
switch (addr) {
case NV_PAPU_EPXMEM ... NV_PAPU_EPXMEM + 0xC00 * 4 - 1: {
uint32_t xaddr = (addr - NV_PAPU_EPXMEM) / 4;
dsp_write_memory(d->ep.dsp, 'X', xaddr, val);
break;
}
case NV_PAPU_EPYMEM ... NV_PAPU_EPYMEM + 0x100 * 4 - 1: {
uint32_t yaddr = (addr - NV_PAPU_EPYMEM) / 4;
dsp_write_memory(d->ep.dsp, 'Y', yaddr, val);
break;
}
case NV_PAPU_EPPMEM ... NV_PAPU_EPPMEM + 0x1000 * 4 - 1: {
uint32_t paddr = (addr - NV_PAPU_EPPMEM) / 4;
dsp_write_memory(d->ep.dsp, 'P', paddr, val);
break;
}
case NV_PAPU_EPRST:
proc_rst_write(d->ep.dsp, d->ep.regs[NV_PAPU_EPRST], val);
d->ep.regs[NV_PAPU_EPRST] = val;
break;
default:
d->ep.regs[addr] = val;
break;
}
}
static const MemoryRegionOps ep_ops = {
.read = ep_read,
.write = ep_write,
};
#if 0
#include "adpcm_block.h"
static hwaddr get_data_ptr(hwaddr sge_base, unsigned int max_sge, uint32_t addr) {
unsigned int entry = addr / TARGET_PAGE_SIZE;
assert(entry <= max_sge);
uint32_t prd_address = ldl_le_phys(&address_space_memory, sge_base + entry*4*2);
// uint32_t prd_control = ldl_le_phys(&address_space_memory, sge_base + entry*4*2 + 4);
MCPX_DPRINTF("Addr: 0x%08X, control: 0x%08X\n", prd_address, prd_control);
return prd_address + addr % TARGET_PAGE_SIZE;
}
static float step_envelope(MCPXAPUState *d, unsigned int v, uint32_t reg_0, uint32_t reg_a, uint32_t rr_reg, uint32_t rr_mask, uint32_t lvl_reg, uint32_t lvl_mask, uint32_t count_mask, uint32_t cur_mask) {
uint8_t cur = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask);
switch(cur) {
case 0: // Off
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0);
voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
return 1.0f;
case 1: { // Delay
uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
voice_set_mask(d, v, lvl_reg, lvl_mask, 0x00); // FIXME: Confirm this?
if (count == 0) {
cur++;
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
count = 0;
} else {
count--;
}
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
break;
}
case 2: { // Attack
uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
uint16_t attack_rate = voice_get_mask(d, v, reg_0, NV_PAVS_VOICE_CFG_ENV0_EA_ATTACKRATE);
float value;
if (attack_rate == 0) {
//FIXME: [division by zero]
// Got crackling sound in hardware for amplitude env.
value = 255.0f;
} else {
if (count <= attack_rate) {
value = (count * 0xFF) / attack_rate;
} else {
//FIXME: Overflow in hardware
// The actual value seems to overflow, but not sure how
value = 255.0f;
}
}
voice_set_mask(d, v, lvl_reg, lvl_mask, value);
//FIXME: Comparison could also be the other way around?! Test please.
if (count == (attack_rate * 16)) {
cur++;
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
uint16_t hold_time = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_HOLDTIME);
count = hold_time * 16; //FIXME: Skip next phase if count is 0? [other instances too]
} else {
count++;
}
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
return value / 255.0f;
}
case 3: { // Hold
uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
voice_set_mask(d, v, lvl_reg, lvl_mask, 0xFF);
if (count == 0) {
cur++;
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
uint16_t decay_rate = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
count = decay_rate * 16;
} else {
count--;
}
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
return 1.0f;
}
case 4: { // Decay
uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
uint16_t decay_rate = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_DECAYRATE);
uint8_t sustain_level = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
float value;
if (decay_rate == 0) {
//FIXME: [division by zero]
// Not tested on hardware
value = 0.0f;
} else {
//FIXME: This formula and threshold is not accurate, but I can't get it any better for now
value = 255.0f * powf(0.99988799f, (decay_rate * 16 - count) * 4096 / decay_rate);
}
if (value <= (sustain_level + 0.2f) || (value > 255.0f)) {
//FIXME: Should we still update lvl?
cur++;
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
} else {
count--;
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
voice_set_mask(d, v, lvl_reg, lvl_mask, value);
}
return value / 255.0f;
}
case 5: { // Sustain
uint8_t sustain_level = voice_get_mask(d, v, reg_a, NV_PAVS_VOICE_CFG_ENVA_EA_SUSTAINLEVEL);
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, 0x00); // FIXME: is this only set to 0 once or forced to zero?
voice_set_mask(d, v, lvl_reg, lvl_mask, sustain_level);
return sustain_level / 255.0f;
}
case 6: { // Release
uint16_t release_rate = voice_get_mask(d, v, rr_reg, rr_mask);
uint16_t count = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask);
count--;
voice_set_mask(d, v, NV_PAVS_VOICE_CUR_ECNT, count_mask, count);
uint8_t lvl = voice_get_mask(d, v, lvl_reg, lvl_mask);
float value = count * lvl / (release_rate * 16);
if (count == 0) {
//FIXME: What to do now?!
#if 0 // Hack so we don't assert
cur++; // FIXME: Does this happen?!
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, cur);
#else
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, cur_mask, 0x0); // Is this correct? FIXME: Turn off voice?
#endif
}
return value / 255.0f;
}
case 7: // Force release
assert(false); //FIXME: This mode is not understood yet
return 1.0f;
default:
fprintf(stderr, "Unknown envelope state 0x%x\n", cur);
assert(false);
}
return 0;
}
#endif
static void process_voice(MCPXAPUState *d,
int32_t mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME],
uint32_t voice)
{
#if 0
uint32_t v = voice;
int32_t samples[2][0x20] = {0};
float ea_value = step_envelope(d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENVA, NV_PAVS_VOICE_TAR_LFO_ENV, NV_PAVS_VOICE_TAR_LFO_ENV_EA_RELEASERATE, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_EALVL, NV_PAVS_VOICE_CUR_ECNT_EACOUNT, NV_PAVS_VOICE_PAR_STATE_EACUR);
float ef_value = step_envelope(d, v, NV_PAVS_VOICE_CFG_ENV1, NV_PAVS_VOICE_CFG_ENVF, NV_PAVS_VOICE_CFG_MISC, NV_PAVS_VOICE_CFG_MISC_EF_RELEASERATE, NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EFLVL, NV_PAVS_VOICE_CUR_ECNT_EFCOUNT, NV_PAVS_VOICE_PAR_STATE_EFCUR);
assert(ea_value >= 0.0f);
assert(ea_value <= 1.0f);
assert(ef_value >= 0.0f);
assert(ef_value <= 1.0f);
int16_t p = voice_get_mask(d, v, NV_PAVS_VOICE_TAR_PITCH_LINK, NV_PAVS_VOICE_TAR_PITCH_LINK_PITCH);
int8_t pm = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_ENV0, NV_PAVS_VOICE_CFG_ENV0_EF_PITCHSCALE);
float rate = powf(2.0f, (p + pm * 32 * ef_value) / 4096.0f);
MCPX_DPRINTF("Got %f\n", rate * 48000.0f);
float overdrive = 1.0f; //FIXME: This is just a hack because our APU runs too rarely
//NV_PAVS_VOICE_PAR_OFFSET_CBO
bool stereo = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_STEREO);
unsigned int channels = stereo ? 2 : 1;
unsigned int sample_size = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE);
// B8, B16, ADPCM, B32
unsigned int container_sizes[4] = { 1, 2, 0, 4 };
unsigned int container_size_index = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE);
//FIXME: Move down, but currently debug code depends on this
unsigned int container_size = container_sizes[container_size_index];
bool stream = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_DATA_TYPE);
assert(!stream);
bool paused = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_PAUSED);
bool loop = voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_LOOP);
uint32_t ebo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_NEXT, NV_PAVS_VOICE_PAR_NEXT_EBO);
uint32_t cbo = voice_get_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_CBO);
uint32_t lbo = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSH_SAMPLE, NV_PAVS_VOICE_CUR_PSH_SAMPLE_LBO);
uint32_t ba = voice_get_mask(d, v, NV_PAVS_VOICE_CUR_PSL_START, NV_PAVS_VOICE_CUR_PSL_START_BA);
unsigned int samples_per_block = 1 + voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_SAMPLES_PER_BLOCK);
// This is probably cleared when the first sample is played
//FIXME: How will this behave if CBO > EBO on first play?
//FIXME: How will this behave if paused?
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_NEW_VOICE, 0);
for(unsigned int i = 0; i < 0x20; i++) {
uint32_t sample_pos = (uint32_t)(i * rate * overdrive);
if ((cbo + sample_pos) > ebo) {
if (!loop) {
// Set to safe state
cbo = ebo; //FIXME: Will the hw do this?
//FIXME: Not sure if this happens.. needs a hwtest.
// Some RE also suggests that the voices will automaticly be removed from the list (!!!)
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_STATE, NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE, 0);
break;
}
// Now go to loop start
//FIXME: Make sure this logic still works for very high sample_pos greater than ebo
cbo += sample_pos;
cbo %= ebo + 1;
cbo += lbo;
cbo -= sample_pos;
}
sample_pos += cbo;
assert(sample_pos <= ebo);
if (container_size_index == NV_PAVS_VOICE_CFG_FMT_CONTAINER_SIZE_ADPCM) {
//FIXME: Not sure how this behaves otherwise
assert(sample_size == NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24);
unsigned int block_index = sample_pos / 65;
unsigned int block_position = sample_pos % 65;
MCPX_DPRINTF("ADPCM: %d + %d\n", block_index, block_position);
//FIXME: Remove this from the loop which collects required samples
// We can always just grab one or two blocks to get all required samples
if (stereo) {
assert(samples_per_block == 2);
// There are 65 samples per 72 byte block
uint32_t block[72/4];
uint32_t linear_addr = ba + block_index * 72;
// FIXME: Only load block data which will be used
for(unsigned int word_index = 0; word_index < 18; word_index++) {
hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
block[word_index] = ldl_le_phys(&address_space_memory, addr);
linear_addr += 4;
}
//FIXME: Used wrong sample format in my own decoder.. stupid me lol
int16_t tmp[2];
adpcm_decode_stereo_block(&tmp[0], &tmp[1], (uint8_t*)block, block_position, block_position);
samples[0][i] = tmp[0];
samples[1][i] = tmp[1];
} else {
assert(samples_per_block == 1);
// There are 65 samples per 36 byte block
uint32_t block[36/4];
uint32_t linear_addr = ba + block_index * 36;
for(unsigned int word_index = 0; word_index < 9; word_index++) {
hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
block[word_index] = ldl_le_phys(&address_space_memory, addr);
linear_addr += 4;
}
//FIXME: Used wrong sample format in my own decoder.. stupid me lol
int16_t tmp;
adpcm_decode_mono_block(&tmp, (uint8_t*)block, block_position, block_position);
samples[0][i] = tmp;
}
} else {
unsigned int block_size = container_size;
block_size *= samples_per_block;
uint32_t linear_addr = ba + sample_pos * block_size;
hwaddr addr = get_data_ptr(d->regs[NV_PAPU_VPSGEADDR], 0xFFFFFFFF, linear_addr);
//FIXME: Handle reading accross pages?!
MCPX_DPRINTF("Sampling from 0x%08X\n", addr);
// Get samples for this voice
for(unsigned int channel = 0; channel < channels; channel++) {
switch(sample_size) {
case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_U8:
samples[channel][i] = ldub_phys(&address_space_memory, addr);
break;
case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S16:
samples[channel][i] = (int16_t)lduw_le_phys(&address_space_memory, addr);
break;
case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S24:
samples[channel][i] = (int32_t)(ldl_le_phys(&address_space_memory, addr) << 8) >> 8;
break;
case NV_PAVS_VOICE_CFG_FMT_SAMPLE_SIZE_S32:
samples[channel][i] = (int32_t)ldl_le_phys(&address_space_memory, addr);
break;
}
// Advance cursor to second channel for stereo
addr += container_size;
}
}
}
//FIXME: Decode voice volume and bins
int bin[8] = {
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V0BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V1BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V2BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V3BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V4BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_VBIN, NV_PAVS_VOICE_CFG_VBIN_V5BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_V6BIN),
voice_get_mask(d, v, NV_PAVS_VOICE_CFG_FMT, NV_PAVS_VOICE_CFG_FMT_V7BIN)
};
uint16_t vol[8] = {
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME0),
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME1),
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME2),
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME3),
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME4),
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME5),
(voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME6_B11_8) << 8) |
(voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME6_B7_4) << 4) |
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME6_B3_0),
(voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLC, NV_PAVS_VOICE_TAR_VOLC_VOLUME7_B11_8) << 8) |
(voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLB, NV_PAVS_VOICE_TAR_VOLB_VOLUME7_B7_4) << 4) |
voice_get_mask(d, v, NV_PAVS_VOICE_TAR_VOLA, NV_PAVS_VOICE_TAR_VOLA_VOLUME7_B3_0),
};
if (paused) {
assert(sizeof(samples) == 0x20 * 4 * 2);
memset(samples, 0x00, sizeof(samples));
} else {
cbo += 0x20 * rate * overdrive;
voice_set_mask(d, v, NV_PAVS_VOICE_PAR_OFFSET, NV_PAVS_VOICE_PAR_OFFSET_CBO, cbo);
}
// Apply the amplitude envelope
//FIXME: Figure out when exactly and how exactly this is actually done
for(unsigned int j = 0; j < channels; j++) {
for(unsigned int i = 0; i < 0x20; i++) {
samples[j][i] *= ea_value;
}
}
//FIXME: If phase negations means to flip the signal upside down
// we should modify volume of bin6 and bin7 here.
// Mix samples into voice bins
for(unsigned int j = 0; j < 8; j++) {
MCPX_DPRINTF("Adding voice 0x%04X to bin %d [Rate %.2f, Volume 0x%03X] sample %d at %d [%.2fs]\n", v, bin[j], rate, vol[j], samples[0], cbo, cbo / (rate * 48000.0f));
for(unsigned int i = 0; i < 0x20; i++) {
//FIXME: how is the volume added?
//FIXME: What happens to the other channel? Is this behaviour correct?
mixbins[bin[j]][i] += (0xFFF - vol[j]) * samples[j % channels][i] / 0xFFF;
}
}
#endif
}
/* This routine must run at 1500 Hz */
/* TODO: this should be on a thread so it waits on the voice lock */
static void se_frame(void *opaque)
{
MCPXAPUState *d = opaque;
int mixbin;
int sample;
timer_mod(d->se.frame_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
MCPX_DPRINTF("mcpx frame ping\n");
/* Buffer for all mixbins for this frame */
int32_t mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME] = { 0 };
/* Process all voices, mixing each into the affected MIXBINs */
int list;
for (list = 0; list < 3; list++) {
hwaddr top, current, next;
top = voice_list_regs[list].top;
current = voice_list_regs[list].current;
next = voice_list_regs[list].next;
d->regs[current] = d->regs[top];
MCPX_DPRINTF("list %d current voice %d\n", list, d->regs[current]);
while (d->regs[current] != 0xFFFF) {
d->regs[next] = voice_get_mask(d, d->regs[current],
NV_PAVS_VOICE_TAR_PITCH_LINK,
NV_PAVS_VOICE_TAR_PITCH_LINK_NEXT_VOICE_HANDLE);
if (!voice_get_mask(d, d->regs[current],
NV_PAVS_VOICE_PAR_STATE,
NV_PAVS_VOICE_PAR_STATE_ACTIVE_VOICE)) {
MCPX_DPRINTF("voice %d not active...!\n", d->regs[current]);
fe_method(d, SE2FE_IDLE_VOICE, d->regs[current]);
} else {
process_voice(d, mixbins, d->regs[current]);
}
MCPX_DPRINTF("next voice %d\n", d->regs[next]);
d->regs[current] = d->regs[next];
}
}
#if GENERATE_MIXBIN_BEEP
/* Inject some audio to the mixbin for debugging.
* Signal is 1500 Hz sine wave, phase shifted by mixbin number. */
for (mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
for (sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
/* Avoid multiple of 1.0 / NUM_SAMPLES_PER_FRAME for phase shift,
* or waves cancel out */
float offset = sample / (float)NUM_SAMPLES_PER_FRAME -
mixbin / (float)(NUM_SAMPLES_PER_FRAME + 1);
float wave = sinf(offset * M_PI * 2.0f);
mixbins[mixbin][sample] += wave * 0x3FFFFF;
}
}
#endif
/* Write VP results to the GP DSP MIXBUF */
for (mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
for (sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
dsp_write_memory(d->gp.dsp,
'X', GP_DSP_MIXBUF_BASE + mixbin * 0x20 + sample,
mixbins[mixbin][sample] & 0xFFFFFF);
}
}
/* Kickoff DSP processing */
if ((d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPRST)
&& (d->gp.regs[NV_PAPU_GPRST] & NV_PAPU_GPRST_GPDSPRST)) {
dsp_start_frame(d->gp.dsp);
// hax
dsp_run(d->gp.dsp, 1000);
}
if ((d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPRST)
&& (d->ep.regs[NV_PAPU_EPRST] & NV_PAPU_GPRST_GPDSPRST)) {
dsp_start_frame(d->ep.dsp);
// hax
// dsp_run(d->ep.dsp, 1000);
}
}
static void mcpx_apu_realize(PCIDevice *dev, Error **errp)
{
MCPXAPUState *d = MCPX_APU_DEVICE(dev);
dev->config[PCI_INTERRUPT_PIN] = 0x01;
memory_region_init_io(&d->mmio, OBJECT(dev), &mcpx_apu_mmio_ops, d,
"mcpx-apu-mmio", 0x80000);
memory_region_init_io(&d->vp.mmio, OBJECT(dev), &vp_ops, d,
"mcpx-apu-vp", 0x10000);
memory_region_add_subregion(&d->mmio, 0x20000, &d->vp.mmio);
memory_region_init_io(&d->gp.mmio, OBJECT(dev), &gp_ops, d,
"mcpx-apu-gp", 0x10000);
memory_region_add_subregion(&d->mmio, 0x30000, &d->gp.mmio);
memory_region_init_io(&d->ep.mmio, OBJECT(dev), &ep_ops, d,
"mcpx-apu-ep", 0x10000);
memory_region_add_subregion(&d->mmio, 0x50000, &d->ep.mmio);
pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
d->se.frame_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, se_frame, d);
d->gp.dsp = dsp_init(d, gp_scratch_rw, gp_fifo_rw);
d->ep.dsp = dsp_init(d, ep_scratch_rw, ep_fifo_rw);
}
static void mcpx_apu_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->vendor_id = PCI_VENDOR_ID_NVIDIA;
k->device_id = PCI_DEVICE_ID_NVIDIA_MCPX_APU;
k->revision = 210;
k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
k->realize = mcpx_apu_realize;
dc->desc = "MCPX Audio Processing Unit";
}
static const TypeInfo mcpx_apu_info = {
.name = "mcpx-apu",
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(MCPXAPUState),
.class_init = mcpx_apu_class_init,
.interfaces = (InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static void mcpx_apu_register(void)
{
type_register_static(&mcpx_apu_info);
}
type_init(mcpx_apu_register);
void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
{
PCIDevice *dev = pci_create_simple(bus, devfn, "mcpx-apu");
MCPXAPUState *d = MCPX_APU_DEVICE(dev);
/* Keep pointers to system memory */
d->ram = ram;
d->ram_ptr = memory_region_get_ram_ptr(d->ram);
}