From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/hw/audio/Makefile.objs | 18 + qemu/hw/audio/ac97.c | 1430 ++++++++++++++++++++++++++++++++++++++ qemu/hw/audio/adlib.c | 389 +++++++++++ qemu/hw/audio/cs4231.c | 186 +++++ qemu/hw/audio/cs4231a.c | 707 +++++++++++++++++++ qemu/hw/audio/es1370.c | 1080 ++++++++++++++++++++++++++++ qemu/hw/audio/fmopl.c | 1394 +++++++++++++++++++++++++++++++++++++ qemu/hw/audio/fmopl.h | 174 +++++ qemu/hw/audio/gus.c | 318 +++++++++ qemu/hw/audio/gusemu.h | 105 +++ qemu/hw/audio/gusemu_hal.c | 554 +++++++++++++++ qemu/hw/audio/gusemu_mixer.c | 240 +++++++ qemu/hw/audio/gustate.h | 132 ++++ qemu/hw/audio/hda-codec-common.h | 456 ++++++++++++ qemu/hw/audio/hda-codec.c | 731 +++++++++++++++++++ qemu/hw/audio/intel-hda-defs.h | 717 +++++++++++++++++++ qemu/hw/audio/intel-hda.c | 1343 +++++++++++++++++++++++++++++++++++ qemu/hw/audio/intel-hda.h | 72 ++ qemu/hw/audio/lm4549.c | 335 +++++++++ qemu/hw/audio/lm4549.h | 43 ++ qemu/hw/audio/marvell_88w8618.c | 306 ++++++++ qemu/hw/audio/milkymist-ac97.c | 348 ++++++++++ qemu/hw/audio/pcspk.c | 213 ++++++ qemu/hw/audio/pl041.c | 649 +++++++++++++++++ qemu/hw/audio/pl041.h | 135 ++++ qemu/hw/audio/pl041.hx | 81 +++ qemu/hw/audio/sb16.c | 1427 +++++++++++++++++++++++++++++++++++++ qemu/hw/audio/wm8750.c | 722 +++++++++++++++++++ 28 files changed, 14305 insertions(+) create mode 100644 qemu/hw/audio/Makefile.objs create mode 100644 qemu/hw/audio/ac97.c create mode 100644 qemu/hw/audio/adlib.c create mode 100644 qemu/hw/audio/cs4231.c create mode 100644 qemu/hw/audio/cs4231a.c create mode 100644 qemu/hw/audio/es1370.c create mode 100644 qemu/hw/audio/fmopl.c create mode 100644 qemu/hw/audio/fmopl.h create mode 100644 qemu/hw/audio/gus.c create mode 100644 qemu/hw/audio/gusemu.h create mode 100644 qemu/hw/audio/gusemu_hal.c create mode 100644 qemu/hw/audio/gusemu_mixer.c create mode 100644 qemu/hw/audio/gustate.h create mode 100644 qemu/hw/audio/hda-codec-common.h create mode 100644 qemu/hw/audio/hda-codec.c create mode 100644 qemu/hw/audio/intel-hda-defs.h create mode 100644 qemu/hw/audio/intel-hda.c create mode 100644 qemu/hw/audio/intel-hda.h create mode 100644 qemu/hw/audio/lm4549.c create mode 100644 qemu/hw/audio/lm4549.h create mode 100644 qemu/hw/audio/marvell_88w8618.c create mode 100644 qemu/hw/audio/milkymist-ac97.c create mode 100644 qemu/hw/audio/pcspk.c create mode 100644 qemu/hw/audio/pl041.c create mode 100644 qemu/hw/audio/pl041.h create mode 100644 qemu/hw/audio/pl041.hx create mode 100644 qemu/hw/audio/sb16.c create mode 100644 qemu/hw/audio/wm8750.c (limited to 'qemu/hw/audio') diff --git a/qemu/hw/audio/Makefile.objs b/qemu/hw/audio/Makefile.objs new file mode 100644 index 000000000..7ce85a2e8 --- /dev/null +++ b/qemu/hw/audio/Makefile.objs @@ -0,0 +1,18 @@ +# Sound +common-obj-$(CONFIG_SB16) += sb16.o +common-obj-$(CONFIG_ES1370) += es1370.o +common-obj-$(CONFIG_AC97) += ac97.o +common-obj-$(CONFIG_ADLIB) += fmopl.o adlib.o +common-obj-$(CONFIG_GUS) += gus.o gusemu_hal.o gusemu_mixer.o +common-obj-$(CONFIG_CS4231A) += cs4231a.o +common-obj-$(CONFIG_HDA) += intel-hda.o hda-codec.o + +common-obj-$(CONFIG_PCSPK) += pcspk.o +common-obj-$(CONFIG_WM8750) += wm8750.o +common-obj-$(CONFIG_PL041) += pl041.o lm4549.o + +common-obj-$(CONFIG_CS4231) += cs4231.o +common-obj-$(CONFIG_MARVELL_88W8618) += marvell_88w8618.o +common-obj-$(CONFIG_MILKYMIST) += milkymist-ac97.o + +$(obj)/adlib.o $(obj)/fmopl.o: QEMU_CFLAGS += -DBUILD_Y8950=0 diff --git a/qemu/hw/audio/ac97.c b/qemu/hw/audio/ac97.c new file mode 100644 index 000000000..b17383555 --- /dev/null +++ b/qemu/hw/audio/ac97.c @@ -0,0 +1,1430 @@ +/* + * Copyright (C) 2006 InnoTek Systemberatung GmbH + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file 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, + * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE + * distribution. VirtualBox OSE is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY of any kind. + * + * If you received this file as part of a commercial VirtualBox + * distribution, then only the terms of your commercial VirtualBox + * license agreement apply instead of the previous paragraph. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/pci/pci.h" +#include "sysemu/dma.h" + +enum { + AC97_Reset = 0x00, + AC97_Master_Volume_Mute = 0x02, + AC97_Headphone_Volume_Mute = 0x04, + AC97_Master_Volume_Mono_Mute = 0x06, + AC97_Master_Tone_RL = 0x08, + AC97_PC_BEEP_Volume_Mute = 0x0A, + AC97_Phone_Volume_Mute = 0x0C, + AC97_Mic_Volume_Mute = 0x0E, + AC97_Line_In_Volume_Mute = 0x10, + AC97_CD_Volume_Mute = 0x12, + AC97_Video_Volume_Mute = 0x14, + AC97_Aux_Volume_Mute = 0x16, + AC97_PCM_Out_Volume_Mute = 0x18, + AC97_Record_Select = 0x1A, + AC97_Record_Gain_Mute = 0x1C, + AC97_Record_Gain_Mic_Mute = 0x1E, + AC97_General_Purpose = 0x20, + AC97_3D_Control = 0x22, + AC97_AC_97_RESERVED = 0x24, + AC97_Powerdown_Ctrl_Stat = 0x26, + AC97_Extended_Audio_ID = 0x28, + AC97_Extended_Audio_Ctrl_Stat = 0x2A, + AC97_PCM_Front_DAC_Rate = 0x2C, + AC97_PCM_Surround_DAC_Rate = 0x2E, + AC97_PCM_LFE_DAC_Rate = 0x30, + AC97_PCM_LR_ADC_Rate = 0x32, + AC97_MIC_ADC_Rate = 0x34, + AC97_6Ch_Vol_C_LFE_Mute = 0x36, + AC97_6Ch_Vol_L_R_Surround_Mute = 0x38, + AC97_Vendor_Reserved = 0x58, + AC97_Sigmatel_Analog = 0x6c, /* We emulate a Sigmatel codec */ + AC97_Sigmatel_Dac2Invert = 0x6e, /* We emulate a Sigmatel codec */ + AC97_Vendor_ID1 = 0x7c, + AC97_Vendor_ID2 = 0x7e +}; + +#define SOFT_VOLUME +#define SR_FIFOE 16 /* rwc */ +#define SR_BCIS 8 /* rwc */ +#define SR_LVBCI 4 /* rwc */ +#define SR_CELV 2 /* ro */ +#define SR_DCH 1 /* ro */ +#define SR_VALID_MASK ((1 << 5) - 1) +#define SR_WCLEAR_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI) +#define SR_RO_MASK (SR_DCH | SR_CELV) +#define SR_INT_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI) + +#define CR_IOCE 16 /* rw */ +#define CR_FEIE 8 /* rw */ +#define CR_LVBIE 4 /* rw */ +#define CR_RR 2 /* rw */ +#define CR_RPBM 1 /* rw */ +#define CR_VALID_MASK ((1 << 5) - 1) +#define CR_DONT_CLEAR_MASK (CR_IOCE | CR_FEIE | CR_LVBIE) + +#define GC_WR 4 /* rw */ +#define GC_CR 2 /* rw */ +#define GC_VALID_MASK ((1 << 6) - 1) + +#define GS_MD3 (1<<17) /* rw */ +#define GS_AD3 (1<<16) /* rw */ +#define GS_RCS (1<<15) /* rwc */ +#define GS_B3S12 (1<<14) /* ro */ +#define GS_B2S12 (1<<13) /* ro */ +#define GS_B1S12 (1<<12) /* ro */ +#define GS_S1R1 (1<<11) /* rwc */ +#define GS_S0R1 (1<<10) /* rwc */ +#define GS_S1CR (1<<9) /* ro */ +#define GS_S0CR (1<<8) /* ro */ +#define GS_MINT (1<<7) /* ro */ +#define GS_POINT (1<<6) /* ro */ +#define GS_PIINT (1<<5) /* ro */ +#define GS_RSRVD ((1<<4)|(1<<3)) +#define GS_MOINT (1<<2) /* ro */ +#define GS_MIINT (1<<1) /* ro */ +#define GS_GSCI 1 /* rwc */ +#define GS_RO_MASK (GS_B3S12| \ + GS_B2S12| \ + GS_B1S12| \ + GS_S1CR| \ + GS_S0CR| \ + GS_MINT| \ + GS_POINT| \ + GS_PIINT| \ + GS_RSRVD| \ + GS_MOINT| \ + GS_MIINT) +#define GS_VALID_MASK ((1 << 18) - 1) +#define GS_WCLEAR_MASK (GS_RCS|GS_S1R1|GS_S0R1|GS_GSCI) + +#define BD_IOC (1<<31) +#define BD_BUP (1<<30) + +#define EACS_VRA 1 +#define EACS_VRM 8 + +#define MUTE_SHIFT 15 + +#define REC_MASK 7 +enum { + REC_MIC = 0, + REC_CD, + REC_VIDEO, + REC_AUX, + REC_LINE_IN, + REC_STEREO_MIX, + REC_MONO_MIX, + REC_PHONE +}; + +typedef struct BD { + uint32_t addr; + uint32_t ctl_len; +} BD; + +typedef struct AC97BusMasterRegs { + uint32_t bdbar; /* rw 0 */ + uint8_t civ; /* ro 0 */ + uint8_t lvi; /* rw 0 */ + uint16_t sr; /* rw 1 */ + uint16_t picb; /* ro 0 */ + uint8_t piv; /* ro 0 */ + uint8_t cr; /* rw 0 */ + unsigned int bd_valid; + BD bd; +} AC97BusMasterRegs; + +typedef struct AC97LinkState { + PCIDevice dev; + QEMUSoundCard card; + uint32_t use_broken_id; + uint32_t glob_cnt; + uint32_t glob_sta; + uint32_t cas; + uint32_t last_samp; + AC97BusMasterRegs bm_regs[3]; + uint8_t mixer_data[256]; + SWVoiceIn *voice_pi; + SWVoiceOut *voice_po; + SWVoiceIn *voice_mc; + int invalid_freq[3]; + uint8_t silence[128]; + int bup_flag; + MemoryRegion io_nam; + MemoryRegion io_nabm; +} AC97LinkState; + +enum { + BUP_SET = 1, + BUP_LAST = 2 +}; + +#ifdef DEBUG_AC97 +#define dolog(...) AUD_log ("ac97", __VA_ARGS__) +#else +#define dolog(...) +#endif + +#define MKREGS(prefix, start) \ +enum { \ + prefix ## _BDBAR = start, \ + prefix ## _CIV = start + 4, \ + prefix ## _LVI = start + 5, \ + prefix ## _SR = start + 6, \ + prefix ## _PICB = start + 8, \ + prefix ## _PIV = start + 10, \ + prefix ## _CR = start + 11 \ +} + +enum { + PI_INDEX = 0, + PO_INDEX, + MC_INDEX, + LAST_INDEX +}; + +MKREGS (PI, PI_INDEX * 16); +MKREGS (PO, PO_INDEX * 16); +MKREGS (MC, MC_INDEX * 16); + +enum { + GLOB_CNT = 0x2c, + GLOB_STA = 0x30, + CAS = 0x34 +}; + +#define GET_BM(index) (((index) >> 4) & 3) + +static void po_callback (void *opaque, int free); +static void pi_callback (void *opaque, int avail); +static void mc_callback (void *opaque, int avail); + +static void warm_reset (AC97LinkState *s) +{ + (void) s; +} + +static void cold_reset (AC97LinkState * s) +{ + (void) s; +} + +static void fetch_bd (AC97LinkState *s, AC97BusMasterRegs *r) +{ + uint8_t b[8]; + + pci_dma_read (&s->dev, r->bdbar + r->civ * 8, b, 8); + r->bd_valid = 1; + r->bd.addr = le32_to_cpu (*(uint32_t *) &b[0]) & ~3; + r->bd.ctl_len = le32_to_cpu (*(uint32_t *) &b[4]); + r->picb = r->bd.ctl_len & 0xffff; + dolog ("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes)\n", + r->civ, r->bd.addr, r->bd.ctl_len >> 16, + r->bd.ctl_len & 0xffff, + (r->bd.ctl_len & 0xffff) << 1); +} + +static void update_sr (AC97LinkState *s, AC97BusMasterRegs *r, uint32_t new_sr) +{ + int event = 0; + int level = 0; + uint32_t new_mask = new_sr & SR_INT_MASK; + uint32_t old_mask = r->sr & SR_INT_MASK; + uint32_t masks[] = {GS_PIINT, GS_POINT, GS_MINT}; + + if (new_mask ^ old_mask) { + /** @todo is IRQ deasserted when only one of status bits is cleared? */ + if (!new_mask) { + event = 1; + level = 0; + } + else { + if ((new_mask & SR_LVBCI) && (r->cr & CR_LVBIE)) { + event = 1; + level = 1; + } + if ((new_mask & SR_BCIS) && (r->cr & CR_IOCE)) { + event = 1; + level = 1; + } + } + } + + r->sr = new_sr; + + dolog ("IOC%d LVB%d sr=%#x event=%d level=%d\n", + r->sr & SR_BCIS, r->sr & SR_LVBCI, + r->sr, + event, level); + + if (!event) + return; + + if (level) { + s->glob_sta |= masks[r - s->bm_regs]; + dolog ("set irq level=1\n"); + pci_irq_assert(&s->dev); + } + else { + s->glob_sta &= ~masks[r - s->bm_regs]; + dolog ("set irq level=0\n"); + pci_irq_deassert(&s->dev); + } +} + +static void voice_set_active (AC97LinkState *s, int bm_index, int on) +{ + switch (bm_index) { + case PI_INDEX: + AUD_set_active_in (s->voice_pi, on); + break; + + case PO_INDEX: + AUD_set_active_out (s->voice_po, on); + break; + + case MC_INDEX: + AUD_set_active_in (s->voice_mc, on); + break; + + default: + AUD_log ("ac97", "invalid bm_index(%d) in voice_set_active", bm_index); + break; + } +} + +static void reset_bm_regs (AC97LinkState *s, AC97BusMasterRegs *r) +{ + dolog ("reset_bm_regs\n"); + r->bdbar = 0; + r->civ = 0; + r->lvi = 0; + /** todo do we need to do that? */ + update_sr (s, r, SR_DCH); + r->picb = 0; + r->piv = 0; + r->cr = r->cr & CR_DONT_CLEAR_MASK; + r->bd_valid = 0; + + voice_set_active (s, r - s->bm_regs, 0); + memset (s->silence, 0, sizeof (s->silence)); +} + +static void mixer_store (AC97LinkState *s, uint32_t i, uint16_t v) +{ + if (i + 2 > sizeof (s->mixer_data)) { + dolog ("mixer_store: index %d out of bounds %zd\n", + i, sizeof (s->mixer_data)); + return; + } + + s->mixer_data[i + 0] = v & 0xff; + s->mixer_data[i + 1] = v >> 8; +} + +static uint16_t mixer_load (AC97LinkState *s, uint32_t i) +{ + uint16_t val = 0xffff; + + if (i + 2 > sizeof (s->mixer_data)) { + dolog ("mixer_load: index %d out of bounds %zd\n", + i, sizeof (s->mixer_data)); + } + else { + val = s->mixer_data[i + 0] | (s->mixer_data[i + 1] << 8); + } + + return val; +} + +static void open_voice (AC97LinkState *s, int index, int freq) +{ + struct audsettings as; + + as.freq = freq; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + if (freq > 0) { + s->invalid_freq[index] = 0; + switch (index) { + case PI_INDEX: + s->voice_pi = AUD_open_in ( + &s->card, + s->voice_pi, + "ac97.pi", + s, + pi_callback, + &as + ); + break; + + case PO_INDEX: + s->voice_po = AUD_open_out ( + &s->card, + s->voice_po, + "ac97.po", + s, + po_callback, + &as + ); + break; + + case MC_INDEX: + s->voice_mc = AUD_open_in ( + &s->card, + s->voice_mc, + "ac97.mc", + s, + mc_callback, + &as + ); + break; + } + } + else { + s->invalid_freq[index] = freq; + switch (index) { + case PI_INDEX: + AUD_close_in (&s->card, s->voice_pi); + s->voice_pi = NULL; + break; + + case PO_INDEX: + AUD_close_out (&s->card, s->voice_po); + s->voice_po = NULL; + break; + + case MC_INDEX: + AUD_close_in (&s->card, s->voice_mc); + s->voice_mc = NULL; + break; + } + } +} + +static void reset_voices (AC97LinkState *s, uint8_t active[LAST_INDEX]) +{ + uint16_t freq; + + freq = mixer_load (s, AC97_PCM_LR_ADC_Rate); + open_voice (s, PI_INDEX, freq); + AUD_set_active_in (s->voice_pi, active[PI_INDEX]); + + freq = mixer_load (s, AC97_PCM_Front_DAC_Rate); + open_voice (s, PO_INDEX, freq); + AUD_set_active_out (s->voice_po, active[PO_INDEX]); + + freq = mixer_load (s, AC97_MIC_ADC_Rate); + open_voice (s, MC_INDEX, freq); + AUD_set_active_in (s->voice_mc, active[MC_INDEX]); +} + +static void get_volume (uint16_t vol, uint16_t mask, int inverse, + int *mute, uint8_t *lvol, uint8_t *rvol) +{ + *mute = (vol >> MUTE_SHIFT) & 1; + *rvol = (255 * (vol & mask)) / mask; + *lvol = (255 * ((vol >> 8) & mask)) / mask; + + if (inverse) { + *rvol = 255 - *rvol; + *lvol = 255 - *lvol; + } +} + +static void update_combined_volume_out (AC97LinkState *s) +{ + uint8_t lvol, rvol, plvol, prvol; + int mute, pmute; + + get_volume (mixer_load (s, AC97_Master_Volume_Mute), 0x3f, 1, + &mute, &lvol, &rvol); + get_volume (mixer_load (s, AC97_PCM_Out_Volume_Mute), 0x1f, 1, + &pmute, &plvol, &prvol); + + mute = mute | pmute; + lvol = (lvol * plvol) / 255; + rvol = (rvol * prvol) / 255; + + AUD_set_volume_out (s->voice_po, mute, lvol, rvol); +} + +static void update_volume_in (AC97LinkState *s) +{ + uint8_t lvol, rvol; + int mute; + + get_volume (mixer_load (s, AC97_Record_Gain_Mute), 0x0f, 0, + &mute, &lvol, &rvol); + + AUD_set_volume_in (s->voice_pi, mute, lvol, rvol); +} + +static void set_volume (AC97LinkState *s, int index, uint32_t val) +{ + switch (index) { + case AC97_Master_Volume_Mute: + val &= 0xbf3f; + mixer_store (s, index, val); + update_combined_volume_out (s); + break; + case AC97_PCM_Out_Volume_Mute: + val &= 0x9f1f; + mixer_store (s, index, val); + update_combined_volume_out (s); + break; + case AC97_Record_Gain_Mute: + val &= 0x8f0f; + mixer_store (s, index, val); + update_volume_in (s); + break; + } +} + +static void record_select (AC97LinkState *s, uint32_t val) +{ + uint8_t rs = val & REC_MASK; + uint8_t ls = (val >> 8) & REC_MASK; + mixer_store (s, AC97_Record_Select, rs | (ls << 8)); +} + +static void mixer_reset (AC97LinkState *s) +{ + uint8_t active[LAST_INDEX]; + + dolog ("mixer_reset\n"); + memset (s->mixer_data, 0, sizeof (s->mixer_data)); + memset (active, 0, sizeof (active)); + mixer_store (s, AC97_Reset , 0x0000); /* 6940 */ + mixer_store (s, AC97_Headphone_Volume_Mute , 0x0000); + mixer_store (s, AC97_Master_Volume_Mono_Mute , 0x0000); + mixer_store (s, AC97_Master_Tone_RL, 0x0000); + mixer_store (s, AC97_PC_BEEP_Volume_Mute , 0x0000); + mixer_store (s, AC97_Phone_Volume_Mute , 0x0000); + mixer_store (s, AC97_Mic_Volume_Mute , 0x0000); + mixer_store (s, AC97_Line_In_Volume_Mute , 0x0000); + mixer_store (s, AC97_CD_Volume_Mute , 0x0000); + mixer_store (s, AC97_Video_Volume_Mute , 0x0000); + mixer_store (s, AC97_Aux_Volume_Mute , 0x0000); + mixer_store (s, AC97_Record_Gain_Mic_Mute , 0x0000); + mixer_store (s, AC97_General_Purpose , 0x0000); + mixer_store (s, AC97_3D_Control , 0x0000); + mixer_store (s, AC97_Powerdown_Ctrl_Stat , 0x000f); + + /* + * Sigmatel 9700 (STAC9700) + */ + mixer_store (s, AC97_Vendor_ID1 , 0x8384); + mixer_store (s, AC97_Vendor_ID2 , 0x7600); /* 7608 */ + + mixer_store (s, AC97_Extended_Audio_ID , 0x0809); + mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, 0x0009); + mixer_store (s, AC97_PCM_Front_DAC_Rate , 0xbb80); + mixer_store (s, AC97_PCM_Surround_DAC_Rate , 0xbb80); + mixer_store (s, AC97_PCM_LFE_DAC_Rate , 0xbb80); + mixer_store (s, AC97_PCM_LR_ADC_Rate , 0xbb80); + mixer_store (s, AC97_MIC_ADC_Rate , 0xbb80); + + record_select (s, 0); + set_volume (s, AC97_Master_Volume_Mute, 0x8000); + set_volume (s, AC97_PCM_Out_Volume_Mute, 0x8808); + set_volume (s, AC97_Record_Gain_Mute, 0x8808); + + reset_voices (s, active); +} + +/** + * Native audio mixer + * I/O Reads + */ +static uint32_t nam_readb (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + dolog ("U nam readb %#x\n", addr); + s->cas = 0; + return ~0U; +} + +static uint32_t nam_readw (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + uint32_t val = ~0U; + uint32_t index = addr; + s->cas = 0; + val = mixer_load (s, index); + return val; +} + +static uint32_t nam_readl (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + dolog ("U nam readl %#x\n", addr); + s->cas = 0; + return ~0U; +} + +/** + * Native audio mixer + * I/O Writes + */ +static void nam_writeb (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + dolog ("U nam writeb %#x <- %#x\n", addr, val); + s->cas = 0; +} + +static void nam_writew (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + uint32_t index = addr; + s->cas = 0; + switch (index) { + case AC97_Reset: + mixer_reset (s); + break; + case AC97_Powerdown_Ctrl_Stat: + val &= ~0x800f; + val |= mixer_load (s, index) & 0xf; + mixer_store (s, index, val); + break; + case AC97_PCM_Out_Volume_Mute: + case AC97_Master_Volume_Mute: + case AC97_Record_Gain_Mute: + set_volume (s, index, val); + break; + case AC97_Record_Select: + record_select (s, val); + break; + case AC97_Vendor_ID1: + case AC97_Vendor_ID2: + dolog ("Attempt to write vendor ID to %#x\n", val); + break; + case AC97_Extended_Audio_ID: + dolog ("Attempt to write extended audio ID to %#x\n", val); + break; + case AC97_Extended_Audio_Ctrl_Stat: + if (!(val & EACS_VRA)) { + mixer_store (s, AC97_PCM_Front_DAC_Rate, 0xbb80); + mixer_store (s, AC97_PCM_LR_ADC_Rate, 0xbb80); + open_voice (s, PI_INDEX, 48000); + open_voice (s, PO_INDEX, 48000); + } + if (!(val & EACS_VRM)) { + mixer_store (s, AC97_MIC_ADC_Rate, 0xbb80); + open_voice (s, MC_INDEX, 48000); + } + dolog ("Setting extended audio control to %#x\n", val); + mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, val); + break; + case AC97_PCM_Front_DAC_Rate: + if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) { + mixer_store (s, index, val); + dolog ("Set front DAC rate to %d\n", val); + open_voice (s, PO_INDEX, val); + } + else { + dolog ("Attempt to set front DAC rate to %d, " + "but VRA is not set\n", + val); + } + break; + case AC97_MIC_ADC_Rate: + if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRM) { + mixer_store (s, index, val); + dolog ("Set MIC ADC rate to %d\n", val); + open_voice (s, MC_INDEX, val); + } + else { + dolog ("Attempt to set MIC ADC rate to %d, " + "but VRM is not set\n", + val); + } + break; + case AC97_PCM_LR_ADC_Rate: + if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) { + mixer_store (s, index, val); + dolog ("Set front LR ADC rate to %d\n", val); + open_voice (s, PI_INDEX, val); + } + else { + dolog ("Attempt to set LR ADC rate to %d, but VRA is not set\n", + val); + } + break; + case AC97_Headphone_Volume_Mute: + case AC97_Master_Volume_Mono_Mute: + case AC97_Master_Tone_RL: + case AC97_PC_BEEP_Volume_Mute: + case AC97_Phone_Volume_Mute: + case AC97_Mic_Volume_Mute: + case AC97_Line_In_Volume_Mute: + case AC97_CD_Volume_Mute: + case AC97_Video_Volume_Mute: + case AC97_Aux_Volume_Mute: + case AC97_Record_Gain_Mic_Mute: + case AC97_General_Purpose: + case AC97_3D_Control: + case AC97_Sigmatel_Analog: + case AC97_Sigmatel_Dac2Invert: + /* None of the features in these regs are emulated, so they are RO */ + break; + default: + dolog ("U nam writew %#x <- %#x\n", addr, val); + mixer_store (s, index, val); + break; + } +} + +static void nam_writel (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + dolog ("U nam writel %#x <- %#x\n", addr, val); + s->cas = 0; +} + +/** + * Native audio bus master + * I/O Reads + */ +static uint32_t nabm_readb (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + uint32_t val = ~0U; + + switch (index) { + case CAS: + dolog ("CAS %d\n", s->cas); + val = s->cas; + s->cas = 1; + break; + case PI_CIV: + case PO_CIV: + case MC_CIV: + r = &s->bm_regs[GET_BM (index)]; + val = r->civ; + dolog ("CIV[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_LVI: + case PO_LVI: + case MC_LVI: + r = &s->bm_regs[GET_BM (index)]; + val = r->lvi; + dolog ("LVI[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_PIV: + case PO_PIV: + case MC_PIV: + r = &s->bm_regs[GET_BM (index)]; + val = r->piv; + dolog ("PIV[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_CR: + case PO_CR: + case MC_CR: + r = &s->bm_regs[GET_BM (index)]; + val = r->cr; + dolog ("CR[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_SR: + case PO_SR: + case MC_SR: + r = &s->bm_regs[GET_BM (index)]; + val = r->sr & 0xff; + dolog ("SRb[%d] -> %#x\n", GET_BM (index), val); + break; + default: + dolog ("U nabm readb %#x -> %#x\n", addr, val); + break; + } + return val; +} + +static uint32_t nabm_readw (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + uint32_t val = ~0U; + + switch (index) { + case PI_SR: + case PO_SR: + case MC_SR: + r = &s->bm_regs[GET_BM (index)]; + val = r->sr; + dolog ("SR[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_PICB: + case PO_PICB: + case MC_PICB: + r = &s->bm_regs[GET_BM (index)]; + val = r->picb; + dolog ("PICB[%d] -> %#x\n", GET_BM (index), val); + break; + default: + dolog ("U nabm readw %#x -> %#x\n", addr, val); + break; + } + return val; +} + +static uint32_t nabm_readl (void *opaque, uint32_t addr) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + uint32_t val = ~0U; + + switch (index) { + case PI_BDBAR: + case PO_BDBAR: + case MC_BDBAR: + r = &s->bm_regs[GET_BM (index)]; + val = r->bdbar; + dolog ("BMADDR[%d] -> %#x\n", GET_BM (index), val); + break; + case PI_CIV: + case PO_CIV: + case MC_CIV: + r = &s->bm_regs[GET_BM (index)]; + val = r->civ | (r->lvi << 8) | (r->sr << 16); + dolog ("CIV LVI SR[%d] -> %#x, %#x, %#x\n", GET_BM (index), + r->civ, r->lvi, r->sr); + break; + case PI_PICB: + case PO_PICB: + case MC_PICB: + r = &s->bm_regs[GET_BM (index)]; + val = r->picb | (r->piv << 16) | (r->cr << 24); + dolog ("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", GET_BM (index), + val, r->picb, r->piv, r->cr); + break; + case GLOB_CNT: + val = s->glob_cnt; + dolog ("glob_cnt -> %#x\n", val); + break; + case GLOB_STA: + val = s->glob_sta | GS_S0CR; + dolog ("glob_sta -> %#x\n", val); + break; + default: + dolog ("U nabm readl %#x -> %#x\n", addr, val); + break; + } + return val; +} + +/** + * Native audio bus master + * I/O Writes + */ +static void nabm_writeb (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + switch (index) { + case PI_LVI: + case PO_LVI: + case MC_LVI: + r = &s->bm_regs[GET_BM (index)]; + if ((r->cr & CR_RPBM) && (r->sr & SR_DCH)) { + r->sr &= ~(SR_DCH | SR_CELV); + r->civ = r->piv; + r->piv = (r->piv + 1) % 32; + fetch_bd (s, r); + } + r->lvi = val % 32; + dolog ("LVI[%d] <- %#x\n", GET_BM (index), val); + break; + case PI_CR: + case PO_CR: + case MC_CR: + r = &s->bm_regs[GET_BM (index)]; + if (val & CR_RR) { + reset_bm_regs (s, r); + } + else { + r->cr = val & CR_VALID_MASK; + if (!(r->cr & CR_RPBM)) { + voice_set_active (s, r - s->bm_regs, 0); + r->sr |= SR_DCH; + } + else { + r->civ = r->piv; + r->piv = (r->piv + 1) % 32; + fetch_bd (s, r); + r->sr &= ~SR_DCH; + voice_set_active (s, r - s->bm_regs, 1); + } + } + dolog ("CR[%d] <- %#x (cr %#x)\n", GET_BM (index), val, r->cr); + break; + case PI_SR: + case PO_SR: + case MC_SR: + r = &s->bm_regs[GET_BM (index)]; + r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK); + update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK)); + dolog ("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr); + break; + default: + dolog ("U nabm writeb %#x <- %#x\n", addr, val); + break; + } +} + +static void nabm_writew (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + switch (index) { + case PI_SR: + case PO_SR: + case MC_SR: + r = &s->bm_regs[GET_BM (index)]; + r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK); + update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK)); + dolog ("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr); + break; + default: + dolog ("U nabm writew %#x <- %#x\n", addr, val); + break; + } +} + +static void nabm_writel (void *opaque, uint32_t addr, uint32_t val) +{ + AC97LinkState *s = opaque; + AC97BusMasterRegs *r = NULL; + uint32_t index = addr; + switch (index) { + case PI_BDBAR: + case PO_BDBAR: + case MC_BDBAR: + r = &s->bm_regs[GET_BM (index)]; + r->bdbar = val & ~3; + dolog ("BDBAR[%d] <- %#x (bdbar %#x)\n", + GET_BM (index), val, r->bdbar); + break; + case GLOB_CNT: + if (val & GC_WR) + warm_reset (s); + if (val & GC_CR) + cold_reset (s); + if (!(val & (GC_WR | GC_CR))) + s->glob_cnt = val & GC_VALID_MASK; + dolog ("glob_cnt <- %#x (glob_cnt %#x)\n", val, s->glob_cnt); + break; + case GLOB_STA: + s->glob_sta &= ~(val & GS_WCLEAR_MASK); + s->glob_sta |= (val & ~(GS_WCLEAR_MASK | GS_RO_MASK)) & GS_VALID_MASK; + dolog ("glob_sta <- %#x (glob_sta %#x)\n", val, s->glob_sta); + break; + default: + dolog ("U nabm writel %#x <- %#x\n", addr, val); + break; + } +} + +static int write_audio (AC97LinkState *s, AC97BusMasterRegs *r, + int max, int *stop) +{ + uint8_t tmpbuf[4096]; + uint32_t addr = r->bd.addr; + uint32_t temp = r->picb << 1; + uint32_t written = 0; + int to_copy = 0; + temp = audio_MIN (temp, max); + + if (!temp) { + *stop = 1; + return 0; + } + + while (temp) { + int copied; + to_copy = audio_MIN (temp, sizeof (tmpbuf)); + pci_dma_read (&s->dev, addr, tmpbuf, to_copy); + copied = AUD_write (s->voice_po, tmpbuf, to_copy); + dolog ("write_audio max=%x to_copy=%x copied=%x\n", + max, to_copy, copied); + if (!copied) { + *stop = 1; + break; + } + temp -= copied; + addr += copied; + written += copied; + } + + if (!temp) { + if (to_copy < 4) { + dolog ("whoops\n"); + s->last_samp = 0; + } + else { + s->last_samp = *(uint32_t *) &tmpbuf[to_copy - 4]; + } + } + + r->bd.addr = addr; + return written; +} + +static void write_bup (AC97LinkState *s, int elapsed) +{ + dolog ("write_bup\n"); + if (!(s->bup_flag & BUP_SET)) { + if (s->bup_flag & BUP_LAST) { + int i; + uint8_t *p = s->silence; + for (i = 0; i < sizeof (s->silence) / 4; i++, p += 4) { + *(uint32_t *) p = s->last_samp; + } + } + else { + memset (s->silence, 0, sizeof (s->silence)); + } + s->bup_flag |= BUP_SET; + } + + while (elapsed) { + int temp = audio_MIN (elapsed, sizeof (s->silence)); + while (temp) { + int copied = AUD_write (s->voice_po, s->silence, temp); + if (!copied) + return; + temp -= copied; + elapsed -= copied; + } + } +} + +static int read_audio (AC97LinkState *s, AC97BusMasterRegs *r, + int max, int *stop) +{ + uint8_t tmpbuf[4096]; + uint32_t addr = r->bd.addr; + uint32_t temp = r->picb << 1; + uint32_t nread = 0; + int to_copy = 0; + SWVoiceIn *voice = (r - s->bm_regs) == MC_INDEX ? s->voice_mc : s->voice_pi; + + temp = audio_MIN (temp, max); + + if (!temp) { + *stop = 1; + return 0; + } + + while (temp) { + int acquired; + to_copy = audio_MIN (temp, sizeof (tmpbuf)); + acquired = AUD_read (voice, tmpbuf, to_copy); + if (!acquired) { + *stop = 1; + break; + } + pci_dma_write (&s->dev, addr, tmpbuf, acquired); + temp -= acquired; + addr += acquired; + nread += acquired; + } + + r->bd.addr = addr; + return nread; +} + +static void transfer_audio (AC97LinkState *s, int index, int elapsed) +{ + AC97BusMasterRegs *r = &s->bm_regs[index]; + int stop = 0; + + if (s->invalid_freq[index]) { + AUD_log ("ac97", "attempt to use voice %d with invalid frequency %d\n", + index, s->invalid_freq[index]); + return; + } + + if (r->sr & SR_DCH) { + if (r->cr & CR_RPBM) { + switch (index) { + case PO_INDEX: + write_bup (s, elapsed); + break; + } + } + return; + } + + while ((elapsed >> 1) && !stop) { + int temp; + + if (!r->bd_valid) { + dolog ("invalid bd\n"); + fetch_bd (s, r); + } + + if (!r->picb) { + dolog ("fresh bd %d is empty %#x %#x\n", + r->civ, r->bd.addr, r->bd.ctl_len); + if (r->civ == r->lvi) { + r->sr |= SR_DCH; /* CELV? */ + s->bup_flag = 0; + break; + } + r->sr &= ~SR_CELV; + r->civ = r->piv; + r->piv = (r->piv + 1) % 32; + fetch_bd (s, r); + return; + } + + switch (index) { + case PO_INDEX: + temp = write_audio (s, r, elapsed, &stop); + elapsed -= temp; + r->picb -= (temp >> 1); + break; + + case PI_INDEX: + case MC_INDEX: + temp = read_audio (s, r, elapsed, &stop); + elapsed -= temp; + r->picb -= (temp >> 1); + break; + } + + if (!r->picb) { + uint32_t new_sr = r->sr & ~SR_CELV; + + if (r->bd.ctl_len & BD_IOC) { + new_sr |= SR_BCIS; + } + + if (r->civ == r->lvi) { + dolog ("Underrun civ (%d) == lvi (%d)\n", r->civ, r->lvi); + + new_sr |= SR_LVBCI | SR_DCH | SR_CELV; + stop = 1; + s->bup_flag = (r->bd.ctl_len & BD_BUP) ? BUP_LAST : 0; + } + else { + r->civ = r->piv; + r->piv = (r->piv + 1) % 32; + fetch_bd (s, r); + } + + update_sr (s, r, new_sr); + } + } +} + +static void pi_callback (void *opaque, int avail) +{ + transfer_audio (opaque, PI_INDEX, avail); +} + +static void mc_callback (void *opaque, int avail) +{ + transfer_audio (opaque, MC_INDEX, avail); +} + +static void po_callback (void *opaque, int free) +{ + transfer_audio (opaque, PO_INDEX, free); +} + +static const VMStateDescription vmstate_ac97_bm_regs = { + .name = "ac97_bm_regs", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32 (bdbar, AC97BusMasterRegs), + VMSTATE_UINT8 (civ, AC97BusMasterRegs), + VMSTATE_UINT8 (lvi, AC97BusMasterRegs), + VMSTATE_UINT16 (sr, AC97BusMasterRegs), + VMSTATE_UINT16 (picb, AC97BusMasterRegs), + VMSTATE_UINT8 (piv, AC97BusMasterRegs), + VMSTATE_UINT8 (cr, AC97BusMasterRegs), + VMSTATE_UINT32 (bd_valid, AC97BusMasterRegs), + VMSTATE_UINT32 (bd.addr, AC97BusMasterRegs), + VMSTATE_UINT32 (bd.ctl_len, AC97BusMasterRegs), + VMSTATE_END_OF_LIST () + } +}; + +static int ac97_post_load (void *opaque, int version_id) +{ + uint8_t active[LAST_INDEX]; + AC97LinkState *s = opaque; + + record_select (s, mixer_load (s, AC97_Record_Select)); + set_volume (s, AC97_Master_Volume_Mute, + mixer_load (s, AC97_Master_Volume_Mute)); + set_volume (s, AC97_PCM_Out_Volume_Mute, + mixer_load (s, AC97_PCM_Out_Volume_Mute)); + set_volume (s, AC97_Record_Gain_Mute, + mixer_load (s, AC97_Record_Gain_Mute)); + + active[PI_INDEX] = !!(s->bm_regs[PI_INDEX].cr & CR_RPBM); + active[PO_INDEX] = !!(s->bm_regs[PO_INDEX].cr & CR_RPBM); + active[MC_INDEX] = !!(s->bm_regs[MC_INDEX].cr & CR_RPBM); + reset_voices (s, active); + + s->bup_flag = 0; + s->last_samp = 0; + return 0; +} + +static bool is_version_2 (void *opaque, int version_id) +{ + return version_id == 2; +} + +static const VMStateDescription vmstate_ac97 = { + .name = "ac97", + .version_id = 3, + .minimum_version_id = 2, + .post_load = ac97_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE (dev, AC97LinkState), + VMSTATE_UINT32 (glob_cnt, AC97LinkState), + VMSTATE_UINT32 (glob_sta, AC97LinkState), + VMSTATE_UINT32 (cas, AC97LinkState), + VMSTATE_STRUCT_ARRAY (bm_regs, AC97LinkState, 3, 1, + vmstate_ac97_bm_regs, AC97BusMasterRegs), + VMSTATE_BUFFER (mixer_data, AC97LinkState), + VMSTATE_UNUSED_TEST (is_version_2, 3), + VMSTATE_END_OF_LIST () + } +}; + +static uint64_t nam_read(void *opaque, hwaddr addr, unsigned size) +{ + if ((addr / size) > 256) { + return -1; + } + + switch (size) { + case 1: + return nam_readb(opaque, addr); + case 2: + return nam_readw(opaque, addr); + case 4: + return nam_readl(opaque, addr); + default: + return -1; + } +} + +static void nam_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + if ((addr / size) > 256) { + return; + } + + switch (size) { + case 1: + nam_writeb(opaque, addr, val); + break; + case 2: + nam_writew(opaque, addr, val); + break; + case 4: + nam_writel(opaque, addr, val); + break; + } +} + +static const MemoryRegionOps ac97_io_nam_ops = { + .read = nam_read, + .write = nam_write, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static uint64_t nabm_read(void *opaque, hwaddr addr, unsigned size) +{ + if ((addr / size) > 64) { + return -1; + } + + switch (size) { + case 1: + return nabm_readb(opaque, addr); + case 2: + return nabm_readw(opaque, addr); + case 4: + return nabm_readl(opaque, addr); + default: + return -1; + } +} + +static void nabm_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + if ((addr / size) > 64) { + return; + } + + switch (size) { + case 1: + nabm_writeb(opaque, addr, val); + break; + case 2: + nabm_writew(opaque, addr, val); + break; + case 4: + nabm_writel(opaque, addr, val); + break; + } +} + + +static const MemoryRegionOps ac97_io_nabm_ops = { + .read = nabm_read, + .write = nabm_write, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void ac97_on_reset (DeviceState *dev) +{ + AC97LinkState *s = container_of(dev, AC97LinkState, dev.qdev); + + reset_bm_regs (s, &s->bm_regs[0]); + reset_bm_regs (s, &s->bm_regs[1]); + reset_bm_regs (s, &s->bm_regs[2]); + + /* + * Reset the mixer too. The Windows XP driver seems to rely on + * this. At least it wants to read the vendor id before it resets + * the codec manually. + */ + mixer_reset (s); +} + +static void ac97_realize(PCIDevice *dev, Error **errp) +{ + AC97LinkState *s = DO_UPCAST (AC97LinkState, dev, dev); + uint8_t *c = s->dev.config; + + /* TODO: no need to override */ + c[PCI_COMMAND] = 0x00; /* pcicmd pci command rw, ro */ + c[PCI_COMMAND + 1] = 0x00; + + /* TODO: */ + c[PCI_STATUS] = PCI_STATUS_FAST_BACK; /* pcists pci status rwc, ro */ + c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_MEDIUM >> 8; + + c[PCI_CLASS_PROG] = 0x00; /* pi programming interface ro */ + + /* TODO set when bar is registered. no need to override. */ + /* nabmar native audio mixer base address rw */ + c[PCI_BASE_ADDRESS_0] = PCI_BASE_ADDRESS_SPACE_IO; + c[PCI_BASE_ADDRESS_0 + 1] = 0x00; + c[PCI_BASE_ADDRESS_0 + 2] = 0x00; + c[PCI_BASE_ADDRESS_0 + 3] = 0x00; + + /* TODO set when bar is registered. no need to override. */ + /* nabmbar native audio bus mastering base address rw */ + c[PCI_BASE_ADDRESS_0 + 4] = PCI_BASE_ADDRESS_SPACE_IO; + c[PCI_BASE_ADDRESS_0 + 5] = 0x00; + c[PCI_BASE_ADDRESS_0 + 6] = 0x00; + c[PCI_BASE_ADDRESS_0 + 7] = 0x00; + + if (s->use_broken_id) { + c[PCI_SUBSYSTEM_VENDOR_ID] = 0x86; + c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x80; + c[PCI_SUBSYSTEM_ID] = 0x00; + c[PCI_SUBSYSTEM_ID + 1] = 0x00; + } + + c[PCI_INTERRUPT_LINE] = 0x00; /* intr_ln interrupt line rw */ + c[PCI_INTERRUPT_PIN] = 0x01; /* intr_pn interrupt pin ro */ + + memory_region_init_io (&s->io_nam, OBJECT(s), &ac97_io_nam_ops, s, + "ac97-nam", 1024); + memory_region_init_io (&s->io_nabm, OBJECT(s), &ac97_io_nabm_ops, s, + "ac97-nabm", 256); + pci_register_bar (&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nam); + pci_register_bar (&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nabm); + AUD_register_card ("ac97", &s->card); + ac97_on_reset (&s->dev.qdev); +} + +static int ac97_init (PCIBus *bus) +{ + pci_create_simple (bus, -1, "AC97"); + return 0; +} + +static Property ac97_properties[] = { + DEFINE_PROP_UINT32 ("use_broken_id", AC97LinkState, use_broken_id, 0), + DEFINE_PROP_END_OF_LIST (), +}; + +static void ac97_class_init (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS (klass); + + k->realize = ac97_realize; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801AA_5; + k->revision = 0x01; + k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Intel 82801AA AC97 Audio"; + dc->vmsd = &vmstate_ac97; + dc->props = ac97_properties; + dc->reset = ac97_on_reset; +} + +static const TypeInfo ac97_info = { + .name = "AC97", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof (AC97LinkState), + .class_init = ac97_class_init, +}; + +static void ac97_register_types (void) +{ + type_register_static (&ac97_info); + pci_register_soundhw("ac97", "Intel 82801AA AC97 Audio", ac97_init); +} + +type_init (ac97_register_types) diff --git a/qemu/hw/audio/adlib.c b/qemu/hw/audio/adlib.c new file mode 100644 index 000000000..656eb3773 --- /dev/null +++ b/qemu/hw/audio/adlib.c @@ -0,0 +1,389 @@ +/* + * QEMU Proxy for OPL2/3 emulation by MAME team + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/isa/isa.h" + +//#define DEBUG + +#define ADLIB_KILL_TIMERS 1 + +#ifdef HAS_YMF262 +#define ADLIB_DESC "Yamaha YMF262 (OPL3)" +#else +#define ADLIB_DESC "Yamaha YM3812 (OPL2)" +#endif + +#ifdef DEBUG +#include "qemu/timer.h" +#endif + +#define dolog(...) AUD_log ("adlib", __VA_ARGS__) +#ifdef DEBUG +#define ldebug(...) dolog (__VA_ARGS__) +#else +#define ldebug(...) +#endif + +#ifdef HAS_YMF262 +#include "ymf262.h" +void YMF262UpdateOneQEMU (int which, INT16 *dst, int length); +#define SHIFT 2 +#else +#include "fmopl.h" +#define SHIFT 1 +#endif + +#define IO_READ_PROTO(name) \ + uint32_t name (void *opaque, uint32_t nport) +#define IO_WRITE_PROTO(name) \ + void name (void *opaque, uint32_t nport, uint32_t val) + +#define TYPE_ADLIB "adlib" +#define ADLIB(obj) OBJECT_CHECK(AdlibState, (obj), TYPE_ADLIB) + +typedef struct { + ISADevice parent_obj; + + QEMUSoundCard card; + uint32_t freq; + uint32_t port; + int ticking[2]; + int enabled; + int active; + int bufpos; +#ifdef DEBUG + int64_t exp[2]; +#endif + int16_t *mixbuf; + uint64_t dexp[2]; + SWVoiceOut *voice; + int left, pos, samples; + QEMUAudioTimeStamp ats; +#ifndef HAS_YMF262 + FM_OPL *opl; +#endif + PortioList port_list; +} AdlibState; + +static AdlibState *glob_adlib; + +static void adlib_stop_opl_timer (AdlibState *s, size_t n) +{ +#ifdef HAS_YMF262 + YMF262TimerOver (0, n); +#else + OPLTimerOver (s->opl, n); +#endif + s->ticking[n] = 0; +} + +static void adlib_kill_timers (AdlibState *s) +{ + size_t i; + + for (i = 0; i < 2; ++i) { + if (s->ticking[i]) { + uint64_t delta; + + delta = AUD_get_elapsed_usec_out (s->voice, &s->ats); + ldebug ( + "delta = %f dexp = %f expired => %d\n", + delta / 1000000.0, + s->dexp[i] / 1000000.0, + delta >= s->dexp[i] + ); + if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) { + adlib_stop_opl_timer (s, i); + AUD_init_time_stamp_out (s->voice, &s->ats); + } + } + } +} + +static IO_WRITE_PROTO (adlib_write) +{ + AdlibState *s = opaque; + int a = nport & 3; + + s->active = 1; + AUD_set_active_out (s->voice, 1); + + adlib_kill_timers (s); + +#ifdef HAS_YMF262 + YMF262Write (0, a, val); +#else + OPLWrite (s->opl, a, val); +#endif +} + +static IO_READ_PROTO (adlib_read) +{ + AdlibState *s = opaque; + uint8_t data; + int a = nport & 3; + + adlib_kill_timers (s); + +#ifdef HAS_YMF262 + data = YMF262Read (0, a); +#else + data = OPLRead (s->opl, a); +#endif + return data; +} + +static void timer_handler (int c, double interval_Sec) +{ + AdlibState *s = glob_adlib; + unsigned n = c & 1; +#ifdef DEBUG + double interval; + int64_t exp; +#endif + + if (interval_Sec == 0.0) { + s->ticking[n] = 0; + return; + } + + s->ticking[n] = 1; +#ifdef DEBUG + interval = get_ticks_per_sec () * interval_Sec; + exp = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + interval; + s->exp[n] = exp; +#endif + + s->dexp[n] = interval_Sec * 1000000.0; + AUD_init_time_stamp_out (s->voice, &s->ats); +} + +static int write_audio (AdlibState *s, int samples) +{ + int net = 0; + int pos = s->pos; + + while (samples) { + int nbytes, wbytes, wsampl; + + nbytes = samples << SHIFT; + wbytes = AUD_write ( + s->voice, + s->mixbuf + (pos << (SHIFT - 1)), + nbytes + ); + + if (wbytes) { + wsampl = wbytes >> SHIFT; + + samples -= wsampl; + pos = (pos + wsampl) % s->samples; + + net += wsampl; + } + else { + break; + } + } + + return net; +} + +static void adlib_callback (void *opaque, int free) +{ + AdlibState *s = opaque; + int samples, net = 0, to_play, written; + + samples = free >> SHIFT; + if (!(s->active && s->enabled) || !samples) { + return; + } + + to_play = audio_MIN (s->left, samples); + while (to_play) { + written = write_audio (s, to_play); + + if (written) { + s->left -= written; + samples -= written; + to_play -= written; + s->pos = (s->pos + written) % s->samples; + } + else { + return; + } + } + + samples = audio_MIN (samples, s->samples - s->pos); + if (!samples) { + return; + } + +#ifdef HAS_YMF262 + YMF262UpdateOneQEMU (0, s->mixbuf + s->pos * 2, samples); +#else + YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples); +#endif + + while (samples) { + written = write_audio (s, samples); + + if (written) { + net += written; + samples -= written; + s->pos = (s->pos + written) % s->samples; + } + else { + s->left = samples; + return; + } + } +} + +static void Adlib_fini (AdlibState *s) +{ +#ifdef HAS_YMF262 + YMF262Shutdown (); +#else + if (s->opl) { + OPLDestroy (s->opl); + s->opl = NULL; + } +#endif + + g_free(s->mixbuf); + + s->active = 0; + s->enabled = 0; + AUD_remove_card (&s->card); +} + +static MemoryRegionPortio adlib_portio_list[] = { + { 0, 4, 1, .read = adlib_read, .write = adlib_write, }, + { 0, 2, 1, .read = adlib_read, .write = adlib_write, }, + { 0x388, 4, 1, .read = adlib_read, .write = adlib_write, }, + PORTIO_END_OF_LIST(), +}; + +static void adlib_realizefn (DeviceState *dev, Error **errp) +{ + AdlibState *s = ADLIB(dev); + struct audsettings as; + + if (glob_adlib) { + error_setg (errp, "Cannot create more than 1 adlib device"); + return; + } + glob_adlib = s; + +#ifdef HAS_YMF262 + if (YMF262Init (1, 14318180, s->freq)) { + error_setg (errp, "YMF262Init %d failed", s->freq); + return; + } + else { + YMF262SetTimerHandler (0, timer_handler, 0); + s->enabled = 1; + } +#else + s->opl = OPLCreate (OPL_TYPE_YM3812, 3579545, s->freq); + if (!s->opl) { + error_setg (errp, "OPLCreate %d failed", s->freq); + return; + } + else { + OPLSetTimerHandler (s->opl, timer_handler, 0); + s->enabled = 1; + } +#endif + + as.freq = s->freq; + as.nchannels = SHIFT; + as.fmt = AUD_FMT_S16; + as.endianness = AUDIO_HOST_ENDIANNESS; + + AUD_register_card ("adlib", &s->card); + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "adlib", + s, + adlib_callback, + &as + ); + if (!s->voice) { + Adlib_fini (s); + error_setg (errp, "Initializing audio voice failed"); + return; + } + + s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT; + s->mixbuf = g_malloc0 (s->samples << SHIFT); + + adlib_portio_list[0].offset = s->port; + adlib_portio_list[1].offset = s->port + 8; + portio_list_init (&s->port_list, OBJECT(s), adlib_portio_list, s, "adlib"); + portio_list_add (&s->port_list, isa_address_space_io(&s->parent_obj), 0); +} + +static Property adlib_properties[] = { + DEFINE_PROP_UINT32 ("iobase", AdlibState, port, 0x220), + DEFINE_PROP_UINT32 ("freq", AdlibState, freq, 44100), + DEFINE_PROP_END_OF_LIST (), +}; + +static void adlib_class_initfn (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + + dc->realize = adlib_realizefn; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = ADLIB_DESC; + dc->props = adlib_properties; +} + +static const TypeInfo adlib_info = { + .name = TYPE_ADLIB, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof (AdlibState), + .class_init = adlib_class_initfn, +}; + +static int Adlib_init (ISABus *bus) +{ + isa_create_simple (bus, TYPE_ADLIB); + return 0; +} + +static void adlib_register_types (void) +{ + type_register_static (&adlib_info); + isa_register_soundhw("adlib", ADLIB_DESC, Adlib_init); +} + +type_init (adlib_register_types) diff --git a/qemu/hw/audio/cs4231.c b/qemu/hw/audio/cs4231.c new file mode 100644 index 000000000..6325a8cea --- /dev/null +++ b/qemu/hw/audio/cs4231.c @@ -0,0 +1,186 @@ +/* + * QEMU Crystal CS4231 audio chip emulation + * + * Copyright (c) 2006 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "trace.h" + +/* + * In addition to Crystal CS4231 there is a DMA controller on Sparc. + */ +#define CS_SIZE 0x40 +#define CS_REGS 16 +#define CS_DREGS 32 +#define CS_MAXDREG (CS_DREGS - 1) + +#define TYPE_CS4231 "SUNW,CS4231" +#define CS4231(obj) \ + OBJECT_CHECK(CSState, (obj), TYPE_CS4231) + +typedef struct CSState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + uint32_t regs[CS_REGS]; + uint8_t dregs[CS_DREGS]; +} CSState; + +#define CS_RAP(s) ((s)->regs[0] & CS_MAXDREG) +#define CS_VER 0xa0 +#define CS_CDC_VER 0x8a + +static void cs_reset(DeviceState *d) +{ + CSState *s = CS4231(d); + + memset(s->regs, 0, CS_REGS * 4); + memset(s->dregs, 0, CS_DREGS); + s->dregs[12] = CS_CDC_VER; + s->dregs[25] = CS_VER; +} + +static uint64_t cs_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + CSState *s = opaque; + uint32_t saddr, ret; + + saddr = addr >> 2; + switch (saddr) { + case 1: + switch (CS_RAP(s)) { + case 3: // Write only + ret = 0; + break; + default: + ret = s->dregs[CS_RAP(s)]; + break; + } + trace_cs4231_mem_readl_dreg(CS_RAP(s), ret); + break; + default: + ret = s->regs[saddr]; + trace_cs4231_mem_readl_reg(saddr, ret); + break; + } + return ret; +} + +static void cs_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + CSState *s = opaque; + uint32_t saddr; + + saddr = addr >> 2; + trace_cs4231_mem_writel_reg(saddr, s->regs[saddr], val); + switch (saddr) { + case 1: + trace_cs4231_mem_writel_dreg(CS_RAP(s), s->dregs[CS_RAP(s)], val); + switch(CS_RAP(s)) { + case 11: + case 25: // Read only + break; + case 12: + val &= 0x40; + val |= CS_CDC_VER; // Codec version + s->dregs[CS_RAP(s)] = val; + break; + default: + s->dregs[CS_RAP(s)] = val; + break; + } + break; + case 2: // Read only + break; + case 4: + if (val & 1) { + cs_reset(DEVICE(s)); + } + val &= 0x7f; + s->regs[saddr] = val; + break; + default: + s->regs[saddr] = val; + break; + } +} + +static const MemoryRegionOps cs_mem_ops = { + .read = cs_mem_read, + .write = cs_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_cs4231 = { + .name ="cs4231", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, CSState, CS_REGS), + VMSTATE_UINT8_ARRAY(dregs, CSState, CS_DREGS), + VMSTATE_END_OF_LIST() + } +}; + +static int cs4231_init1(SysBusDevice *dev) +{ + CSState *s = CS4231(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &cs_mem_ops, s, "cs4321", + CS_SIZE); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + return 0; +} + +static Property cs4231_properties[] = { + {.name = NULL}, +}; + +static void cs4231_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = cs4231_init1; + dc->reset = cs_reset; + dc->vmsd = &vmstate_cs4231; + dc->props = cs4231_properties; +} + +static const TypeInfo cs4231_info = { + .name = TYPE_CS4231, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CSState), + .class_init = cs4231_class_init, +}; + +static void cs4231_register_types(void) +{ + type_register_static(&cs4231_info); +} + +type_init(cs4231_register_types) diff --git a/qemu/hw/audio/cs4231a.c b/qemu/hw/audio/cs4231a.c new file mode 100644 index 000000000..f96f561c7 --- /dev/null +++ b/qemu/hw/audio/cs4231a.c @@ -0,0 +1,707 @@ +/* + * QEMU Crystal CS4231 audio chip emulation + * + * Copyright (c) 2006 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/isa/isa.h" +#include "hw/qdev.h" +#include "qemu/timer.h" + +/* + Missing features: + ADC + Loopback + Timer + ADPCM + More... +*/ + +/* #define DEBUG */ +/* #define DEBUG_XLAW */ + +static struct { + int aci_counter; +} conf = {1}; + +#ifdef DEBUG +#define dolog(...) AUD_log ("cs4231a", __VA_ARGS__) +#else +#define dolog(...) +#endif + +#define lwarn(...) AUD_log ("cs4231a", "warning: " __VA_ARGS__) +#define lerr(...) AUD_log ("cs4231a", "error: " __VA_ARGS__) + +#define CS_REGS 16 +#define CS_DREGS 32 + +#define TYPE_CS4231A "cs4231a" +#define CS4231A(obj) OBJECT_CHECK (CSState, (obj), TYPE_CS4231A) + +typedef struct CSState { + ISADevice dev; + QEMUSoundCard card; + MemoryRegion ioports; + qemu_irq pic; + uint32_t regs[CS_REGS]; + uint8_t dregs[CS_DREGS]; + uint32_t irq; + uint32_t dma; + uint32_t port; + int shift; + int dma_running; + int audio_free; + int transferred; + int aci_counter; + SWVoiceOut *voice; + int16_t *tab; +} CSState; + +#define MODE2 (1 << 6) +#define MCE (1 << 6) +#define PMCE (1 << 4) +#define CMCE (1 << 5) +#define TE (1 << 6) +#define PEN (1 << 0) +#define INT (1 << 0) +#define IEN (1 << 1) +#define PPIO (1 << 6) +#define PI (1 << 4) +#define CI (1 << 5) +#define TI (1 << 6) + +enum { + Index_Address, + Index_Data, + Status, + PIO_Data +}; + +enum { + Left_ADC_Input_Control, + Right_ADC_Input_Control, + Left_AUX1_Input_Control, + Right_AUX1_Input_Control, + Left_AUX2_Input_Control, + Right_AUX2_Input_Control, + Left_DAC_Output_Control, + Right_DAC_Output_Control, + FS_And_Playback_Data_Format, + Interface_Configuration, + Pin_Control, + Error_Status_And_Initialization, + MODE_And_ID, + Loopback_Control, + Playback_Upper_Base_Count, + Playback_Lower_Base_Count, + Alternate_Feature_Enable_I, + Alternate_Feature_Enable_II, + Left_Line_Input_Control, + Right_Line_Input_Control, + Timer_Low_Base, + Timer_High_Base, + RESERVED, + Alternate_Feature_Enable_III, + Alternate_Feature_Status, + Version_Chip_ID, + Mono_Input_And_Output_Control, + RESERVED_2, + Capture_Data_Format, + RESERVED_3, + Capture_Upper_Base_Count, + Capture_Lower_Base_Count +}; + +static int freqs[2][8] = { + { 8000, 16000, 27420, 32000, -1, -1, 48000, 9000 }, + { 5510, 11025, 18900, 22050, 37800, 44100, 33075, 6620 } +}; + +/* Tables courtesy http://hazelware.luggle.com/tutorials/mulawcompression.html */ +static int16_t MuLawDecompressTable[256] = +{ + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + +static int16_t ALawDecompressTable[256] = +{ + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, + -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, + -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472, + -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 +}; + +static void cs4231a_reset (DeviceState *dev) +{ + CSState *s = CS4231A (dev); + + s->regs[Index_Address] = 0x40; + s->regs[Index_Data] = 0x00; + s->regs[Status] = 0x00; + s->regs[PIO_Data] = 0x00; + + s->dregs[Left_ADC_Input_Control] = 0x00; + s->dregs[Right_ADC_Input_Control] = 0x00; + s->dregs[Left_AUX1_Input_Control] = 0x88; + s->dregs[Right_AUX1_Input_Control] = 0x88; + s->dregs[Left_AUX2_Input_Control] = 0x88; + s->dregs[Right_AUX2_Input_Control] = 0x88; + s->dregs[Left_DAC_Output_Control] = 0x80; + s->dregs[Right_DAC_Output_Control] = 0x80; + s->dregs[FS_And_Playback_Data_Format] = 0x00; + s->dregs[Interface_Configuration] = 0x08; + s->dregs[Pin_Control] = 0x00; + s->dregs[Error_Status_And_Initialization] = 0x00; + s->dregs[MODE_And_ID] = 0x8a; + s->dregs[Loopback_Control] = 0x00; + s->dregs[Playback_Upper_Base_Count] = 0x00; + s->dregs[Playback_Lower_Base_Count] = 0x00; + s->dregs[Alternate_Feature_Enable_I] = 0x00; + s->dregs[Alternate_Feature_Enable_II] = 0x00; + s->dregs[Left_Line_Input_Control] = 0x88; + s->dregs[Right_Line_Input_Control] = 0x88; + s->dregs[Timer_Low_Base] = 0x00; + s->dregs[Timer_High_Base] = 0x00; + s->dregs[RESERVED] = 0x00; + s->dregs[Alternate_Feature_Enable_III] = 0x00; + s->dregs[Alternate_Feature_Status] = 0x00; + s->dregs[Version_Chip_ID] = 0xa0; + s->dregs[Mono_Input_And_Output_Control] = 0xa0; + s->dregs[RESERVED_2] = 0x00; + s->dregs[Capture_Data_Format] = 0x00; + s->dregs[RESERVED_3] = 0x00; + s->dregs[Capture_Upper_Base_Count] = 0x00; + s->dregs[Capture_Lower_Base_Count] = 0x00; +} + +static void cs_audio_callback (void *opaque, int free) +{ + CSState *s = opaque; + s->audio_free = free; +} + +static void cs_reset_voices (CSState *s, uint32_t val) +{ + int xtal; + struct audsettings as; + +#ifdef DEBUG_XLAW + if (val == 0 || val == 32) + val = (1 << 4) | (1 << 5); +#endif + + xtal = val & 1; + as.freq = freqs[xtal][(val >> 1) & 7]; + + if (as.freq == -1) { + lerr ("unsupported frequency (val=%#x)\n", val); + goto error; + } + + as.nchannels = (val & (1 << 4)) ? 2 : 1; + as.endianness = 0; + s->tab = NULL; + + switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) { + case 0: + as.fmt = AUD_FMT_U8; + s->shift = as.nchannels == 2; + break; + + case 1: + s->tab = MuLawDecompressTable; + goto x_law; + case 3: + s->tab = ALawDecompressTable; + x_law: + as.fmt = AUD_FMT_S16; + as.endianness = AUDIO_HOST_ENDIANNESS; + s->shift = as.nchannels == 2; + break; + + case 6: + as.endianness = 1; + case 2: + as.fmt = AUD_FMT_S16; + s->shift = as.nchannels; + break; + + case 7: + case 4: + lerr ("attempt to use reserved format value (%#x)\n", val); + goto error; + + case 5: + lerr ("ADPCM 4 bit IMA compatible format is not supported\n"); + goto error; + } + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "cs4231a", + s, + cs_audio_callback, + &as + ); + + if (s->dregs[Interface_Configuration] & PEN) { + if (!s->dma_running) { + DMA_hold_DREQ (s->dma); + AUD_set_active_out (s->voice, 1); + s->transferred = 0; + } + s->dma_running = 1; + } + else { + if (s->dma_running) { + DMA_release_DREQ (s->dma); + AUD_set_active_out (s->voice, 0); + } + s->dma_running = 0; + } + return; + + error: + if (s->dma_running) { + DMA_release_DREQ (s->dma); + AUD_set_active_out (s->voice, 0); + } +} + +static uint64_t cs_read (void *opaque, hwaddr addr, unsigned size) +{ + CSState *s = opaque; + uint32_t saddr, iaddr, ret; + + saddr = addr; + iaddr = ~0U; + + switch (saddr) { + case Index_Address: + ret = s->regs[saddr] & ~0x80; + break; + + case Index_Data: + if (!(s->dregs[MODE_And_ID] & MODE2)) + iaddr = s->regs[Index_Address] & 0x0f; + else + iaddr = s->regs[Index_Address] & 0x1f; + + ret = s->dregs[iaddr]; + if (iaddr == Error_Status_And_Initialization) { + /* keep SEAL happy */ + if (s->aci_counter) { + ret |= 1 << 5; + s->aci_counter -= 1; + } + } + break; + + default: + ret = s->regs[saddr]; + break; + } + dolog ("read %d:%d -> %d\n", saddr, iaddr, ret); + return ret; +} + +static void cs_write (void *opaque, hwaddr addr, + uint64_t val64, unsigned size) +{ + CSState *s = opaque; + uint32_t saddr, iaddr, val; + + saddr = addr; + val = val64; + + switch (saddr) { + case Index_Address: + if (!(s->regs[Index_Address] & MCE) && (val & MCE) + && (s->dregs[Interface_Configuration] & (3 << 3))) + s->aci_counter = conf.aci_counter; + + s->regs[Index_Address] = val & ~(1 << 7); + break; + + case Index_Data: + if (!(s->dregs[MODE_And_ID] & MODE2)) + iaddr = s->regs[Index_Address] & 0x0f; + else + iaddr = s->regs[Index_Address] & 0x1f; + + switch (iaddr) { + case RESERVED: + case RESERVED_2: + case RESERVED_3: + lwarn ("attempt to write %#x to reserved indirect register %d\n", + val, iaddr); + break; + + case FS_And_Playback_Data_Format: + if (s->regs[Index_Address] & MCE) { + cs_reset_voices (s, val); + } + else { + if (s->dregs[Alternate_Feature_Status] & PMCE) { + val = (val & ~0x0f) | (s->dregs[iaddr] & 0x0f); + cs_reset_voices (s, val); + } + else { + lwarn ("[P]MCE(%#x, %#x) is not set, val=%#x\n", + s->regs[Index_Address], + s->dregs[Alternate_Feature_Status], + val); + break; + } + } + s->dregs[iaddr] = val; + break; + + case Interface_Configuration: + val &= ~(1 << 5); /* D5 is reserved */ + s->dregs[iaddr] = val; + if (val & PPIO) { + lwarn ("PIO is not supported (%#x)\n", val); + break; + } + if (val & PEN) { + if (!s->dma_running) { + cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]); + } + } + else { + if (s->dma_running) { + DMA_release_DREQ (s->dma); + AUD_set_active_out (s->voice, 0); + s->dma_running = 0; + } + } + break; + + case Error_Status_And_Initialization: + lwarn ("attempt to write to read only register %d\n", iaddr); + break; + + case MODE_And_ID: + dolog ("val=%#x\n", val); + if (val & MODE2) + s->dregs[iaddr] |= MODE2; + else + s->dregs[iaddr] &= ~MODE2; + break; + + case Alternate_Feature_Enable_I: + if (val & TE) + lerr ("timer is not yet supported\n"); + s->dregs[iaddr] = val; + break; + + case Alternate_Feature_Status: + if ((s->dregs[iaddr] & PI) && !(val & PI)) { + /* XXX: TI CI */ + qemu_irq_lower (s->pic); + s->regs[Status] &= ~INT; + } + s->dregs[iaddr] = val; + break; + + case Version_Chip_ID: + lwarn ("write to Version_Chip_ID register %#x\n", val); + s->dregs[iaddr] = val; + break; + + default: + s->dregs[iaddr] = val; + break; + } + dolog ("written value %#x to indirect register %d\n", val, iaddr); + break; + + case Status: + if (s->regs[Status] & INT) { + qemu_irq_lower (s->pic); + } + s->regs[Status] &= ~INT; + s->dregs[Alternate_Feature_Status] &= ~(PI | CI | TI); + break; + + case PIO_Data: + lwarn ("attempt to write value %#x to PIO register\n", val); + break; + } +} + +static int cs_write_audio (CSState *s, int nchan, int dma_pos, + int dma_len, int len) +{ + int temp, net; + uint8_t tmpbuf[4096]; + + temp = len; + net = 0; + + while (temp) { + int left = dma_len - dma_pos; + int copied; + size_t to_copy; + + to_copy = audio_MIN (temp, left); + if (to_copy > sizeof (tmpbuf)) { + to_copy = sizeof (tmpbuf); + } + + copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy); + if (s->tab) { + int i; + int16_t linbuf[4096]; + + for (i = 0; i < copied; ++i) + linbuf[i] = s->tab[tmpbuf[i]]; + copied = AUD_write (s->voice, linbuf, copied << 1); + copied >>= 1; + } + else { + copied = AUD_write (s->voice, tmpbuf, copied); + } + + temp -= copied; + dma_pos = (dma_pos + copied) % dma_len; + net += copied; + + if (!copied) { + break; + } + } + + return net; +} + +static int cs_dma_read (void *opaque, int nchan, int dma_pos, int dma_len) +{ + CSState *s = opaque; + int copy, written; + int till = -1; + + copy = s->voice ? (s->audio_free >> (s->tab != NULL)) : dma_len; + + if (s->dregs[Pin_Control] & IEN) { + till = (s->dregs[Playback_Lower_Base_Count] + | (s->dregs[Playback_Upper_Base_Count] << 8)) << s->shift; + till -= s->transferred; + copy = audio_MIN (till, copy); + } + + if ((copy <= 0) || (dma_len <= 0)) { + return dma_pos; + } + + written = cs_write_audio (s, nchan, dma_pos, dma_len, copy); + + dma_pos = (dma_pos + written) % dma_len; + s->audio_free -= (written << (s->tab != NULL)); + + if (written == till) { + s->regs[Status] |= INT; + s->dregs[Alternate_Feature_Status] |= PI; + s->transferred = 0; + qemu_irq_raise (s->pic); + } + else { + s->transferred += written; + } + + return dma_pos; +} + +static int cs4231a_pre_load (void *opaque) +{ + CSState *s = opaque; + + if (s->dma_running) { + DMA_release_DREQ (s->dma); + AUD_set_active_out (s->voice, 0); + } + s->dma_running = 0; + return 0; +} + +static int cs4231a_post_load (void *opaque, int version_id) +{ + CSState *s = opaque; + + if (s->dma_running && (s->dregs[Interface_Configuration] & PEN)) { + s->dma_running = 0; + cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]); + } + return 0; +} + +static const VMStateDescription vmstate_cs4231a = { + .name = "cs4231a", + .version_id = 1, + .minimum_version_id = 1, + .pre_load = cs4231a_pre_load, + .post_load = cs4231a_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY (regs, CSState, CS_REGS), + VMSTATE_BUFFER (dregs, CSState), + VMSTATE_INT32 (dma_running, CSState), + VMSTATE_INT32 (audio_free, CSState), + VMSTATE_INT32 (transferred, CSState), + VMSTATE_INT32 (aci_counter, CSState), + VMSTATE_END_OF_LIST () + } +}; + +static const MemoryRegionOps cs_ioport_ops = { + .read = cs_read, + .write = cs_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + } +}; + +static void cs4231a_initfn (Object *obj) +{ + CSState *s = CS4231A (obj); + + memory_region_init_io (&s->ioports, OBJECT(s), &cs_ioport_ops, s, + "cs4231a", 4); +} + +static void cs4231a_realizefn (DeviceState *dev, Error **errp) +{ + ISADevice *d = ISA_DEVICE (dev); + CSState *s = CS4231A (dev); + + isa_init_irq (d, &s->pic, s->irq); + + isa_register_ioport (d, &s->ioports, s->port); + + DMA_register_channel (s->dma, cs_dma_read, s); + + AUD_register_card ("cs4231a", &s->card); +} + +static int cs4231a_init (ISABus *bus) +{ + isa_create_simple (bus, TYPE_CS4231A); + return 0; +} + +static Property cs4231a_properties[] = { + DEFINE_PROP_UINT32 ("iobase", CSState, port, 0x534), + DEFINE_PROP_UINT32 ("irq", CSState, irq, 9), + DEFINE_PROP_UINT32 ("dma", CSState, dma, 3), + DEFINE_PROP_END_OF_LIST (), +}; + +static void cs4231a_class_initfn (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + + dc->realize = cs4231a_realizefn; + dc->reset = cs4231a_reset; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Crystal Semiconductor CS4231A"; + dc->vmsd = &vmstate_cs4231a; + dc->props = cs4231a_properties; +} + +static const TypeInfo cs4231a_info = { + .name = TYPE_CS4231A, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof (CSState), + .instance_init = cs4231a_initfn, + .class_init = cs4231a_class_initfn, +}; + +static void cs4231a_register_types (void) +{ + type_register_static (&cs4231a_info); + isa_register_soundhw("cs4231a", "CS4231A", cs4231a_init); +} + +type_init (cs4231a_register_types) diff --git a/qemu/hw/audio/es1370.c b/qemu/hw/audio/es1370.c new file mode 100644 index 000000000..8e7bcf503 --- /dev/null +++ b/qemu/hw/audio/es1370.c @@ -0,0 +1,1080 @@ +/* + * QEMU ES1370 emulation + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* #define DEBUG_ES1370 */ +/* #define VERBOSE_ES1370 */ +#define SILENT_ES1370 + +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/pci/pci.h" +#include "sysemu/dma.h" + +/* Missing stuff: + SCTRL_P[12](END|ST)INC + SCTRL_P1SCTRLD + SCTRL_P2DACSEN + CTRL_DAC_SYNC + MIDI + non looped mode + surely more +*/ + +/* + Following macros and samplerate array were copied verbatim from + Linux kernel 2.4.30: drivers/sound/es1370.c + + Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) +*/ + +/* Start blatant GPL violation */ + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 +#define ES1370_REG_PHANTOM_FRAMEADR 0xd38 +#define ES1370_REG_PHANTOM_FRAMECNT 0xd3c + +static const unsigned dac1_samplerate[] = { 5512, 11025, 22050, 44100 }; + +#define DAC2_SRTODIV(x) (((1411200+(x)/2)/(x))-2) +#define DAC2_DIVTOSR(x) (1411200/((x)+2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* electret mic bias */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define USTAT_RXINT 0x80 /* UART rx int pending */ +#define USTAT_TXINT 0x04 /* UART tx int pending */ +#define USTAT_TXRDY 0x02 /* UART tx ready */ +#define USTAT_RXRDY 0x01 /* UART rx ready */ + +#define UCTRL_RXINTEN 0x80 /* 1 = enable RX ints */ +#define UCTRL_TXINTEN 0x60 /* TX int enable field mask */ +#define UCTRL_ENA_TXINT 0x20 /* enable TX int */ +#define UCTRL_CNTRL 0x03 /* control field */ +#define UCTRL_CNTRL_SWR 0x03 /* software reset command */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +/* End blatant GPL violation */ + +#define NB_CHANNELS 3 +#define DAC1_CHANNEL 0 +#define DAC2_CHANNEL 1 +#define ADC_CHANNEL 2 + +#define IO_READ_PROTO(n) \ +static uint32_t n (void *opaque, uint32_t addr) +#define IO_WRITE_PROTO(n) \ +static void n (void *opaque, uint32_t addr, uint32_t val) + +static void es1370_dac1_callback (void *opaque, int free); +static void es1370_dac2_callback (void *opaque, int free); +static void es1370_adc_callback (void *opaque, int avail); + +#ifdef DEBUG_ES1370 + +#define ldebug(...) AUD_log ("es1370", __VA_ARGS__) + +static void print_ctl (uint32_t val) +{ + char buf[1024]; + + buf[0] = '\0'; +#define a(n) if (val & CTRL_##n) strcat (buf, " "#n) + a (ADC_STOP); + a (XCTL1); + a (OPEN); + a (MSFMTSEL); + a (M_SBB); + a (DAC_SYNC); + a (CCB_INTRM); + a (M_CB); + a (XCTL0); + a (BREQ); + a (DAC1_EN); + a (DAC2_EN); + a (ADC_EN); + a (UART_EN); + a (JYSTK_EN); + a (CDC_EN); + a (SERR_DIS); +#undef a + AUD_log ("es1370", "ctl - PCLKDIV %d(DAC2 freq %d), freq %d,%s\n", + (val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV, + DAC2_DIVTOSR ((val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), + dac1_samplerate[(val & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL], + buf); +} + +static void print_sctl (uint32_t val) +{ + static const char *fmt_names[] = {"8M", "8S", "16M", "16S"}; + char buf[1024]; + + buf[0] = '\0'; + +#define a(n) if (val & SCTRL_##n) strcat (buf, " "#n) +#define b(n) if (!(val & SCTRL_##n)) strcat (buf, " "#n) + b (R1LOOPSEL); + b (P2LOOPSEL); + b (P1LOOPSEL); + a (P2PAUSE); + a (P1PAUSE); + a (R1INTEN); + a (P2INTEN); + a (P1INTEN); + a (P1SCTRLD); + a (P2DACSEN); + if (buf[0]) { + strcat (buf, "\n "); + } + else { + buf[0] = ' '; + buf[1] = '\0'; + } +#undef b +#undef a + AUD_log ("es1370", + "%s" + "p2_end_inc %d, p2_st_inc %d, r1_fmt %s, p2_fmt %s, p1_fmt %s\n", + buf, + (val & SCTRL_P2ENDINC) >> SCTRL_SH_P2ENDINC, + (val & SCTRL_P2STINC) >> SCTRL_SH_P2STINC, + fmt_names [(val >> SCTRL_SH_R1FMT) & 3], + fmt_names [(val >> SCTRL_SH_P2FMT) & 3], + fmt_names [(val >> SCTRL_SH_P1FMT) & 3] + ); +} +#else +#define ldebug(...) +#define print_ctl(...) +#define print_sctl(...) +#endif + +#ifdef VERBOSE_ES1370 +#define dolog(...) AUD_log ("es1370", __VA_ARGS__) +#else +#define dolog(...) +#endif + +#ifndef SILENT_ES1370 +#define lwarn(...) AUD_log ("es1370: warning", __VA_ARGS__) +#else +#define lwarn(...) +#endif + +struct chan { + uint32_t shift; + uint32_t leftover; + uint32_t scount; + uint32_t frame_addr; + uint32_t frame_cnt; +}; + +typedef struct ES1370State { + PCIDevice dev; + QEMUSoundCard card; + MemoryRegion io; + struct chan chan[NB_CHANNELS]; + SWVoiceOut *dac_voice[2]; + SWVoiceIn *adc_voice; + + uint32_t ctl; + uint32_t status; + uint32_t mempage; + uint32_t codec; + uint32_t sctl; +} ES1370State; + +struct chan_bits { + uint32_t ctl_en; + uint32_t stat_int; + uint32_t sctl_pause; + uint32_t sctl_inten; + uint32_t sctl_fmt; + uint32_t sctl_sh_fmt; + uint32_t sctl_loopsel; + void (*calc_freq) (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq); +}; + +static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq); +static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, + uint32_t *new_freq); + +static const struct chan_bits es1370_chan_bits[] = { + {CTRL_DAC1_EN, STAT_DAC1, SCTRL_P1PAUSE, SCTRL_P1INTEN, + SCTRL_P1FMT, SCTRL_SH_P1FMT, SCTRL_P1LOOPSEL, + es1370_dac1_calc_freq}, + + {CTRL_DAC2_EN, STAT_DAC2, SCTRL_P2PAUSE, SCTRL_P2INTEN, + SCTRL_P2FMT, SCTRL_SH_P2FMT, SCTRL_P2LOOPSEL, + es1370_dac2_and_adc_calc_freq}, + + {CTRL_ADC_EN, STAT_ADC, 0, SCTRL_R1INTEN, + SCTRL_R1FMT, SCTRL_SH_R1FMT, SCTRL_R1LOOPSEL, + es1370_dac2_and_adc_calc_freq} +}; + +static void es1370_update_status (ES1370State *s, uint32_t new_status) +{ + uint32_t level = new_status & (STAT_DAC1 | STAT_DAC2 | STAT_ADC); + + if (level) { + s->status = new_status | STAT_INTR; + } + else { + s->status = new_status & ~STAT_INTR; + } + pci_set_irq(&s->dev, !!level); +} + +static void es1370_reset (ES1370State *s) +{ + size_t i; + + s->ctl = 1; + s->status = 0x60; + s->mempage = 0; + s->codec = 0; + s->sctl = 0; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + d->scount = 0; + d->leftover = 0; + if (i == ADC_CHANNEL) { + AUD_close_in (&s->card, s->adc_voice); + s->adc_voice = NULL; + } + else { + AUD_close_out (&s->card, s->dac_voice[i]); + s->dac_voice[i] = NULL; + } + } + pci_irq_deassert(&s->dev); +} + +static void es1370_maybe_lower_irq (ES1370State *s, uint32_t sctl) +{ + uint32_t new_status = s->status; + + if (!(sctl & SCTRL_P1INTEN) && (s->sctl & SCTRL_P1INTEN)) { + new_status &= ~STAT_DAC1; + } + + if (!(sctl & SCTRL_P2INTEN) && (s->sctl & SCTRL_P2INTEN)) { + new_status &= ~STAT_DAC2; + } + + if (!(sctl & SCTRL_R1INTEN) && (s->sctl & SCTRL_R1INTEN)) { + new_status &= ~STAT_ADC; + } + + if (new_status != s->status) { + es1370_update_status (s, new_status); + } +} + +static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq) + +{ + *old_freq = dac1_samplerate[(s->ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL]; + *new_freq = dac1_samplerate[(ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL]; +} + +static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, + uint32_t *new_freq) + +{ + uint32_t old_pclkdiv, new_pclkdiv; + + new_pclkdiv = (ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV; + old_pclkdiv = (s->ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV; + *new_freq = DAC2_DIVTOSR (new_pclkdiv); + *old_freq = DAC2_DIVTOSR (old_pclkdiv); +} + +static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) +{ + size_t i; + uint32_t old_freq, new_freq, old_fmt, new_fmt; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + const struct chan_bits *b = &es1370_chan_bits[i]; + + new_fmt = (sctl & b->sctl_fmt) >> b->sctl_sh_fmt; + old_fmt = (s->sctl & b->sctl_fmt) >> b->sctl_sh_fmt; + + b->calc_freq (s, ctl, &old_freq, &new_freq); + + if ((old_fmt != new_fmt) || (old_freq != new_freq)) { + d->shift = (new_fmt & 1) + (new_fmt >> 1); + ldebug ("channel %zu, freq = %d, nchannels %d, fmt %d, shift %d\n", + i, + new_freq, + 1 << (new_fmt & 1), + (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8, + d->shift); + if (new_freq) { + struct audsettings as; + + as.freq = new_freq; + as.nchannels = 1 << (new_fmt & 1); + as.fmt = (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8; + as.endianness = 0; + + if (i == ADC_CHANNEL) { + s->adc_voice = + AUD_open_in ( + &s->card, + s->adc_voice, + "es1370.adc", + s, + es1370_adc_callback, + &as + ); + } + else { + s->dac_voice[i] = + AUD_open_out ( + &s->card, + s->dac_voice[i], + i ? "es1370.dac2" : "es1370.dac1", + s, + i ? es1370_dac2_callback : es1370_dac1_callback, + &as + ); + } + } + } + + if (((ctl ^ s->ctl) & b->ctl_en) + || ((sctl ^ s->sctl) & b->sctl_pause)) { + int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause); + + if (i == ADC_CHANNEL) { + AUD_set_active_in (s->adc_voice, on); + } + else { + AUD_set_active_out (s->dac_voice[i], on); + } + } + } + + s->ctl = ctl; + s->sctl = sctl; +} + +static inline uint32_t es1370_fixup (ES1370State *s, uint32_t addr) +{ + addr &= 0xff; + if (addr >= 0x30 && addr <= 0x3f) + addr |= s->mempage << 8; + return addr; +} + +IO_WRITE_PROTO (es1370_writeb) +{ + ES1370State *s = opaque; + uint32_t shift, mask; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_CONTROL: + case ES1370_REG_CONTROL + 1: + case ES1370_REG_CONTROL + 2: + case ES1370_REG_CONTROL + 3: + shift = (addr - ES1370_REG_CONTROL) << 3; + mask = 0xff << shift; + val = (s->ctl & ~mask) | ((val & 0xff) << shift); + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + case ES1370_REG_MEMPAGE: + s->mempage = val; + break; + case ES1370_REG_SERIAL_CONTROL: + case ES1370_REG_SERIAL_CONTROL + 1: + case ES1370_REG_SERIAL_CONTROL + 2: + case ES1370_REG_SERIAL_CONTROL + 3: + shift = (addr - ES1370_REG_SERIAL_CONTROL) << 3; + mask = 0xff << shift; + val = (s->sctl & ~mask) | ((val & 0xff) << shift); + es1370_maybe_lower_irq (s, val); + es1370_update_voices (s, s->ctl, val); + print_sctl (val); + break; + default: + lwarn ("writeb %#x <- %#x\n", addr, val); + break; + } +} + +IO_WRITE_PROTO (es1370_writew) +{ + ES1370State *s = opaque; + addr = es1370_fixup (s, addr); + uint32_t shift, mask; + struct chan *d = &s->chan[0]; + + switch (addr) { + case ES1370_REG_CODEC: + dolog ("ignored codec write address %#x, data %#x\n", + (val >> 8) & 0xff, val & 0xff); + s->codec = val; + break; + + case ES1370_REG_CONTROL: + case ES1370_REG_CONTROL + 2: + shift = (addr != ES1370_REG_CONTROL) << 4; + mask = 0xffff << shift; + val = (s->ctl & ~mask) | ((val & 0xffff) << shift); + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + d->scount = (d->scount & ~0xffff) | (val & 0xffff); + break; + + default: + lwarn ("writew %#x <- %#x\n", addr, val); + break; + } +} + +IO_WRITE_PROTO (es1370_writel) +{ + ES1370State *s = opaque; + struct chan *d = &s->chan[0]; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_CONTROL: + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + + case ES1370_REG_MEMPAGE: + s->mempage = val & 0xf; + break; + + case ES1370_REG_SERIAL_CONTROL: + es1370_maybe_lower_irq (s, val); + es1370_update_voices (s, s->ctl, val); + print_sctl (val); + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + d->scount = (val & 0xffff) | (d->scount & ~0xffff); + ldebug ("chan %td CURR_SAMP_CT %d, SAMP_CT %d\n", + d - &s->chan[0], val >> 16, (val & 0xffff)); + break; + + case ES1370_REG_ADC_FRAMEADR: + d++; + case ES1370_REG_DAC2_FRAMEADR: + d++; + case ES1370_REG_DAC1_FRAMEADR: + d->frame_addr = val; + ldebug ("chan %td frame address %#x\n", d - &s->chan[0], val); + break; + + case ES1370_REG_PHANTOM_FRAMECNT: + lwarn ("writing to phantom frame count %#x\n", val); + break; + case ES1370_REG_PHANTOM_FRAMEADR: + lwarn ("writing to phantom frame address %#x\n", val); + break; + + case ES1370_REG_ADC_FRAMECNT: + d++; + case ES1370_REG_DAC2_FRAMECNT: + d++; + case ES1370_REG_DAC1_FRAMECNT: + d->frame_cnt = val; + d->leftover = 0; + ldebug ("chan %td frame count %d, buffer size %d\n", + d - &s->chan[0], val >> 16, val & 0xffff); + break; + + default: + lwarn ("writel %#x <- %#x\n", addr, val); + break; + } +} + +IO_READ_PROTO (es1370_readb) +{ + ES1370State *s = opaque; + uint32_t val; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case 0x1b: /* Legacy */ + lwarn ("Attempt to read from legacy register\n"); + val = 5; + break; + case ES1370_REG_MEMPAGE: + val = s->mempage; + break; + case ES1370_REG_CONTROL + 0: + case ES1370_REG_CONTROL + 1: + case ES1370_REG_CONTROL + 2: + case ES1370_REG_CONTROL + 3: + val = s->ctl >> ((addr - ES1370_REG_CONTROL) << 3); + break; + case ES1370_REG_STATUS + 0: + case ES1370_REG_STATUS + 1: + case ES1370_REG_STATUS + 2: + case ES1370_REG_STATUS + 3: + val = s->status >> ((addr - ES1370_REG_STATUS) << 3); + break; + default: + val = ~0; + lwarn ("readb %#x -> %#x\n", addr, val); + break; + } + return val; +} + +IO_READ_PROTO (es1370_readw) +{ + ES1370State *s = opaque; + struct chan *d = &s->chan[0]; + uint32_t val; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_ADC_SCOUNT + 2: + d++; + case ES1370_REG_DAC2_SCOUNT + 2: + d++; + case ES1370_REG_DAC1_SCOUNT + 2: + val = d->scount >> 16; + break; + + case ES1370_REG_ADC_FRAMECNT: + d++; + case ES1370_REG_DAC2_FRAMECNT: + d++; + case ES1370_REG_DAC1_FRAMECNT: + val = d->frame_cnt & 0xffff; + break; + + case ES1370_REG_ADC_FRAMECNT + 2: + d++; + case ES1370_REG_DAC2_FRAMECNT + 2: + d++; + case ES1370_REG_DAC1_FRAMECNT + 2: + val = d->frame_cnt >> 16; + break; + + default: + val = ~0; + lwarn ("readw %#x -> %#x\n", addr, val); + break; + } + + return val; +} + +IO_READ_PROTO (es1370_readl) +{ + ES1370State *s = opaque; + uint32_t val; + struct chan *d = &s->chan[0]; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_CONTROL: + val = s->ctl; + break; + case ES1370_REG_STATUS: + val = s->status; + break; + case ES1370_REG_MEMPAGE: + val = s->mempage; + break; + case ES1370_REG_CODEC: + val = s->codec; + break; + case ES1370_REG_SERIAL_CONTROL: + val = s->sctl; + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + val = d->scount; +#ifdef DEBUG_ES1370 + { + uint32_t curr_count = d->scount >> 16; + uint32_t count = d->scount & 0xffff; + + curr_count <<= d->shift; + count <<= d->shift; + dolog ("read scount curr %d, total %d\n", curr_count, count); + } +#endif + break; + + case ES1370_REG_ADC_FRAMECNT: + d++; + case ES1370_REG_DAC2_FRAMECNT: + d++; + case ES1370_REG_DAC1_FRAMECNT: + val = d->frame_cnt; +#ifdef DEBUG_ES1370 + { + uint32_t size = ((d->frame_cnt & 0xffff) + 1) << 2; + uint32_t curr = ((d->frame_cnt >> 16) + 1) << 2; + if (curr > size) { + dolog ("read framecnt curr %d, size %d %d\n", curr, size, + curr > size); + } + } +#endif + break; + + case ES1370_REG_ADC_FRAMEADR: + d++; + case ES1370_REG_DAC2_FRAMEADR: + d++; + case ES1370_REG_DAC1_FRAMEADR: + val = d->frame_addr; + break; + + case ES1370_REG_PHANTOM_FRAMECNT: + val = ~0U; + lwarn ("reading from phantom frame count\n"); + break; + case ES1370_REG_PHANTOM_FRAMEADR: + val = ~0U; + lwarn ("reading from phantom frame address\n"); + break; + + default: + val = ~0U; + lwarn ("readl %#x -> %#x\n", addr, val); + break; + } + return val; +} + +static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel, + int max, int *irq) +{ + uint8_t tmpbuf[4096]; + uint32_t addr = d->frame_addr; + int sc = d->scount & 0xffff; + int csc = d->scount >> 16; + int csc_bytes = (csc + 1) << d->shift; + int cnt = d->frame_cnt >> 16; + int size = d->frame_cnt & 0xffff; + int left = ((size - cnt + 1) << 2) + d->leftover; + int transferred = 0; + int temp = audio_MIN (max, audio_MIN (left, csc_bytes)); + int index = d - &s->chan[0]; + + addr += (cnt << 2) + d->leftover; + + if (index == ADC_CHANNEL) { + while (temp) { + int acquired, to_copy; + + to_copy = audio_MIN ((size_t) temp, sizeof (tmpbuf)); + acquired = AUD_read (s->adc_voice, tmpbuf, to_copy); + if (!acquired) + break; + + pci_dma_write (&s->dev, addr, tmpbuf, acquired); + + temp -= acquired; + addr += acquired; + transferred += acquired; + } + } + else { + SWVoiceOut *voice = s->dac_voice[index]; + + while (temp) { + int copied, to_copy; + + to_copy = audio_MIN ((size_t) temp, sizeof (tmpbuf)); + pci_dma_read (&s->dev, addr, tmpbuf, to_copy); + copied = AUD_write (voice, tmpbuf, to_copy); + if (!copied) + break; + temp -= copied; + addr += copied; + transferred += copied; + } + } + + if (csc_bytes == transferred) { + *irq = 1; + d->scount = sc | (sc << 16); + ldebug ("sc = %d, rate = %f\n", + (sc + 1) << d->shift, + (sc + 1) / (double) 44100); + } + else { + *irq = 0; + d->scount = sc | (((csc_bytes - transferred - 1) >> d->shift) << 16); + } + + cnt += (transferred + d->leftover) >> 2; + + if (s->sctl & loop_sel) { + /* Bah, how stupid is that having a 0 represent true value? + i just spent few hours on this shit */ + AUD_log ("es1370: warning", "non looping mode\n"); + } + else { + d->frame_cnt = size; + + if ((uint32_t) cnt <= d->frame_cnt) + d->frame_cnt |= cnt << 16; + } + + d->leftover = (transferred + d->leftover) & 3; +} + +static void es1370_run_channel (ES1370State *s, size_t chan, int free_or_avail) +{ + uint32_t new_status = s->status; + int max_bytes, irq; + struct chan *d = &s->chan[chan]; + const struct chan_bits *b = &es1370_chan_bits[chan]; + + if (!(s->ctl & b->ctl_en) || (s->sctl & b->sctl_pause)) { + return; + } + + max_bytes = free_or_avail; + max_bytes &= ~((1 << d->shift) - 1); + if (!max_bytes) { + return; + } + + es1370_transfer_audio (s, d, b->sctl_loopsel, max_bytes, &irq); + + if (irq) { + if (s->sctl & b->sctl_inten) { + new_status |= b->stat_int; + } + } + + if (new_status != s->status) { + es1370_update_status (s, new_status); + } +} + +static void es1370_dac1_callback (void *opaque, int free) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, DAC1_CHANNEL, free); +} + +static void es1370_dac2_callback (void *opaque, int free) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, DAC2_CHANNEL, free); +} + +static void es1370_adc_callback (void *opaque, int avail) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, ADC_CHANNEL, avail); +} + +static uint64_t es1370_read(void *opaque, hwaddr addr, + unsigned size) +{ + switch (size) { + case 1: + return es1370_readb(opaque, addr); + case 2: + return es1370_readw(opaque, addr); + case 4: + return es1370_readl(opaque, addr); + default: + return -1; + } +} + +static void es1370_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + switch (size) { + case 1: + es1370_writeb(opaque, addr, val); + break; + case 2: + es1370_writew(opaque, addr, val); + break; + case 4: + es1370_writel(opaque, addr, val); + break; + } +} + +static const MemoryRegionOps es1370_io_ops = { + .read = es1370_read, + .write = es1370_write, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const VMStateDescription vmstate_es1370_channel = { + .name = "es1370_channel", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32 (shift, struct chan), + VMSTATE_UINT32 (leftover, struct chan), + VMSTATE_UINT32 (scount, struct chan), + VMSTATE_UINT32 (frame_addr, struct chan), + VMSTATE_UINT32 (frame_cnt, struct chan), + VMSTATE_END_OF_LIST () + } +}; + +static int es1370_post_load (void *opaque, int version_id) +{ + uint32_t ctl, sctl; + ES1370State *s = opaque; + size_t i; + + for (i = 0; i < NB_CHANNELS; ++i) { + if (i == ADC_CHANNEL) { + if (s->adc_voice) { + AUD_close_in (&s->card, s->adc_voice); + s->adc_voice = NULL; + } + } + else { + if (s->dac_voice[i]) { + AUD_close_out (&s->card, s->dac_voice[i]); + s->dac_voice[i] = NULL; + } + } + } + + ctl = s->ctl; + sctl = s->sctl; + s->ctl = 0; + s->sctl = 0; + es1370_update_voices (s, ctl, sctl); + return 0; +} + +static const VMStateDescription vmstate_es1370 = { + .name = "es1370", + .version_id = 2, + .minimum_version_id = 2, + .post_load = es1370_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE (dev, ES1370State), + VMSTATE_STRUCT_ARRAY (chan, ES1370State, NB_CHANNELS, 2, + vmstate_es1370_channel, struct chan), + VMSTATE_UINT32 (ctl, ES1370State), + VMSTATE_UINT32 (status, ES1370State), + VMSTATE_UINT32 (mempage, ES1370State), + VMSTATE_UINT32 (codec, ES1370State), + VMSTATE_UINT32 (sctl, ES1370State), + VMSTATE_END_OF_LIST () + } +}; + +static void es1370_on_reset (void *opaque) +{ + ES1370State *s = opaque; + es1370_reset (s); +} + +static void es1370_realize(PCIDevice *dev, Error **errp) +{ + ES1370State *s = DO_UPCAST (ES1370State, dev, dev); + uint8_t *c = s->dev.config; + + c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_SLOW >> 8; + +#if 0 + c[PCI_CAPABILITY_LIST] = 0xdc; + c[PCI_INTERRUPT_LINE] = 10; + c[0xdc] = 0x00; +#endif + + c[PCI_INTERRUPT_PIN] = 1; + c[PCI_MIN_GNT] = 0x0c; + c[PCI_MAX_LAT] = 0x80; + + memory_region_init_io (&s->io, OBJECT(s), &es1370_io_ops, s, "es1370", 256); + pci_register_bar (&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io); + qemu_register_reset (es1370_on_reset, s); + + AUD_register_card ("es1370", &s->card); + es1370_reset (s); +} + +static int es1370_init (PCIBus *bus) +{ + pci_create_simple (bus, -1, "ES1370"); + return 0; +} + +static void es1370_class_init (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS (klass); + + k->realize = es1370_realize; + k->vendor_id = PCI_VENDOR_ID_ENSONIQ; + k->device_id = PCI_DEVICE_ID_ENSONIQ_ES1370; + k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO; + k->subsystem_vendor_id = 0x4942; + k->subsystem_id = 0x4c4c; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "ENSONIQ AudioPCI ES1370"; + dc->vmsd = &vmstate_es1370; +} + +static const TypeInfo es1370_info = { + .name = "ES1370", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof (ES1370State), + .class_init = es1370_class_init, +}; + +static void es1370_register_types (void) +{ + type_register_static (&es1370_info); + pci_register_soundhw("es1370", "ENSONIQ AudioPCI ES1370", es1370_init); +} + +type_init (es1370_register_types) + diff --git a/qemu/hw/audio/fmopl.c b/qemu/hw/audio/fmopl.c new file mode 100644 index 000000000..adcef2d3b --- /dev/null +++ b/qemu/hw/audio/fmopl.c @@ -0,0 +1,1394 @@ +/* +** +** File: fmopl.c -- software implementation of FM sound generator +** +** Copyright (C) 1999,2000 Tatsuyuki Satoh , MultiArcadeMachineEmurator development +** +** Version 0.37a +** +*/ + +/* + preliminary : + Problem : + note: +*/ + +/* This version of fmopl.c is a fork of the MAME one, relicensed under the LGPL. + * + * 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.1 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 . + */ + +#define HAS_YM3812 1 + +#include +#include +#include +#include +#include +//#include "driver.h" /* use M.A.M.E. */ +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +/* -------------------- for debug --------------------- */ +/* #define OPL_OUTPUT_LOG */ +#ifdef OPL_OUTPUT_LOG +static FILE *opl_dbg_fp = NULL; +static FM_OPL *opl_dbg_opl[16]; +static int opl_dbg_maxchip,opl_dbg_chip; +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define DELTAT_MIXING_LEVEL (1) /* DELTA-T ADPCM MIXING LEVEL */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<=LOG_LEVEL ) logerror x +#define LOG(n,x) + +/* --------------------- subroutines --------------------- */ + +static inline int Limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + +/* status set and IRQ handling */ +static inline void OPL_STATUS_SET(FM_OPL *OPL,int flag) +{ + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) + { + if(OPL->status & OPL->statusmask) + { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +static inline void OPL_STATUS_RESET(FM_OPL *OPL,int flag) +{ + /* reset status flag */ + OPL->status &=~flag; + if((OPL->status & 0x80)) + { + if (!(OPL->status & OPL->statusmask) ) + { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +static inline void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +static inline void OPL_KEYON(OPL_SLOT *SLOT) +{ + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} +/* ----- key off ----- */ +static inline void OPL_KEYOFF(OPL_SLOT *SLOT) +{ + if( SLOT->evm > ENV_MOD_RR) + { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if( !(SLOT->evc&EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +static inline UINT32 OPL_CALC_SLOT( OPL_SLOT *SLOT ) +{ + /* calcrate envelope generator */ + if( (SLOT->evc+=SLOT->evs) >= SLOT->eve ) + { + switch( SLOT->evm ){ + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) + { + SLOT->evs = 0; + } + else + { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF+1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL+ENV_CURVE[SLOT->evc>>ENV_BITS]+(SLOT->ams ? ams : 0); +} + +/* set algorithm connection */ +static void set_algorithm( OPL_CH *CH) +{ + INT32 *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +static inline void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) +{ + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +static inline void set_mul(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = MUL_TABLE[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_typ = (v&0x20)>>5; + SLOT->vib = (v&0x40); + SLOT->ams = (v&0x80); + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl & tl */ +static inline void set_ksl_tl(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ksl = v>>6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)*(0.75/EG_STEP); /* 0.75db step */ + + if( !(OPL->mode&0x80) ) + { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +static inline void set_ar_dr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ar = v>>4; + int dr = v&0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar<<2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_AR ) SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +static inline void set_sl_rr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int sl = v>>4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_RR ) SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt+con)/(0x1000000/SIN_ENT))&(SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +static inline void OPL_CALC_CH( OPL_CH *CH ) +{ + UINT32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH->FB) + { + int feedback1 = (CH->op1_out[0]+CH->op1_out[1])>>CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + *CH->connect1 += OP_OUT(SLOT,env_out,0); + } + }else + { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2); + } +} + +/* ---------- calcrate rhythm block ---------- */ +#define WHITE_NOISE_db 6.0 +static inline void OPL_CALC_RH( OPL_CH *CH ) +{ + UINT32 env_tam,env_sd,env_top,env_hh; + int whitenoise = (rand()&1)*(WHITE_NOISE_db/EG_STEP); + INT32 tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH[6].FB) + { + int feedback1 = (CH[6].op1_out[0]+CH[6].op1_out[1])>>CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + feedback2 = OP_OUT(SLOT,env_out,0); + } + }else + { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2)*2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd =OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam=OPL_CALC_SLOT(SLOT8_1); + env_top=OPL_CALC_SLOT(SLOT8_2); + env_hh =OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) SLOT7_1->Cnt += (2*SLOT7_1->Incr*vib/VIB_RATE); + else SLOT7_1->Cnt += 2*SLOT7_1->Incr; + if(SLOT7_2->vib) SLOT7_2->Cnt += ((CH[7].fc*8)*vib/VIB_RATE); + else SLOT7_2->Cnt += (CH[7].fc*8); + if(SLOT8_1->vib) SLOT8_1->Cnt += (SLOT8_1->Incr*vib/VIB_RATE); + else SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) SLOT8_2->Cnt += ((CH[8].fc*48)*vib/VIB_RATE); + else SLOT8_2->Cnt += (CH[8].fc*48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if( env_sd < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_1,env_sd, 0)*8; + /* TAM */ + if( env_tam < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT8_1,env_tam, 0)*2; + /* TOP-CY */ + if( env_top < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_top,tone8)*2; + /* HH */ + if( env_hh < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_hh,tone8)*2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables( FM_OPL *OPL , int ARRATE , int DRRATE ) +{ + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0;i < 4;i++) OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4;i <= 60;i++){ + rate = OPL->freqbase; /* frequency rate */ + if( i < 60 ) rate *= 1.0+(i&3)*0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1<<((i>>2)-1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT<AR_TABLE[i] = rate / ARRATE; + OPL->DR_TABLE[i] = rate / DRRATE; + } + for (i = 60; i < ARRAY_SIZE(OPL->AR_TABLE); i++) + { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +#if 0 + for (i = 0;i < 64 ;i++){ /* make for overflow area */ + LOG(LOG_WAR, ("rate %2d , ar %f ms , dr %f ms\n", i, + ((double)(EG_ENT<AR_TABLE[i]) * (1000.0 / OPL->rate), + ((double)(EG_ENT<DR_TABLE[i]) * (1000.0 / OPL->rate) )); + } +#endif +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable( void ) +{ + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if( (TL_TABLE = malloc(TL_MAX*2*sizeof(INT32))) == NULL) + return 0; + if( (SIN_TABLE = malloc(SIN_ENT*4 *sizeof(INT32 *))) == NULL) + { + free(TL_TABLE); + return 0; + } + if( (AMS_TABLE = malloc(AMS_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + if( (VIB_TABLE = malloc(VIB_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0;t < EG_ENT-1 ;t++){ + rate = ((1< voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX+t] = -TL_TABLE[t]; +/* LOG(LOG_INF,("TotalLevel(%3d) = %x\n",t,TL_TABLE[t]));*/ + } + /* fill volume off area */ + for ( t = EG_ENT-1; t < TL_MAX ;t++){ + TL_TABLE[t] = TL_TABLE[TL_MAX+t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT/2] = &TL_TABLE[EG_ENT-1]; + for (s = 1;s <= SIN_ENT/4;s++){ + pom = sin(2*PI*s/SIN_ENT); /* sin */ + pom = 20*log10(1/pom); /* decibel */ + j = pom / EG_STEP; /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT/2-s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT/2+s] = SIN_TABLE[SIN_ENT -s] = &TL_TABLE[TL_MAX+j]; +/* LOG(LOG_INF,("sin(%3d) = %f:%f db\n",s,pom,(double)j * EG_STEP));*/ + } + for (s = 0;s < SIN_ENT;s++) + { + SIN_TABLE[SIN_ENT*1+s] = s<(SIN_ENT/2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT*2+s] = SIN_TABLE[s % (SIN_ENT/2)]; + SIN_TABLE[SIN_ENT*3+s] = (s/(SIN_ENT/4))&1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT*2+s]; + } + + /* envelope counter -> envelope output table */ + for (i=0; i= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST>>ENV_BITS)+i]= i; + } + /* off */ + ENV_CURVE[EG_OFF>>ENV_BITS]= EG_ENT-1; + /* make LFO ams table */ + for (i=0; iSLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initialize(FM_OPL *OPL) +{ + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables( OPL , OPL_ARRATE , OPL_DRRATE ); + /* make fnumber -> increment counter table */ + for( fn=0 ; fn < 1024 ; fn++ ) + { + OPL->FN_TABLE[fn] = OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2; + } + /* LFO freq.table */ + OPL->amsIncr = OPL->rate ? (double)AMS_ENT*(1<rate * 3.7 * ((double)OPL->clock/3600000) : 0; + OPL->vibIncr = OPL->rate ? (double)VIB_ENT*(1<rate * 6.4 * ((double)OPL->clock/3600000) : 0; +} + +/* ---------- write a OPL registers ---------- */ +static void OPLWriteReg(FM_OPL *OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + int block_fnum; + + switch(r&0xe0) + { + case 0x00: /* 00-1f:control */ + switch(r&0x1f) + { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) + { + OPL->wavesel = v&0x20; + if(!OPL->wavesel) + { + /* preset compatible mode */ + int c; + for(c=0;cmax_ch;c++) + { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v)*4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v)*16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v&0x80) + { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL,0x7f); + } + else + { /* set IRQ mask ,timer enable*/ + UINT8 st1 = v&1; + UINT8 st2 = (v>>1)&1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL,v&0x78); + OPL_STATUSMASK_SET(OPL,((~v)&0x78)|0x01); + /* timer 2 */ + if(OPL->st[1] != st2) + { + double interval = st2 ? (double)OPL->T[1]*OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+1,interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) + { + double interval = st1 ? (double)OPL->T[0]*OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+0,interval); + } + } + return; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_w) + OPL->keyboardhandler_w(OPL->keyboard_param,v); + else + LOG(LOG_WAR,("OPL:write unmapped KEYBOARD port\n")); + } + return; + case 0x07: /* DELTA-T control : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; + case 0x08: /* MODE,DELTA-T : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; + v&=0x1f; /* for DELTA-T unit */ + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* EG-CTRL */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; +#if 0 + case 0x15: /* DAC data */ + case 0x16: + case 0x17: /* SHIFT */ + return; + case 0x18: /* I/O CTRL (Direction) */ + if(OPL->type&OPL_TYPE_IO) + OPL->portDirection = v&0x0f; + return; + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + OPL->portLatch = v; + if(OPL->porthandler_w) + OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); + } + return; + case 0x1a: /* PCM data */ + return; +#endif +#endif + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) + { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + UINT8 rkey = OPL->rhythm^v; + OPL->ams_table = &AMS_TABLE[v&0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v&0x40 ? VIB_ENT : 0]; + OPL->rhythm = v&0x3f; + if(OPL->rhythm&0x20) + { +#if 0 + usrintf_showmessage("OPL Rhythm mode select"); +#endif + /* BD key on/off */ + if(rkey&0x10) + { + if(v&0x10) + { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey&0x08) + { + if(v&0x08) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey&0x04) + { + if(v&0x04) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey&0x02) + { + if(v&0x02) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey&0x01) + { + if(v&0x01) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + if(!(r&0x10)) + { /* a0-a8 */ + block_fnum = (CH->block_fnum&0x1f00) | v; + } + else + { /* b0-b8 */ + int keyon = (v>>5)&1; + block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); + if(CH->keyon != keyon) + { + if( (CH->keyon=keyon) ) + { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) + { + int blockRv = 7-(block_fnum>>10); + int fnum = block_fnum&0x3ff; + CH->block_fnum = block_fnum; + + CH->ksl_base = KSL_TABLE[block_fnum>>6]; + CH->fc = OPL->FN_TABLE[fnum]>>blockRv; + CH->kcode = CH->block_fnum>>9; + if( (OPL->mode&0x40) && CH->block_fnum&0x100) CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v>>1)&7; + CH->FB = feedback ? (8+1) - feedback : 0; + CH->CON = v&1; + set_algorithm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + CH = &OPL->P_CH[slot/2]; + if(OPL->wavesel) + { + /* LOG(LOG_INF,("OPL SLOT %d wave select %d\n",slot,v&3)); */ + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v&0x03)*SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) +{ + num_lock++; + if(num_lock>1) return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if( !OPLOpenTable() ) + { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) +{ + if(num_lock) num_lock--; + if(num_lock) return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +#if (BUILD_YM3812 || BUILD_YM3526) +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length) +{ + int i; + int data; + OPLSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rhythm = OPL->rhythm&0x20; + OPL_CH *CH,*R_CH; + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rhythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rhythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rhythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chipamsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rhythm = OPL->rhythm&0x20; + OPL_CH *CH,*R_CH; + YM_DELTAT *DELTAT = OPL->deltat; + + /* setup DELTA-T unit */ + YM_DELTAT_DECODE_PRESET(DELTAT); + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rhythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rhythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* deltaT ADPCM */ + if( DELTAT->portstate ) + YM_DELTAT_ADPCM_CALC(DELTAT); + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rhythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; + /* deltaT START flag */ + if( !DELTAT->portstate ) + OPL->status &= 0xfe; +} +#endif + +/* ---------- reset one of chip ---------- */ +void OPLResetChip(FM_OPL *OPL) +{ + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL,0x7f); + /* reset with register write */ + OPLWriteReg(OPL,0x01,0); /* wabesel disable */ + OPLWriteReg(OPL,0x02,0); /* Timer1 */ + OPLWriteReg(OPL,0x03,0); /* Timer2 */ + OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ + for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); + /* reset OPerator paramater */ + for( c = 0 ; c < OPL->max_ch ; c++ ) + { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF+1; + CH->SLOT[s].evs = 0; + } + } +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = outd; + DELTAT->portshift = 5; + DELTAT->output_range = DELTAT_MIXING_LEVEL<P_CH = (OPL_CH *)ptr; ptr+=sizeof(OPL_CH)*max_ch; +#if BUILD_Y8950 + if(type&OPL_TYPE_ADPCM) OPL->deltat = (YM_DELTAT *)ptr; ptr+=sizeof(YM_DELTAT); +#endif + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + /* init grobal tables */ + OPL_initialize(OPL); + /* reset chip */ + OPLResetChip(OPL); +#ifdef OPL_OUTPUT_LOG + if(!opl_dbg_fp) + { + opl_dbg_fp = fopen("opllog.opl","wb"); + opl_dbg_maxchip = 0; + } + if(opl_dbg_fp) + { + opl_dbg_opl[opl_dbg_maxchip] = OPL; + fprintf(opl_dbg_fp,"%c%c%c%c%c%c",0x00+opl_dbg_maxchip, + type, + clock&0xff, + (clock/0x100)&0xff, + (clock/0x10000)&0xff, + (clock/0x1000000)&0xff); + opl_dbg_maxchip++; + } +#endif + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) +{ +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + fclose(opl_dbg_fp); + opl_dbg_fp = NULL; + } +#endif + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ + +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset) +{ + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} +#if BUILD_Y8950 +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param) +{ + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param) +{ + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} +#endif +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + OPL->address = v & 0xff; + } + else + { /* data port */ + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chipaddress,v); + } +#endif + OPLWriteReg(OPL,OPL->address,v); + } + return OPL->status>>7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) +{ + if( !(a&1) ) + { /* status port */ + return OPL->status & (OPL->statusmask|0x80); + } + /* data port */ + switch(OPL->address) + { + case 0x05: /* KeyBoard IN */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_r) + return OPL->keyboardhandler_r(OPL->keyboard_param); + else { + LOG(LOG_WAR,("OPL:read unmapped KEYBOARD port\n")); + } + } + return 0; +#if 0 + case 0x0f: /* ADPCM-DATA */ + return 0; +#endif + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + if(OPL->porthandler_r) + return OPL->porthandler_r(OPL->port_param); + else { + LOG(LOG_WAR,("OPL:read unmapped I/O port\n")); + } + } + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL,int c) +{ + if( c ) + { /* Timer B */ + OPL_STATUS_SET(OPL,0x20); + } + else + { /* Timer A */ + OPL_STATUS_SET(OPL,0x40); + /* CSM mode key,TL control */ + if( OPL->mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch=0;ch<9;ch++) + CSMKeyControll( &OPL->P_CH[ch] ); + } + } + /* reload timer */ + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+c,(double)OPL->T[c]*OPL->TimerBase); + return OPL->status>>7; +} diff --git a/qemu/hw/audio/fmopl.h b/qemu/hw/audio/fmopl.h new file mode 100644 index 000000000..24ba5f480 --- /dev/null +++ b/qemu/hw/audio/fmopl.h @@ -0,0 +1,174 @@ +#ifndef __FMOPL_H_ +#define __FMOPL_H_ + +/* --- select emulation chips --- */ +#define BUILD_YM3812 (HAS_YM3812) +//#define BUILD_YM3526 (HAS_YM3526) +//#define BUILD_Y8950 (HAS_Y8950) + +/* --- system optimize --- */ +/* select bit size of output : 8 or 16 */ +#define OPL_OUTPUT_BIT 16 + +/* compiler dependence */ +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif + +#if (OPL_OUTPUT_BIT==16) +typedef INT16 OPLSAMPLE; +#endif +#if (OPL_OUTPUT_BIT==8) +typedef unsigned char OPLSAMPLE; +#endif + + +#if BUILD_Y8950 +#include "ymdeltat.h" +#endif + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); +typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); +typedef unsigned char (*OPL_PORTHANDLER_R)(int param); + +/* !!!!! here is private section , do not access there member direct !!!!! */ + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + INT32 TL; /* total level :TL << 8 */ + INT32 TLL; /* adjusted now TL */ + UINT8 KSR; /* key scale rate :(shift down bit) */ + INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */ + INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */ + INT32 SL; /* sustin level :SL_TALBE[SL] */ + INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */ + UINT8 ksl; /* keyscale level :(shift down bits) */ + UINT8 ksr; /* key scale rate :kcode>>KSR */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + UINT32 Cnt; /* frequency count : */ + UINT32 Incr; /* frequency step : */ + /* envelope generator state */ + UINT8 eg_typ; /* envelope type flag */ + UINT8 evm; /* envelope phase */ + INT32 evc; /* envelope counter */ + INT32 eve; /* envelope counter end point */ + INT32 evs; /* envelope counter step */ + INT32 evsa; /* envelope step for AR :AR[ksr] */ + INT32 evsd; /* envelope step for DR :DR[ksr] */ + INT32 evsr; /* envelope step for RR :RR[ksr] */ + /* LFO */ + UINT8 ams; /* ams flag */ + UINT8 vib; /* vibrate flag */ + /* wave selector */ + INT32 **wavetable; +}OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + UINT8 CON; /* connection type */ + UINT8 FB; /* feed back :(shift down bit) */ + INT32 *connect1; /* slot1 output pointer */ + INT32 *connect2; /* slot2 output pointer */ + INT32 op1_out[2]; /* slot1 output for selfeedback */ + /* phase generator state */ + UINT32 block_fnum; /* block+fnum : */ + UINT8 kcode; /* key code : KeyScaleCode */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + UINT8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT32 mode; /* Reg.08 : CSM , notesel,etc. */ + /* Timer */ + int T[2]; /* timer counter */ + UINT8 st[2]; /* timer enable */ + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + /* Rhythm sention */ + UINT8 rhythm; /* Rhythm mode , key flag */ +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + YM_DELTAT *deltat; /* DELTA-T ADPCM */ +#endif + /* Keyboard / I/O interface unit (Y8950) */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + int port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + int keyboard_param; + /* time tables */ + INT32 AR_TABLE[75]; /* atttack rate tables */ + INT32 DR_TABLE[75]; /* decay rate tables */ + UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */ + /* LFO */ + INT32 *ams_table; + INT32 *vib_table; + INT32 amsCnt; + INT32 amsIncr; + INT32 vibCnt; + INT32 vibIncr; + /* wave selector enable flag */ + UINT8 wavesel; + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param); +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param); +/* Y8950 port handlers */ +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param); +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL,int a,int v); +unsigned char OPLRead(FM_OPL *OPL,int a); +int OPLTimerOver(FM_OPL *OPL,int c); + +/* YM3626/YM3812 local section */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +#endif diff --git a/qemu/hw/audio/gus.c b/qemu/hw/audio/gus.c new file mode 100644 index 000000000..86223a954 --- /dev/null +++ b/qemu/hw/audio/gus.c @@ -0,0 +1,318 @@ +/* + * QEMU Proxy for Gravis Ultrasound GF1 emulation by Tibor "TS" Schütz + * + * Copyright (c) 2002-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/isa/isa.h" +#include "gusemu.h" +#include "gustate.h" + +#define dolog(...) AUD_log ("audio", __VA_ARGS__) +#ifdef DEBUG +#define ldebug(...) dolog (__VA_ARGS__) +#else +#define ldebug(...) +#endif + +#ifdef HOST_WORDS_BIGENDIAN +#define GUS_ENDIANNESS 1 +#else +#define GUS_ENDIANNESS 0 +#endif + +#define IO_READ_PROTO(name) \ + static uint32_t name (void *opaque, uint32_t nport) +#define IO_WRITE_PROTO(name) \ + static void name (void *opaque, uint32_t nport, uint32_t val) + +#define TYPE_GUS "gus" +#define GUS(obj) OBJECT_CHECK (GUSState, (obj), TYPE_GUS) + +typedef struct GUSState { + ISADevice dev; + GUSEmuState emu; + QEMUSoundCard card; + uint32_t freq; + uint32_t port; + int pos, left, shift, irqs; + GUSsample *mixbuf; + uint8_t himem[1024 * 1024 + 32 + 4096]; + int samples; + SWVoiceOut *voice; + int64_t last_ticks; + qemu_irq pic; +} GUSState; + +IO_READ_PROTO (gus_readb) +{ + GUSState *s = opaque; + + return gus_read (&s->emu, nport, 1); +} + +IO_WRITE_PROTO (gus_writeb) +{ + GUSState *s = opaque; + + gus_write (&s->emu, nport, 1, val); +} + +static int write_audio (GUSState *s, int samples) +{ + int net = 0; + int pos = s->pos; + + while (samples) { + int nbytes, wbytes, wsampl; + + nbytes = samples << s->shift; + wbytes = AUD_write ( + s->voice, + s->mixbuf + (pos << (s->shift - 1)), + nbytes + ); + + if (wbytes) { + wsampl = wbytes >> s->shift; + + samples -= wsampl; + pos = (pos + wsampl) % s->samples; + + net += wsampl; + } + else { + break; + } + } + + return net; +} + +static void GUS_callback (void *opaque, int free) +{ + int samples, to_play, net = 0; + GUSState *s = opaque; + + samples = free >> s->shift; + to_play = audio_MIN (samples, s->left); + + while (to_play) { + int written = write_audio (s, to_play); + + if (!written) { + goto reset; + } + + s->left -= written; + to_play -= written; + samples -= written; + net += written; + } + + samples = audio_MIN (samples, s->samples); + if (samples) { + gus_mixvoices (&s->emu, s->freq, samples, s->mixbuf); + + while (samples) { + int written = write_audio (s, samples); + if (!written) { + break; + } + samples -= written; + net += written; + } + } + s->left = samples; + + reset: + gus_irqgen (&s->emu, muldiv64 (net, 1000000, s->freq)); +} + +int GUS_irqrequest (GUSEmuState *emu, int hwirq, int n) +{ + GUSState *s = emu->opaque; + /* qemu_irq_lower (s->pic); */ + qemu_irq_raise (s->pic); + s->irqs += n; + ldebug ("irqrequest %d %d %d\n", hwirq, n, s->irqs); + return n; +} + +void GUS_irqclear (GUSEmuState *emu, int hwirq) +{ + GUSState *s = emu->opaque; + ldebug ("irqclear %d %d\n", hwirq, s->irqs); + qemu_irq_lower (s->pic); + s->irqs -= 1; +#ifdef IRQ_STORM + if (s->irqs > 0) { + qemu_irq_raise (s->pic[hwirq]); + } +#endif +} + +void GUS_dmarequest (GUSEmuState *der) +{ + /* GUSState *s = (GUSState *) der; */ + ldebug ("dma request %d\n", der->gusdma); + DMA_hold_DREQ (der->gusdma); +} + +static int GUS_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len) +{ + GUSState *s = opaque; + char tmpbuf[4096]; + int pos = dma_pos, mode, left = dma_len - dma_pos; + + ldebug ("read DMA %#x %d\n", dma_pos, dma_len); + mode = DMA_get_channel_mode (s->emu.gusdma); + while (left) { + int to_copy = audio_MIN ((size_t) left, sizeof (tmpbuf)); + int copied; + + ldebug ("left=%d to_copy=%d pos=%d\n", left, to_copy, pos); + copied = DMA_read_memory (nchan, tmpbuf, pos, to_copy); + gus_dma_transferdata (&s->emu, tmpbuf, copied, left == copied); + left -= copied; + pos += copied; + } + + if (((mode >> 4) & 1) == 0) { + DMA_release_DREQ (s->emu.gusdma); + } + return dma_len; +} + +static const VMStateDescription vmstate_gus = { + .name = "gus", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_INT32 (pos, GUSState), + VMSTATE_INT32 (left, GUSState), + VMSTATE_INT32 (shift, GUSState), + VMSTATE_INT32 (irqs, GUSState), + VMSTATE_INT32 (samples, GUSState), + VMSTATE_INT64 (last_ticks, GUSState), + VMSTATE_BUFFER (himem, GUSState), + VMSTATE_END_OF_LIST () + } +}; + +static const MemoryRegionPortio gus_portio_list1[] = { + {0x000, 1, 1, .write = gus_writeb }, + {0x006, 10, 1, .read = gus_readb, .write = gus_writeb }, + {0x100, 8, 1, .read = gus_readb, .write = gus_writeb }, + PORTIO_END_OF_LIST (), +}; + +static const MemoryRegionPortio gus_portio_list2[] = { + {0, 2, 1, .read = gus_readb }, + PORTIO_END_OF_LIST (), +}; + +static void gus_realizefn (DeviceState *dev, Error **errp) +{ + ISADevice *d = ISA_DEVICE(dev); + GUSState *s = GUS (dev); + struct audsettings as; + + AUD_register_card ("gus", &s->card); + + as.freq = s->freq; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = GUS_ENDIANNESS; + + s->voice = AUD_open_out ( + &s->card, + NULL, + "gus", + s, + GUS_callback, + &as + ); + + if (!s->voice) { + AUD_remove_card (&s->card); + error_setg(errp, "No voice"); + return; + } + + s->shift = 2; + s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift; + s->mixbuf = g_malloc0 (s->samples << s->shift); + + isa_register_portio_list (d, s->port, gus_portio_list1, s, "gus"); + isa_register_portio_list (d, (s->port + 0x100) & 0xf00, + gus_portio_list2, s, "gus"); + + DMA_register_channel (s->emu.gusdma, GUS_read_DMA, s); + s->emu.himemaddr = s->himem; + s->emu.gusdatapos = s->emu.himemaddr + 1024 * 1024 + 32; + s->emu.opaque = s; + isa_init_irq (d, &s->pic, s->emu.gusirq); + + AUD_set_active_out (s->voice, 1); +} + +static int GUS_init (ISABus *bus) +{ + isa_create_simple (bus, TYPE_GUS); + return 0; +} + +static Property gus_properties[] = { + DEFINE_PROP_UINT32 ("freq", GUSState, freq, 44100), + DEFINE_PROP_UINT32 ("iobase", GUSState, port, 0x240), + DEFINE_PROP_UINT32 ("irq", GUSState, emu.gusirq, 7), + DEFINE_PROP_UINT32 ("dma", GUSState, emu.gusdma, 3), + DEFINE_PROP_END_OF_LIST (), +}; + +static void gus_class_initfn (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + + dc->realize = gus_realizefn; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Gravis Ultrasound GF1"; + dc->vmsd = &vmstate_gus; + dc->props = gus_properties; +} + +static const TypeInfo gus_info = { + .name = TYPE_GUS, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof (GUSState), + .class_init = gus_class_initfn, +}; + +static void gus_register_types (void) +{ + type_register_static (&gus_info); + isa_register_soundhw("gus", "Gravis Ultrasound GF1", GUS_init); +} + +type_init (gus_register_types) diff --git a/qemu/hw/audio/gusemu.h b/qemu/hw/audio/gusemu.h new file mode 100644 index 000000000..331bb6fec --- /dev/null +++ b/qemu/hw/audio/gusemu.h @@ -0,0 +1,105 @@ +/* + * GUSEMU32 - API + * + * Copyright (C) 2000-2007 Tibor "TS" Schütz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUSEMU_H +#define GUSEMU_H + +/* data types (need to be adjusted if neither a VC6 nor a C99 compatible compiler is used) */ + +#if defined _WIN32 && defined _MSC_VER /* doesn't support other win32 compilers yet, do it yourself... */ + typedef unsigned char GUSbyte; + typedef unsigned short GUSword; + typedef unsigned int GUSdword; + typedef signed char GUSchar; + typedef signed short GUSsample; +#else + #include + typedef int8_t GUSchar; + typedef uint8_t GUSbyte; + typedef uint16_t GUSword; + typedef uint32_t GUSdword; + typedef int16_t GUSsample; +#endif + +typedef struct _GUSEmuState +{ + GUSbyte *himemaddr; /* 1024*1024 bytes used for storing uploaded samples (+32 additional bytes for read padding) */ + GUSbyte *gusdatapos; /* (gusdataend-gusdata) bytes used for storing emulated GF1/mixer register states (32*32+4 bytes in initial GUSemu32 version) */ + uint32_t gusirq; + uint32_t gusdma; + unsigned int timer1fraction; + unsigned int timer2fraction; + void *opaque; +} GUSEmuState; + +/* ** Callback functions needed: */ +/* NMI is defined as hwirq=-1 (not supported (yet?)) */ +/* GUS_irqrequest returns the number of IRQs actually scheduled into the virtual machine */ +/* Level triggered IRQ simulations normally return 1 */ +/* Event triggered IRQ simulation can safely ignore GUS_irqclear calls */ +int GUS_irqrequest(GUSEmuState *state, int hwirq, int num);/* needed in both mixer and bus emulation functions. */ +void GUS_irqclear( GUSEmuState *state, int hwirq); /* used by gus_write() only - can be left empty for mixer functions */ +void GUS_dmarequest(GUSEmuState *state); /* used by gus_write() only - can be left empty for mixer functions */ + +/* ** ISA bus interface functions: */ + +/* Port I/O handlers */ +/* support the following ports: */ +/* 2x0,2x6,2x8...2xF,3x0...3x7; */ +/* optional: 388,389 (at least writes should be forwarded or some GUS detection algorithms will fail) */ +/* data is passed in host byte order */ +unsigned int gus_read( GUSEmuState *state, int port, int size); +void gus_write(GUSEmuState *state, int port, int size, unsigned int data); +/* size is given in bytes (1 for byte, 2 for word) */ + +/* DMA data transfer function */ +/* data pointed to is passed in native x86 order */ +void gus_dma_transferdata(GUSEmuState *state, char *dma_addr, unsigned int count, int TC); +/* Called back by GUS_start_DMA as soon as the emulated DMA controller is ready for a transfer to or from GUS */ +/* (might be immediately if the DMA controller was programmed first) */ +/* dma_addr is an already translated address directly pointing to the beginning of the memory block */ +/* do not forget to update DMA states after the call, including the DREQ and TC flags */ +/* it is possible to break down a single transfer into multiple ones, but take care that: */ +/* -dma_count is actually count-1 */ +/* -before and during a transfer, DREQ is set and TC cleared */ +/* -when calling gus_dma_transferdata(), TC is only set true for call transferring the last byte */ +/* -after the last transfer, DREQ is cleared and TC is set */ + +/* ** GF1 mixer emulation functions: */ +/* Usually, gus_irqgen should be called directly after gus_mixvoices if you can meet the recommended ranges. */ +/* If the interrupts are executed immediately (i.e., are synchronous), it may be useful to break this */ +/* down into a sequence of gus_mixvoice();gus_irqgen(); calls while mixing an audio block. */ +/* If the interrupts are asynchronous, it may be needed to use a separate thread mixing into a temporary */ +/* audio buffer in order to avoid quality loss caused by large numsamples and elapsed_time values. */ + +void gus_mixvoices(GUSEmuState *state, unsigned int playback_freq, unsigned int numsamples, GUSsample *bufferpos); +/* recommended range: 10 < numsamples < 100 */ +/* lower values may result in increased rounding error, higher values often cause audible timing delays */ + +void gus_irqgen(GUSEmuState *state, unsigned int elapsed_time); +/* recommended range: 80us < elapsed_time < max(1000us, numsamples/playback_freq) */ +/* lower values won´t provide any benefit at all, higher values can cause audible timing delays */ +/* note: masked timers are also calculated by this function, thus it might be needed even without any IRQs in use! */ + +#endif /* gusemu.h */ diff --git a/qemu/hw/audio/gusemu_hal.c b/qemu/hw/audio/gusemu_hal.c new file mode 100644 index 000000000..609669073 --- /dev/null +++ b/qemu/hw/audio/gusemu_hal.c @@ -0,0 +1,554 @@ +/* + * GUSEMU32 - bus interface part + * + * Copyright (C) 2000-2007 Tibor "TS" Schütz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * TODO: check mixer: see 7.20 of sdk for panning pos (applies to all gus models?)? + */ + +#include "gustate.h" +#include "gusemu.h" + +#define GUSregb(position) (* (gusptr+(position))) +#define GUSregw(position) (*(GUSword *) (gusptr+(position))) +#define GUSregd(position) (*(GUSdword *)(gusptr+(position))) + +/* size given in bytes */ +unsigned int gus_read(GUSEmuState * state, int port, int size) +{ + int value_read = 0; + + GUSbyte *gusptr; + gusptr = state->gusdatapos; + GUSregd(portaccesses)++; + + switch (port & 0xff0f) + { + /* MixerCtrlReg (read not supported on GUS classic) */ + /* case 0x200: return GUSregb(MixerCtrlReg2x0); */ + case 0x206: /* IRQstatReg / SB2x6IRQ */ + /* adlib/sb bits set in port handlers */ + /* timer/voice bits set in gus_irqgen() */ + /* dma bit set in gus_dma_transferdata */ + /* midi not implemented yet */ + return GUSregb(IRQStatReg2x6); + /* case 0x308: */ /* AdLib388 */ + case 0x208: + if (GUSregb(GUS45TimerCtrl) & 1) + return GUSregb(TimerStatus2x8); + return GUSregb(AdLibStatus2x8); /* AdLibStatus */ + case 0x309: /* AdLib389 */ + case 0x209: + return GUSregb(AdLibData2x9); /* AdLibData */ + case 0x20A: + return GUSregb(AdLibCommand2xA); /* AdLib2x8_2xA */ + +#if 0 + case 0x20B: /* GUS hidden registers (read not supported on GUS classic) */ + switch (GUSregb(RegCtrl_2xF) & 0x07) + { + case 0: /* IRQ/DMA select */ + if (GUSregb(MixerCtrlReg2x0) & 0x40) + return GUSregb(IRQ_2xB); /* control register select bit */ + else + return GUSregb(DMA_2xB); + /* case 1-5: */ /* general purpose emulation regs */ + /* return ... */ /* + status reset reg (write only) */ + case 6: + return GUSregb(Jumper_2xB); /* Joystick/MIDI enable (JumperReg) */ + default:; + } + break; +#endif + + case 0x20C: /* SB2xCd */ + value_read = GUSregb(SB2xCd); + if (GUSregb(StatRead_2xF) & 0x20) + GUSregb(SB2xCd) ^= 0x80; /* toggle MSB on read */ + return value_read; + /* case 0x20D: */ /* SB2xD is write only -> 2xE writes to it*/ + case 0x20E: + if (GUSregb(RegCtrl_2xF) & 0x80) /* 2xE read IRQ enabled? */ + { + GUSregb(StatRead_2xF) |= 0x80; + GUS_irqrequest(state, state->gusirq, 1); + } + return GUSregb(SB2xE); /* SB2xE */ + case 0x20F: /* StatRead_2xF */ + /*set/clear fixed bits */ + /*value_read = (GUSregb(StatRead_2xF) & 0xf9)|1; */ /*(LSB not set on GUS classic!)*/ + value_read = (GUSregb(StatRead_2xF) & 0xf9); + if (GUSregb(MixerCtrlReg2x0) & 0x08) + value_read |= 2; /* DMA/IRQ enabled flag */ + return value_read; + /* case 0x300: */ /* MIDI (not implemented) */ + /* case 0x301: */ /* MIDI (not implemented) */ + case 0x302: + return GUSregb(VoiceSelReg3x2); /* VoiceSelReg */ + case 0x303: + return GUSregb(FunkSelReg3x3); /* FunkSelReg */ + case 0x304: /* DataRegLoByte3x4 + DataRegWord3x4 */ + case 0x305: /* DataRegHiByte3x5 */ + switch (GUSregb(FunkSelReg3x3)) + { + /* common functions */ + case 0x41: /* DramDMAContrReg */ + value_read = GUSregb(GUS41DMACtrl); /* &0xfb */ + GUSregb(GUS41DMACtrl) &= 0xbb; + if (state->gusdma >= 4) + value_read |= 0x04; + if (GUSregb(IRQStatReg2x6) & 0x80) + { + value_read |= 0x40; + GUSregb(IRQStatReg2x6) &= 0x7f; + if (!GUSregb(IRQStatReg2x6)) + GUS_irqclear(state, state->gusirq); + } + return (GUSbyte) value_read; + /* DramDMAmemPosReg */ + /* case 0x42: value_read=GUSregw(GUS42DMAStart); break;*/ + /* 43h+44h write only */ + case 0x45: + return GUSregb(GUS45TimerCtrl); /* TimerCtrlReg */ + /* 46h+47h write only */ + /* 48h: samp freq - write only */ + case 0x49: + return GUSregb(GUS49SampCtrl) & 0xbf; /* SampCtrlReg */ + /* case 4bh: */ /* joystick trim not supported */ + /* case 0x4c: return GUSregb(GUS4cReset); */ /* GUSreset: write only*/ + /* voice specific functions */ + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + { + int offset = 2 * (GUSregb(FunkSelReg3x3) & 0x0f); + offset += ((int) GUSregb(VoiceSelReg3x2) & 0x1f) << 5; /* = Voice*32 + Funktion*2 */ + value_read = GUSregw(offset); + } + break; + /* voice unspecific functions */ + case 0x8e: /* NumVoice */ + return GUSregb(NumVoices); + case 0x8f: /* irqstatreg */ + /* (pseudo IRQ-FIFO is processed during a gus_write(0x3X3,0x8f)) */ + return GUSregb(SynVoiceIRQ8f); + default: + return 0xffff; + } + if (size == 1) + { + if ((port & 0xff0f) == 0x305) + value_read = value_read >> 8; + value_read &= 0xff; + } + return (GUSword) value_read; + /* case 0x306: */ /* Mixer/Version info */ + /* return 0xff; */ /* Pre 3.6 boards, ICS mixer NOT present */ + case 0x307: /* DRAMaccess */ + { + GUSbyte *adr; + adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff); + return *adr; + } + default:; + } + return 0xffff; +} + +void gus_write(GUSEmuState * state, int port, int size, unsigned int data) +{ + GUSbyte *gusptr; + gusptr = state->gusdatapos; + GUSregd(portaccesses)++; + + switch (port & 0xff0f) + { + case 0x200: /* MixerCtrlReg */ + GUSregb(MixerCtrlReg2x0) = (GUSbyte) data; + break; + case 0x206: /* IRQstatReg / SB2x6IRQ */ + if (GUSregb(GUS45TimerCtrl) & 0x20) /* SB IRQ enabled? -> set 2x6IRQ bit */ + { + GUSregb(TimerStatus2x8) |= 0x08; + GUSregb(IRQStatReg2x6) = 0x10; + GUS_irqrequest(state, state->gusirq, 1); + } + break; + case 0x308: /* AdLib 388h */ + case 0x208: /* AdLibCommandReg */ + GUSregb(AdLibCommand2xA) = (GUSbyte) data; + break; + case 0x309: /* AdLib 389h */ + case 0x209: /* AdLibDataReg */ + if ((GUSregb(AdLibCommand2xA) == 0x04) && (!(GUSregb(GUS45TimerCtrl) & 1))) /* GUS auto timer mode enabled? */ + { + if (data & 0x80) + GUSregb(TimerStatus2x8) &= 0x1f; /* AdLib IRQ reset? -> clear maskable adl. timer int regs */ + else + GUSregb(TimerDataReg2x9) = (GUSbyte) data; + } + else + { + GUSregb(AdLibData2x9) = (GUSbyte) data; + if (GUSregb(GUS45TimerCtrl) & 0x02) + { + GUSregb(TimerStatus2x8) |= 0x01; + GUSregb(IRQStatReg2x6) = 0x10; + GUS_irqrequest(state, state->gusirq, 1); + } + } + break; + case 0x20A: + GUSregb(AdLibStatus2x8) = (GUSbyte) data; + break; /* AdLibStatus2x8 */ + case 0x20B: /* GUS hidden registers */ + switch (GUSregb(RegCtrl_2xF) & 0x7) + { + case 0: + if (GUSregb(MixerCtrlReg2x0) & 0x40) + GUSregb(IRQ_2xB) = (GUSbyte) data; /* control register select bit */ + else + GUSregb(DMA_2xB) = (GUSbyte) data; + break; + /* case 1-4: general purpose emulation regs */ + case 5: /* clear stat reg 2xF */ + GUSregb(StatRead_2xF) = 0; /* ToDo: is this identical with GUS classic? */ + if (!GUSregb(IRQStatReg2x6)) + GUS_irqclear(state, state->gusirq); + break; + case 6: /* Jumper reg (Joystick/MIDI enable) */ + GUSregb(Jumper_2xB) = (GUSbyte) data; + break; + default:; + } + break; + case 0x20C: /* SB2xCd */ + if (GUSregb(GUS45TimerCtrl) & 0x20) + { + GUSregb(TimerStatus2x8) |= 0x10; /* SB IRQ enabled? -> set 2xCIRQ bit */ + GUSregb(IRQStatReg2x6) = 0x10; + GUS_irqrequest(state, state->gusirq, 1); + } + case 0x20D: /* SB2xCd no IRQ */ + GUSregb(SB2xCd) = (GUSbyte) data; + break; + case 0x20E: /* SB2xE */ + GUSregb(SB2xE) = (GUSbyte) data; + break; + case 0x20F: + GUSregb(RegCtrl_2xF) = (GUSbyte) data; + break; /* CtrlReg2xF */ + case 0x302: /* VoiceSelReg */ + GUSregb(VoiceSelReg3x2) = (GUSbyte) data; + break; + case 0x303: /* FunkSelReg */ + GUSregb(FunkSelReg3x3) = (GUSbyte) data; + if ((GUSbyte) data == 0x8f) /* set irqstatreg, get voicereg and clear IRQ */ + { + int voice; + if (GUSregd(voicewavetableirq)) /* WavetableIRQ */ + { + for (voice = 0; voice < 31; voice++) + { + if (GUSregd(voicewavetableirq) & (1 << voice)) + { + GUSregd(voicewavetableirq) ^= (1 << voice); /* clear IRQ bit */ + GUSregb(voice << 5) &= 0x7f; /* clear voice reg irq bit */ + if (!GUSregd(voicewavetableirq)) + GUSregb(IRQStatReg2x6) &= 0xdf; + if (!GUSregb(IRQStatReg2x6)) + GUS_irqclear(state, state->gusirq); + GUSregb(SynVoiceIRQ8f) = voice | 0x60; /* (bit==0 => IRQ wartend) */ + return; + } + } + } + else if (GUSregd(voicevolrampirq)) /* VolRamp IRQ */ + { + for (voice = 0; voice < 31; voice++) + { + if (GUSregd(voicevolrampirq) & (1 << voice)) + { + GUSregd(voicevolrampirq) ^= (1 << voice); /* clear IRQ bit */ + GUSregb((voice << 5) + VSRVolRampControl) &= 0x7f; /* clear voice volume reg irq bit */ + if (!GUSregd(voicevolrampirq)) + GUSregb(IRQStatReg2x6) &= 0xbf; + if (!GUSregb(IRQStatReg2x6)) + GUS_irqclear(state, state->gusirq); + GUSregb(SynVoiceIRQ8f) = voice | 0x80; /* (bit==0 => IRQ wartend) */ + return; + } + } + } + GUSregb(SynVoiceIRQ8f) = 0xe8; /* kein IRQ wartet */ + } + break; + case 0x304: + case 0x305: + { + GUSword writedata = (GUSword) data; + GUSword readmask = 0x0000; + if (size == 1) + { + readmask = 0xff00; + writedata &= 0xff; + if ((port & 0xff0f) == 0x305) + { + writedata = (GUSword) (writedata << 8); + readmask = 0x00ff; + } + } + switch (GUSregb(FunkSelReg3x3)) + { + /* voice specific functions */ + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + { + int offset; + if (!(GUSregb(GUS4cReset) & 0x01)) + break; /* reset flag active? */ + offset = 2 * (GUSregb(FunkSelReg3x3) & 0x0f); + offset += (GUSregb(VoiceSelReg3x2) & 0x1f) << 5; /* = Voice*32 + Funktion*2 */ + GUSregw(offset) = (GUSword) ((GUSregw(offset) & readmask) | writedata); + } + break; + /* voice unspecific functions */ + case 0x0e: /* NumVoices */ + GUSregb(NumVoices) = (GUSbyte) data; + break; + /* case 0x0f: */ /* read only */ + /* common functions */ + case 0x41: /* DramDMAContrReg */ + GUSregb(GUS41DMACtrl) = (GUSbyte) data; + if (data & 0x01) + GUS_dmarequest(state); + break; + case 0x42: /* DramDMAmemPosReg */ + GUSregw(GUS42DMAStart) = (GUSregw(GUS42DMAStart) & readmask) | writedata; + GUSregb(GUS50DMAHigh) &= 0xf; /* compatibility stuff... */ + break; + case 0x43: /* DRAMaddrLo */ + GUSregd(GUSDRAMPOS24bit) = + (GUSregd(GUSDRAMPOS24bit) & (readmask | 0xff0000)) | writedata; + break; + case 0x44: /* DRAMaddrHi */ + GUSregd(GUSDRAMPOS24bit) = + (GUSregd(GUSDRAMPOS24bit) & 0xffff) | ((data & 0x0f) << 16); + break; + case 0x45: /* TCtrlReg */ + GUSregb(GUS45TimerCtrl) = (GUSbyte) data; + if (!(data & 0x20)) + GUSregb(TimerStatus2x8) &= 0xe7; /* sb IRQ dis? -> clear 2x8/2xC sb IRQ flags */ + if (!(data & 0x02)) + GUSregb(TimerStatus2x8) &= 0xfe; /* adlib data IRQ dis? -> clear 2x8 adlib IRQ flag */ + if (!(GUSregb(TimerStatus2x8) & 0x19)) + GUSregb(IRQStatReg2x6) &= 0xef; /* 0xe6; $$clear IRQ if both IRQ bits are inactive or cleared */ + /* catch up delayed timer IRQs: */ + if ((GUSregw(TimerIRQs) > 1) && (GUSregb(TimerDataReg2x9) & 3)) + { + if (GUSregb(TimerDataReg2x9) & 1) /* start timer 1 (80us decrement rate) */ + { + if (!(GUSregb(TimerDataReg2x9) & 0x40)) + GUSregb(TimerStatus2x8) |= 0xc0; /* maskable bits */ + if (data & 4) /* timer1 irq enable */ + { + GUSregb(TimerStatus2x8) |= 4; /* nonmaskable bit */ + GUSregb(IRQStatReg2x6) |= 4; /* timer 1 irq pending */ + } + } + if (GUSregb(TimerDataReg2x9) & 2) /* start timer 2 (320us decrement rate) */ + { + if (!(GUSregb(TimerDataReg2x9) & 0x20)) + GUSregb(TimerStatus2x8) |= 0xa0; /* maskable bits */ + if (data & 8) /* timer2 irq enable */ + { + GUSregb(TimerStatus2x8) |= 2; /* nonmaskable bit */ + GUSregb(IRQStatReg2x6) |= 8; /* timer 2 irq pending */ + } + } + GUSregw(TimerIRQs)--; + if (GUSregw(BusyTimerIRQs) > 1) + GUSregw(BusyTimerIRQs)--; + else + GUSregw(BusyTimerIRQs) = + GUS_irqrequest(state, state->gusirq, GUSregw(TimerIRQs)); + } + else + GUSregw(TimerIRQs) = 0; + + if (!(data & 0x04)) + { + GUSregb(TimerStatus2x8) &= 0xfb; /* clear non-maskable timer1 bit */ + GUSregb(IRQStatReg2x6) &= 0xfb; + } + if (!(data & 0x08)) + { + GUSregb(TimerStatus2x8) &= 0xfd; /* clear non-maskable timer2 bit */ + GUSregb(IRQStatReg2x6) &= 0xf7; + } + if (!GUSregb(IRQStatReg2x6)) + GUS_irqclear(state, state->gusirq); + break; + case 0x46: /* Counter1 */ + GUSregb(GUS46Counter1) = (GUSbyte) data; + break; + case 0x47: /* Counter2 */ + GUSregb(GUS47Counter2) = (GUSbyte) data; + break; + /* case 0x48: */ /* sampling freq reg not emulated (same as interwave) */ + case 0x49: /* SampCtrlReg */ + GUSregb(GUS49SampCtrl) = (GUSbyte) data; + break; + /* case 0x4b: */ /* joystick trim not emulated */ + case 0x4c: /* GUSreset */ + GUSregb(GUS4cReset) = (GUSbyte) data; + if (!(GUSregb(GUS4cReset) & 1)) /* reset... */ + { + GUSregd(voicewavetableirq) = 0; + GUSregd(voicevolrampirq) = 0; + GUSregw(TimerIRQs) = 0; + GUSregw(BusyTimerIRQs) = 0; + GUSregb(NumVoices) = 0xcd; + GUSregb(IRQStatReg2x6) = 0; + GUSregb(TimerStatus2x8) = 0; + GUSregb(AdLibData2x9) = 0; + GUSregb(TimerDataReg2x9) = 0; + GUSregb(GUS41DMACtrl) = 0; + GUSregb(GUS45TimerCtrl) = 0; + GUSregb(GUS49SampCtrl) = 0; + GUSregb(GUS4cReset) &= 0xf9; /* clear IRQ and DAC enable bits */ + GUS_irqclear(state, state->gusirq); + } + /* IRQ enable bit checked elsewhere */ + /* EnableDAC bit may be used by external callers */ + break; + } + } + break; + case 0x307: /* DRAMaccess */ + { + GUSbyte *adr; + adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff); + *adr = (GUSbyte) data; + } + break; + } +} + +/* Attention when breaking up a single DMA transfer to multiple ones: + * it may lead to multiple terminal count interrupts and broken transfers: + * + * 1. Whenever you transfer a piece of data, the gusemu callback is invoked + * 2. The callback may generate a TC irq (if the register was set up to do so) + * 3. The irq may result in the program using the GUS to reprogram the GUS + * + * Some programs also decide to upload by just checking if TC occurs + * (via interrupt or a cleared GUS dma flag) + * and then start the next transfer, without checking DMA state + * + * Thus: Always make sure to set the TC flag correctly! + * + * Note that the genuine GUS had a granularity of 16 bytes/words for low/high DMA + * while later cards had atomic granularity provided by an additional GUS50DMAHigh register + * GUSemu also uses this register to support byte-granular transfers for better compatibility + * with emulators other than GUSemu32 + */ + +void gus_dma_transferdata(GUSEmuState * state, char *dma_addr, unsigned int count, int TC) +{ + /* this function gets called by the callback function as soon as a DMA transfer is about to start + * dma_addr is a translated address within accessible memory, not the physical one, + * count is (real dma count register)+1 + * note that the amount of bytes transferred is fully determined by values in the DMA registers + * do not forget to update DMA states after transferring the entire block: + * DREQ cleared & TC asserted after the _whole_ transfer */ + + char *srcaddr; + char *destaddr; + char msbmask = 0; + GUSbyte *gusptr; + gusptr = state->gusdatapos; + + srcaddr = dma_addr; /* system memory address */ + { + int offset = (GUSregw(GUS42DMAStart) << 4) + (GUSregb(GUS50DMAHigh) & 0xf); + if (state->gusdma >= 4) + offset = (offset & 0xc0000) + (2 * (offset & 0x1fff0)); /* 16 bit address translation */ + destaddr = (char *) state->himemaddr + offset; /* wavetable RAM address */ + } + + GUSregw(GUS42DMAStart) += (GUSword) (count >> 4); /* ToDo: add 16bit GUS page limit? */ + GUSregb(GUS50DMAHigh) = (GUSbyte) ((count + GUSregb(GUS50DMAHigh)) & 0xf); /* ToDo: add 16bit GUS page limit? */ + + if (GUSregb(GUS41DMACtrl) & 0x02) /* direction, 0 := sysram->gusram */ + { + char *tmpaddr = destaddr; + destaddr = srcaddr; + srcaddr = tmpaddr; + } + + if ((GUSregb(GUS41DMACtrl) & 0x80) && (!(GUSregb(GUS41DMACtrl) & 0x02))) + msbmask = (const char) 0x80; /* invert MSB */ + for (; count > 0; count--) + { + if (GUSregb(GUS41DMACtrl) & 0x40) + *(destaddr++) = *(srcaddr++); /* 16 bit lobyte */ + else + *(destaddr++) = (msbmask ^ (*(srcaddr++))); /* 8 bit */ + if (state->gusdma >= 4) + *(destaddr++) = (msbmask ^ (*(srcaddr++))); /* 16 bit hibyte */ + } + + if (TC) + { + (GUSregb(GUS41DMACtrl)) &= 0xfe; /* clear DMA request bit */ + if (GUSregb(GUS41DMACtrl) & 0x20) /* DMA terminal count IRQ */ + { + GUSregb(IRQStatReg2x6) |= 0x80; + GUS_irqrequest(state, state->gusirq, 1); + } + } +} diff --git a/qemu/hw/audio/gusemu_mixer.c b/qemu/hw/audio/gusemu_mixer.c new file mode 100644 index 000000000..6d8d9ced1 --- /dev/null +++ b/qemu/hw/audio/gusemu_mixer.c @@ -0,0 +1,240 @@ +/* + * GUSEMU32 - mixing engine (similar to Interwave GF1 compatibility) + * + * Copyright (C) 2000-2007 Tibor "TS" Schütz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "gusemu.h" +#include "gustate.h" + +#define GUSregb(position) (* (gusptr+(position))) +#define GUSregw(position) (*(GUSword *) (gusptr+(position))) +#define GUSregd(position) (*(GUSdword *)(gusptr+(position))) + +#define GUSvoice(position) (*(GUSword *)(voiceptr+(position))) + +/* samples are always 16bit stereo (4 bytes each, first right then left interleaved) */ +void gus_mixvoices(GUSEmuState * state, unsigned int playback_freq, unsigned int numsamples, + GUSsample *bufferpos) +{ + /* note that byte registers are stored in the upper half of each voice register! */ + GUSbyte *gusptr; + int Voice; + GUSword *voiceptr; + + unsigned int count; + for (count = 0; count < numsamples * 2; count++) + *(bufferpos + count) = 0; /* clear */ + + gusptr = state->gusdatapos; + voiceptr = (GUSword *) gusptr; + if (!(GUSregb(GUS4cReset) & 0x01)) /* reset flag active? */ + return; + + for (Voice = 0; Voice <= (GUSregb(NumVoices) & 31); Voice++) + { + if (GUSvoice(wVSRControl) & 0x200) + GUSvoice(wVSRControl) |= 0x100; /* voice stop request */ + if (GUSvoice(wVSRVolRampControl) & 0x200) + GUSvoice(wVSRVolRampControl) |= 0x100; /* Volume ramp stop request */ + if (!(GUSvoice(wVSRControl) & GUSvoice(wVSRVolRampControl) & 0x100)) /* neither voice nor volume calculation active - save some time here ;) */ + { + unsigned int sample; + + unsigned int LoopStart = (GUSvoice(wVSRLoopStartHi) << 16) | GUSvoice(wVSRLoopStartLo); /* 23.9 format */ + unsigned int LoopEnd = (GUSvoice(wVSRLoopEndHi) << 16) | GUSvoice(wVSRLoopEndLo); /* 23.9 format */ + unsigned int CurrPos = (GUSvoice(wVSRCurrPosHi) << 16) | GUSvoice(wVSRCurrPosLo); /* 23.9 format */ + int VoiceIncrement = ((((unsigned long) GUSvoice(wVSRFreq) * 44100) / playback_freq) * (14 >> 1)) / + ((GUSregb(NumVoices) & 31) + 1); /* 6.10 increment/frame to 23.9 increment/sample */ + + int PanningPos = (GUSvoice(wVSRPanning) >> 8) & 0xf; + + unsigned int Volume32 = 32 * GUSvoice(wVSRCurrVol); /* 32 times larger than original gus for maintaining precision while ramping */ + unsigned int StartVol32 = (GUSvoice(wVSRVolRampStartVol) & 0xff00) * 32; + unsigned int EndVol32 = (GUSvoice(wVSRVolRampEndVol) & 0xff00) * 32; + int VolumeIncrement32 = (32 * 16 * (GUSvoice(wVSRVolRampRate) & 0x3f00) >> 8) >> ((((GUSvoice(wVSRVolRampRate) & 0xc000) >> 8) >> 6) * 3); /* including 1/8/64/512 volume speed divisor */ + VolumeIncrement32 = (((VolumeIncrement32 * 44100 / 2) / playback_freq) * 14) / ((GUSregb(NumVoices) & 31) + 1); /* adjust ramping speed to playback speed */ + + if (GUSvoice(wVSRControl) & 0x4000) + VoiceIncrement = -VoiceIncrement; /* reverse playback */ + if (GUSvoice(wVSRVolRampControl) & 0x4000) + VolumeIncrement32 = -VolumeIncrement32; /* reverse ramping */ + + for (sample = 0; sample < numsamples; sample++) + { + int sample1, sample2, Volume; + if (GUSvoice(wVSRControl) & 0x400) /* 16bit */ + { + int offset = ((CurrPos >> 9) & 0xc0000) + (((CurrPos >> 9) & 0x1ffff) << 1); + GUSchar *adr; + adr = (GUSchar *) state->himemaddr + offset; + sample1 = (*adr & 0xff) + (*(adr + 1) * 256); + sample2 = (*(adr + 2) & 0xff) + (*(adr + 2 + 1) * 256); + } + else /* 8bit */ + { + int offset = (CurrPos >> 9) & 0xfffff; + GUSchar *adr; + adr = (GUSchar *) state->himemaddr + offset; + sample1 = (*adr) * 256; + sample2 = (*(adr + 1)) * 256; + } + + Volume = ((((Volume32 >> (4 + 5)) & 0xff) + 256) << (Volume32 >> ((4 + 8) + 5))) / 512; /* semi-logarithmic volume, +5 due to additional precision */ + sample1 = (((sample1 * Volume) >> 16) * (512 - (CurrPos % 512))) / 512; + sample2 = (((sample2 * Volume) >> 16) * (CurrPos % 512)) / 512; + sample1 += sample2; + + if (!(GUSvoice(wVSRVolRampControl) & 0x100)) + { + Volume32 += VolumeIncrement32; + if ((GUSvoice(wVSRVolRampControl) & 0x4000) ? (Volume32 <= StartVol32) : (Volume32 >= EndVol32)) /* ramp up boundary cross */ + { + if (GUSvoice(wVSRVolRampControl) & 0x2000) + GUSvoice(wVSRVolRampControl) |= 0x8000; /* volramp IRQ enabled? -> IRQ wait flag */ + if (GUSvoice(wVSRVolRampControl) & 0x800) /* loop enabled */ + { + if (GUSvoice(wVSRVolRampControl) & 0x1000) /* bidir. loop */ + { + GUSvoice(wVSRVolRampControl) ^= 0x4000; /* toggle dir */ + VolumeIncrement32 = -VolumeIncrement32; + } + else + Volume32 = (GUSvoice(wVSRVolRampControl) & 0x4000) ? EndVol32 : StartVol32; /* unidir. loop ramp */ + } + else + { + GUSvoice(wVSRVolRampControl) |= 0x100; + Volume32 = + (GUSvoice(wVSRVolRampControl) & 0x4000) ? StartVol32 : EndVol32; + } + } + } + if ((GUSvoice(wVSRVolRampControl) & 0xa000) == 0xa000) /* volramp IRQ set and enabled? */ + { + GUSregd(voicevolrampirq) |= 1 << Voice; /* set irq slot */ + } + else + { + GUSregd(voicevolrampirq) &= (~(1 << Voice)); /* clear irq slot */ + GUSvoice(wVSRVolRampControl) &= 0x7f00; + } + + if (!(GUSvoice(wVSRControl) & 0x100)) + { + CurrPos += VoiceIncrement; + if ((GUSvoice(wVSRControl) & 0x4000) ? (CurrPos <= LoopStart) : (CurrPos >= LoopEnd)) /* playback boundary cross */ + { + if (GUSvoice(wVSRControl) & 0x2000) + GUSvoice(wVSRControl) |= 0x8000; /* voice IRQ enabled -> IRQ wait flag */ + if (GUSvoice(wVSRControl) & 0x800) /* loop enabled */ + { + if (GUSvoice(wVSRControl) & 0x1000) /* pingpong loop */ + { + GUSvoice(wVSRControl) ^= 0x4000; /* toggle dir */ + VoiceIncrement = -VoiceIncrement; + } + else + CurrPos = (GUSvoice(wVSRControl) & 0x4000) ? LoopEnd : LoopStart; /* unidir. loop */ + } + else if (!(GUSvoice(wVSRVolRampControl) & 0x400)) + GUSvoice(wVSRControl) |= 0x100; /* loop disabled, rollover check */ + } + } + if ((GUSvoice(wVSRControl) & 0xa000) == 0xa000) /* wavetable IRQ set and enabled? */ + { + GUSregd(voicewavetableirq) |= 1 << Voice; /* set irq slot */ + } + else + { + GUSregd(voicewavetableirq) &= (~(1 << Voice)); /* clear irq slot */ + GUSvoice(wVSRControl) &= 0x7f00; + } + + /* mix samples into buffer */ + *(bufferpos + 2 * sample) += (GUSsample) ((sample1 * PanningPos) >> 4); /* right */ + *(bufferpos + 2 * sample + 1) += (GUSsample) ((sample1 * (15 - PanningPos)) >> 4); /* left */ + } + /* write back voice and volume */ + GUSvoice(wVSRCurrVol) = Volume32 / 32; + GUSvoice(wVSRCurrPosHi) = CurrPos >> 16; + GUSvoice(wVSRCurrPosLo) = CurrPos & 0xffff; + } + voiceptr += 16; /* next voice */ + } +} + +void gus_irqgen(GUSEmuState * state, unsigned int elapsed_time) +/* time given in microseconds */ +{ + int requestedIRQs = 0; + GUSbyte *gusptr; + gusptr = state->gusdatapos; + if (GUSregb(TimerDataReg2x9) & 1) /* start timer 1 (80us decrement rate) */ + { + unsigned int timer1fraction = state->timer1fraction; + int newtimerirqs; + newtimerirqs = (elapsed_time + timer1fraction) / (80 * (256 - GUSregb(GUS46Counter1))); + state->timer1fraction = (elapsed_time + timer1fraction) % (80 * (256 - GUSregb(GUS46Counter1))); + if (newtimerirqs) + { + if (!(GUSregb(TimerDataReg2x9) & 0x40)) + GUSregb(TimerStatus2x8) |= 0xc0; /* maskable bits */ + if (GUSregb(GUS45TimerCtrl) & 4) /* timer1 irq enable */ + { + GUSregb(TimerStatus2x8) |= 4; /* nonmaskable bit */ + GUSregb(IRQStatReg2x6) |= 4; /* timer 1 irq pending */ + GUSregw(TimerIRQs) += newtimerirqs; + requestedIRQs += newtimerirqs; + } + } + } + if (GUSregb(TimerDataReg2x9) & 2) /* start timer 2 (320us decrement rate) */ + { + unsigned int timer2fraction = state->timer2fraction; + int newtimerirqs; + newtimerirqs = (elapsed_time + timer2fraction) / (320 * (256 - GUSregb(GUS47Counter2))); + state->timer2fraction = (elapsed_time + timer2fraction) % (320 * (256 - GUSregb(GUS47Counter2))); + if (newtimerirqs) + { + if (!(GUSregb(TimerDataReg2x9) & 0x20)) + GUSregb(TimerStatus2x8) |= 0xa0; /* maskable bits */ + if (GUSregb(GUS45TimerCtrl) & 8) /* timer2 irq enable */ + { + GUSregb(TimerStatus2x8) |= 2; /* nonmaskable bit */ + GUSregb(IRQStatReg2x6) |= 8; /* timer 2 irq pending */ + GUSregw(TimerIRQs) += newtimerirqs; + requestedIRQs += newtimerirqs; + } + } + } + if (GUSregb(GUS4cReset) & 0x4) /* synth IRQ enable */ + { + if (GUSregd(voicewavetableirq)) + GUSregb(IRQStatReg2x6) |= 0x20; + if (GUSregd(voicevolrampirq)) + GUSregb(IRQStatReg2x6) |= 0x40; + } + if ((!requestedIRQs) && GUSregb(IRQStatReg2x6)) + requestedIRQs++; + if (GUSregb(IRQStatReg2x6)) + GUSregw(BusyTimerIRQs) = GUS_irqrequest(state, state->gusirq, requestedIRQs); +} diff --git a/qemu/hw/audio/gustate.h b/qemu/hw/audio/gustate.h new file mode 100644 index 000000000..ece903abb --- /dev/null +++ b/qemu/hw/audio/gustate.h @@ -0,0 +1,132 @@ +/* + * GUSEMU32 - persistent GUS register state + * + * Copyright (C) 2000-2007 Tibor "TS" Schütz + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUSTATE_H +#define GUSTATE_H + +/*state block offset*/ +#define gusdata (0) + +/* data stored using this structure is in host byte order! */ + +/*access type*/ +#define PortRead (0) +#define PortWrite (1) + +#define Port8Bitacc (0) +#define Port16Bitacc (1) + +/*voice register offsets (in bytes)*/ +#define VSRegs (0) +#define VSRControl (0) +#define VSRegsEnd (VSRControl+VSRegs + 32*(16*2)) +#define VSRFreq (2) +#define VSRLoopStartHi (4) +#define VSRLoopStartLo (6) +#define VSRLoopEndHi (8) +#define VSRLoopEndLo (10) +#define VSRVolRampRate (12) +#define VSRVolRampStartVol (14) +#define VSRVolRampEndVol (16) +#define VSRCurrVol (18) +#define VSRCurrPosHi (20) +#define VSRCurrPosLo (22) +#define VSRPanning (24) +#define VSRVolRampControl (26) + +/*voice register offsets (in words)*/ +#define wVSRegs (0) +#define wVSRControl (0) +#define wVSRegsEnd (wVSRControl+wVSRegs + 32*(16)) +#define wVSRFreq (1) +#define wVSRLoopStartHi (2) +#define wVSRLoopStartLo (3) +#define wVSRLoopEndHi (4) +#define wVSRLoopEndLo (5) +#define wVSRVolRampRate (6) +#define wVSRVolRampStartVol (7) +#define wVSRVolRampEndVol (8) +#define wVSRCurrVol (9) +#define wVSRCurrPosHi (10) +#define wVSRCurrPosLo (11) +#define wVSRPanning (12) +#define wVSRVolRampControl (13) + +/*GUS register state block: 32 voices, padding filled with remaining registers*/ +#define DataRegLoByte3x4 (VSRVolRampControl+2) +#define DataRegWord3x4 (DataRegLoByte3x4) +#define DataRegHiByte3x5 (VSRVolRampControl+2 +1) +#define DMA_2xB (VSRVolRampControl+2+2) +#define IRQ_2xB (VSRVolRampControl+2+3) + +#define RegCtrl_2xF (VSRVolRampControl+2+(16*2)) +#define Jumper_2xB (VSRVolRampControl+2+(16*2)+1) +#define GUS42DMAStart (VSRVolRampControl+2+(16*2)+2) + +#define GUS43DRAMIOlo (VSRVolRampControl+2+(16*2)*2) +#define GUSDRAMPOS24bit (GUS43DRAMIOlo) +#define GUS44DRAMIOhi (VSRVolRampControl+2+(16*2)*2+2) + +#define voicewavetableirq (VSRVolRampControl+2+(16*2)*3) /* voice IRQ pseudoqueue: 1 bit per voice */ + +#define voicevolrampirq (VSRVolRampControl+2+(16*2)*4) /* voice IRQ pseudoqueue: 1 bit per voice */ + +#define startvoices (VSRVolRampControl+2+(16*2)*5) /* statistics / optimizations */ + +#define IRQStatReg2x6 (VSRVolRampControl+2+(16*2)*6) +#define TimerStatus2x8 (VSRVolRampControl+2+(16*2)*6+1) +#define TimerDataReg2x9 (VSRVolRampControl+2+(16*2)*6+2) +#define MixerCtrlReg2x0 (VSRVolRampControl+2+(16*2)*6+3) + +#define VoiceSelReg3x2 (VSRVolRampControl+2+(16*2)*7) +#define FunkSelReg3x3 (VSRVolRampControl+2+(16*2)*7+1) +#define AdLibStatus2x8 (VSRVolRampControl+2+(16*2)*7+2) +#define StatRead_2xF (VSRVolRampControl+2+(16*2)*7+3) + +#define GUS48SampSpeed (VSRVolRampControl+2+(16*2)*8) +#define GUS41DMACtrl (VSRVolRampControl+2+(16*2)*8+1) +#define GUS45TimerCtrl (VSRVolRampControl+2+(16*2)*8+2) +#define GUS46Counter1 (VSRVolRampControl+2+(16*2)*8+3) + +#define GUS47Counter2 (VSRVolRampControl+2+(16*2)*9) +#define GUS49SampCtrl (VSRVolRampControl+2+(16*2)*9+1) +#define GUS4cReset (VSRVolRampControl+2+(16*2)*9+2) +#define NumVoices (VSRVolRampControl+2+(16*2)*9+3) + +#define TimerIRQs (VSRVolRampControl+2+(16*2)*10) /* delayed IRQ, statistics */ +#define BusyTimerIRQs (VSRVolRampControl+2+(16*2)*10+2) /* delayed IRQ, statistics */ + +#define AdLibCommand2xA (VSRVolRampControl+2+(16*2)*11) +#define AdLibData2x9 (VSRVolRampControl+2+(16*2)*11+1) +#define SB2xCd (VSRVolRampControl+2+(16*2)*11+2) +#define SB2xE (VSRVolRampControl+2+(16*2)*11+3) + +#define SynVoiceIRQ8f (VSRVolRampControl+2+(16*2)*12) +#define GUS50DMAHigh (VSRVolRampControl+2+(16*2)*12+1) + +#define portaccesses (VSRegsEnd) /* statistics / suspend mode */ + +#define gusdataend (VSRegsEnd+4) + +#endif /* gustate.h */ diff --git a/qemu/hw/audio/hda-codec-common.h b/qemu/hw/audio/hda-codec-common.h new file mode 100644 index 000000000..b4fdb51e8 --- /dev/null +++ b/qemu/hw/audio/hda-codec-common.h @@ -0,0 +1,456 @@ +/* + * Common code to disable/enable mixer emulation at run time + * + * Copyright (C) 2013 Red Hat, Inc. + * + * Written by Bandan Das + * with important bits picked up from hda-codec.c + * + * 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 2 or + * (at your option) version 3 of the License. + * + * 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 . + */ + +/* + * HDA codec descriptions + */ + +#ifdef HDA_MIXER +#define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x12) +#define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x22) +#define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x32) +#define QEMU_HDA_AMP_CAPS \ + (AC_AMPCAP_MUTE | \ + (QEMU_HDA_AMP_STEPS << AC_AMPCAP_OFFSET_SHIFT) | \ + (QEMU_HDA_AMP_STEPS << AC_AMPCAP_NUM_STEPS_SHIFT) | \ + (3 << AC_AMPCAP_STEP_SIZE_SHIFT)) +#else +#define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x11) +#define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x21) +#define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x31) +#define QEMU_HDA_AMP_CAPS QEMU_HDA_AMP_NONE +#endif + + +/* common: audio output widget */ +static const desc_param glue(common_params_audio_dac_, PARAM)[] = { + { + .id = AC_PAR_AUDIO_WIDGET_CAP, + .val = ((AC_WID_AUD_OUT << AC_WCAP_TYPE_SHIFT) | + AC_WCAP_FORMAT_OVRD | + AC_WCAP_AMP_OVRD | + AC_WCAP_OUT_AMP | + AC_WCAP_STEREO), + },{ + .id = AC_PAR_PCM, + .val = QEMU_HDA_PCM_FORMATS, + },{ + .id = AC_PAR_STREAM, + .val = AC_SUPFMT_PCM, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_CAPS, + }, +}; + +/* common: audio input widget */ +static const desc_param glue(common_params_audio_adc_, PARAM)[] = { + { + .id = AC_PAR_AUDIO_WIDGET_CAP, + .val = ((AC_WID_AUD_IN << AC_WCAP_TYPE_SHIFT) | + AC_WCAP_CONN_LIST | + AC_WCAP_FORMAT_OVRD | + AC_WCAP_AMP_OVRD | + AC_WCAP_IN_AMP | + AC_WCAP_STEREO), + },{ + .id = AC_PAR_CONNLIST_LEN, + .val = 1, + },{ + .id = AC_PAR_PCM, + .val = QEMU_HDA_PCM_FORMATS, + },{ + .id = AC_PAR_STREAM, + .val = AC_SUPFMT_PCM, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_CAPS, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + }, +}; + +/* common: pin widget (line-out) */ +static const desc_param glue(common_params_audio_lineout_, PARAM)[] = { + { + .id = AC_PAR_AUDIO_WIDGET_CAP, + .val = ((AC_WID_PIN << AC_WCAP_TYPE_SHIFT) | + AC_WCAP_CONN_LIST | + AC_WCAP_STEREO), + },{ + .id = AC_PAR_PIN_CAP, + .val = AC_PINCAP_OUT, + },{ + .id = AC_PAR_CONNLIST_LEN, + .val = 1, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + }, +}; + +/* common: pin widget (line-in) */ +static const desc_param glue(common_params_audio_linein_, PARAM)[] = { + { + .id = AC_PAR_AUDIO_WIDGET_CAP, + .val = ((AC_WID_PIN << AC_WCAP_TYPE_SHIFT) | + AC_WCAP_STEREO), + },{ + .id = AC_PAR_PIN_CAP, + .val = AC_PINCAP_IN, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + }, +}; + +/* output: root node */ +static const desc_param glue(output_params_root_, PARAM)[] = { + { + .id = AC_PAR_VENDOR_ID, + .val = QEMU_HDA_ID_OUTPUT, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_OUTPUT, + },{ + .id = AC_PAR_REV_ID, + .val = 0x00100101, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00010001, + }, +}; + +/* output: audio function */ +static const desc_param glue(output_params_audio_func_, PARAM)[] = { + { + .id = AC_PAR_FUNCTION_TYPE, + .val = AC_GRP_AUDIO_FUNCTION, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_OUTPUT, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00020002, + },{ + .id = AC_PAR_PCM, + .val = QEMU_HDA_PCM_FORMATS, + },{ + .id = AC_PAR_STREAM, + .val = AC_SUPFMT_PCM, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_GPIO_CAP, + .val = 0, + },{ + .id = AC_PAR_AUDIO_FG_CAP, + .val = 0x00000808, + },{ + .id = AC_PAR_POWER_STATE, + .val = 0, + }, +}; + +/* output: nodes */ +static const desc_node glue(output_nodes_, PARAM)[] = { + { + .nid = AC_NODE_ROOT, + .name = "root", + .params = glue(output_params_root_, PARAM), + .nparams = ARRAY_SIZE(glue(output_params_root_, PARAM)), + },{ + .nid = 1, + .name = "func", + .params = glue(output_params_audio_func_, PARAM), + .nparams = ARRAY_SIZE(glue(output_params_audio_func_, PARAM)), + },{ + .nid = 2, + .name = "dac", + .params = glue(common_params_audio_dac_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)), + .stindex = 0, + },{ + .nid = 3, + .name = "out", + .params = glue(common_params_audio_lineout_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)), + .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) | + (AC_JACK_LINE_OUT << AC_DEFCFG_DEVICE_SHIFT) | + (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) | + (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) | + 0x10), + .pinctl = AC_PINCTL_OUT_EN, + .conn = (uint32_t[]) { 2 }, + } +}; + +/* output: codec */ +static const desc_codec glue(output_, PARAM) = { + .name = "output", + .iid = QEMU_HDA_ID_OUTPUT, + .nodes = glue(output_nodes_, PARAM), + .nnodes = ARRAY_SIZE(glue(output_nodes_, PARAM)), +}; + +/* duplex: root node */ +static const desc_param glue(duplex_params_root_, PARAM)[] = { + { + .id = AC_PAR_VENDOR_ID, + .val = QEMU_HDA_ID_DUPLEX, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_DUPLEX, + },{ + .id = AC_PAR_REV_ID, + .val = 0x00100101, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00010001, + }, +}; + +/* duplex: audio function */ +static const desc_param glue(duplex_params_audio_func_, PARAM)[] = { + { + .id = AC_PAR_FUNCTION_TYPE, + .val = AC_GRP_AUDIO_FUNCTION, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_DUPLEX, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00020004, + },{ + .id = AC_PAR_PCM, + .val = QEMU_HDA_PCM_FORMATS, + },{ + .id = AC_PAR_STREAM, + .val = AC_SUPFMT_PCM, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_GPIO_CAP, + .val = 0, + },{ + .id = AC_PAR_AUDIO_FG_CAP, + .val = 0x00000808, + },{ + .id = AC_PAR_POWER_STATE, + .val = 0, + }, +}; + +/* duplex: nodes */ +static const desc_node glue(duplex_nodes_, PARAM)[] = { + { + .nid = AC_NODE_ROOT, + .name = "root", + .params = glue(duplex_params_root_, PARAM), + .nparams = ARRAY_SIZE(glue(duplex_params_root_, PARAM)), + },{ + .nid = 1, + .name = "func", + .params = glue(duplex_params_audio_func_, PARAM), + .nparams = ARRAY_SIZE(glue(duplex_params_audio_func_, PARAM)), + },{ + .nid = 2, + .name = "dac", + .params = glue(common_params_audio_dac_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)), + .stindex = 0, + },{ + .nid = 3, + .name = "out", + .params = glue(common_params_audio_lineout_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)), + .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) | + (AC_JACK_LINE_OUT << AC_DEFCFG_DEVICE_SHIFT) | + (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) | + (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) | + 0x10), + .pinctl = AC_PINCTL_OUT_EN, + .conn = (uint32_t[]) { 2 }, + },{ + .nid = 4, + .name = "adc", + .params = glue(common_params_audio_adc_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_adc_, PARAM)), + .stindex = 1, + .conn = (uint32_t[]) { 5 }, + },{ + .nid = 5, + .name = "in", + .params = glue(common_params_audio_linein_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_linein_, PARAM)), + .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) | + (AC_JACK_LINE_IN << AC_DEFCFG_DEVICE_SHIFT) | + (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) | + (AC_JACK_COLOR_RED << AC_DEFCFG_COLOR_SHIFT) | + 0x20), + .pinctl = AC_PINCTL_IN_EN, + } +}; + +/* duplex: codec */ +static const desc_codec glue(duplex_, PARAM) = { + .name = "duplex", + .iid = QEMU_HDA_ID_DUPLEX, + .nodes = glue(duplex_nodes_, PARAM), + .nnodes = ARRAY_SIZE(glue(duplex_nodes_, PARAM)), +}; + +/* micro: root node */ +static const desc_param glue(micro_params_root_, PARAM)[] = { + { + .id = AC_PAR_VENDOR_ID, + .val = QEMU_HDA_ID_MICRO, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_MICRO, + },{ + .id = AC_PAR_REV_ID, + .val = 0x00100101, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00010001, + }, +}; + +/* micro: audio function */ +static const desc_param glue(micro_params_audio_func_, PARAM)[] = { + { + .id = AC_PAR_FUNCTION_TYPE, + .val = AC_GRP_AUDIO_FUNCTION, + },{ + .id = AC_PAR_SUBSYSTEM_ID, + .val = QEMU_HDA_ID_MICRO, + },{ + .id = AC_PAR_NODE_COUNT, + .val = 0x00020004, + },{ + .id = AC_PAR_PCM, + .val = QEMU_HDA_PCM_FORMATS, + },{ + .id = AC_PAR_STREAM, + .val = AC_SUPFMT_PCM, + },{ + .id = AC_PAR_AMP_IN_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_AMP_OUT_CAP, + .val = QEMU_HDA_AMP_NONE, + },{ + .id = AC_PAR_GPIO_CAP, + .val = 0, + },{ + .id = AC_PAR_AUDIO_FG_CAP, + .val = 0x00000808, + },{ + .id = AC_PAR_POWER_STATE, + .val = 0, + }, +}; + +/* micro: nodes */ +static const desc_node glue(micro_nodes_, PARAM)[] = { + { + .nid = AC_NODE_ROOT, + .name = "root", + .params = glue(micro_params_root_, PARAM), + .nparams = ARRAY_SIZE(glue(micro_params_root_, PARAM)), + },{ + .nid = 1, + .name = "func", + .params = glue(micro_params_audio_func_, PARAM), + .nparams = ARRAY_SIZE(glue(micro_params_audio_func_, PARAM)), + },{ + .nid = 2, + .name = "dac", + .params = glue(common_params_audio_dac_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_dac_, PARAM)), + .stindex = 0, + },{ + .nid = 3, + .name = "out", + .params = glue(common_params_audio_lineout_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_lineout_, PARAM)), + .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) | + (AC_JACK_SPEAKER << AC_DEFCFG_DEVICE_SHIFT) | + (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) | + (AC_JACK_COLOR_GREEN << AC_DEFCFG_COLOR_SHIFT) | + 0x10), + .pinctl = AC_PINCTL_OUT_EN, + .conn = (uint32_t[]) { 2 }, + },{ + .nid = 4, + .name = "adc", + .params = glue(common_params_audio_adc_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_adc_, PARAM)), + .stindex = 1, + .conn = (uint32_t[]) { 5 }, + },{ + .nid = 5, + .name = "in", + .params = glue(common_params_audio_linein_, PARAM), + .nparams = ARRAY_SIZE(glue(common_params_audio_linein_, PARAM)), + .config = ((AC_JACK_PORT_COMPLEX << AC_DEFCFG_PORT_CONN_SHIFT) | + (AC_JACK_MIC_IN << AC_DEFCFG_DEVICE_SHIFT) | + (AC_JACK_CONN_UNKNOWN << AC_DEFCFG_CONN_TYPE_SHIFT) | + (AC_JACK_COLOR_RED << AC_DEFCFG_COLOR_SHIFT) | + 0x20), + .pinctl = AC_PINCTL_IN_EN, + } +}; + +/* micro: codec */ +static const desc_codec glue(micro_, PARAM) = { + .name = "micro", + .iid = QEMU_HDA_ID_MICRO, + .nodes = glue(micro_nodes_, PARAM), + .nnodes = ARRAY_SIZE(glue(micro_nodes_, PARAM)), +}; + +#undef PARAM +#undef HDA_MIXER +#undef QEMU_HDA_ID_OUTPUT +#undef QEMU_HDA_ID_DUPLEX +#undef QEMU_HDA_ID_MICRO +#undef QEMU_HDA_AMP_CAPS diff --git a/qemu/hw/audio/hda-codec.c b/qemu/hw/audio/hda-codec.c new file mode 100644 index 000000000..3c03ff566 --- /dev/null +++ b/qemu/hw/audio/hda-codec.c @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Gerd Hoffmann + * + * 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 2 or + * (at your option) version 3 of the License. + * + * 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 . + */ + +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "intel-hda.h" +#include "intel-hda-defs.h" +#include "audio/audio.h" + +/* -------------------------------------------------------------------------- */ + +typedef struct desc_param { + uint32_t id; + uint32_t val; +} desc_param; + +typedef struct desc_node { + uint32_t nid; + const char *name; + const desc_param *params; + uint32_t nparams; + uint32_t config; + uint32_t pinctl; + uint32_t *conn; + uint32_t stindex; +} desc_node; + +typedef struct desc_codec { + const char *name; + uint32_t iid; + const desc_node *nodes; + uint32_t nnodes; +} desc_codec; + +static const desc_param* hda_codec_find_param(const desc_node *node, uint32_t id) +{ + int i; + + for (i = 0; i < node->nparams; i++) { + if (node->params[i].id == id) { + return &node->params[i]; + } + } + return NULL; +} + +static const desc_node* hda_codec_find_node(const desc_codec *codec, uint32_t nid) +{ + int i; + + for (i = 0; i < codec->nnodes; i++) { + if (codec->nodes[i].nid == nid) { + return &codec->nodes[i]; + } + } + return NULL; +} + +static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) +{ + if (format & AC_FMT_TYPE_NON_PCM) { + return; + } + + as->freq = (format & AC_FMT_BASE_44K) ? 44100 : 48000; + + switch ((format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT) { + case 1: as->freq *= 2; break; + case 2: as->freq *= 3; break; + case 3: as->freq *= 4; break; + } + + switch ((format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT) { + case 1: as->freq /= 2; break; + case 2: as->freq /= 3; break; + case 3: as->freq /= 4; break; + case 4: as->freq /= 5; break; + case 5: as->freq /= 6; break; + case 6: as->freq /= 7; break; + case 7: as->freq /= 8; break; + } + + switch (format & AC_FMT_BITS_MASK) { + case AC_FMT_BITS_8: as->fmt = AUD_FMT_S8; break; + case AC_FMT_BITS_16: as->fmt = AUD_FMT_S16; break; + case AC_FMT_BITS_32: as->fmt = AUD_FMT_S32; break; + } + + as->nchannels = ((format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT) + 1; +} + +/* -------------------------------------------------------------------------- */ +/* + * HDA codec descriptions + */ + +/* some defines */ + +#define QEMU_HDA_ID_VENDOR 0x1af4 +#define QEMU_HDA_PCM_FORMATS (AC_SUPPCM_BITS_16 | \ + 0x1fc /* 16 -> 96 kHz */) +#define QEMU_HDA_AMP_NONE (0) +#define QEMU_HDA_AMP_STEPS 0x4a + +#define PARAM mixemu +#define HDA_MIXER +#include "hda-codec-common.h" + +#define PARAM nomixemu +#include "hda-codec-common.h" + +/* -------------------------------------------------------------------------- */ + +static const char *fmt2name[] = { + [ AUD_FMT_U8 ] = "PCM-U8", + [ AUD_FMT_S8 ] = "PCM-S8", + [ AUD_FMT_U16 ] = "PCM-U16", + [ AUD_FMT_S16 ] = "PCM-S16", + [ AUD_FMT_U32 ] = "PCM-U32", + [ AUD_FMT_S32 ] = "PCM-S32", +}; + +typedef struct HDAAudioState HDAAudioState; +typedef struct HDAAudioStream HDAAudioStream; + +struct HDAAudioStream { + HDAAudioState *state; + const desc_node *node; + bool output, running; + uint32_t stream; + uint32_t channel; + uint32_t format; + uint32_t gain_left, gain_right; + bool mute_left, mute_right; + struct audsettings as; + union { + SWVoiceIn *in; + SWVoiceOut *out; + } voice; + uint8_t buf[HDA_BUFFER_SIZE]; + uint32_t bpos; +}; + +#define TYPE_HDA_AUDIO "hda-audio" +#define HDA_AUDIO(obj) OBJECT_CHECK(HDAAudioState, (obj), TYPE_HDA_AUDIO) + +struct HDAAudioState { + HDACodecDevice hda; + const char *name; + + QEMUSoundCard card; + const desc_codec *desc; + HDAAudioStream st[4]; + bool running_compat[16]; + bool running_real[2 * 16]; + + /* properties */ + uint32_t debug; + bool mixer; +}; + +static void hda_audio_input_cb(void *opaque, int avail) +{ + HDAAudioStream *st = opaque; + int recv = 0; + int len; + bool rc; + + while (avail - recv >= sizeof(st->buf)) { + if (st->bpos != sizeof(st->buf)) { + len = AUD_read(st->voice.in, st->buf + st->bpos, + sizeof(st->buf) - st->bpos); + st->bpos += len; + recv += len; + if (st->bpos != sizeof(st->buf)) { + break; + } + } + rc = hda_codec_xfer(&st->state->hda, st->stream, false, + st->buf, sizeof(st->buf)); + if (!rc) { + break; + } + st->bpos = 0; + } +} + +static void hda_audio_output_cb(void *opaque, int avail) +{ + HDAAudioStream *st = opaque; + int sent = 0; + int len; + bool rc; + + while (avail - sent >= sizeof(st->buf)) { + if (st->bpos == sizeof(st->buf)) { + rc = hda_codec_xfer(&st->state->hda, st->stream, true, + st->buf, sizeof(st->buf)); + if (!rc) { + break; + } + st->bpos = 0; + } + len = AUD_write(st->voice.out, st->buf + st->bpos, + sizeof(st->buf) - st->bpos); + st->bpos += len; + sent += len; + if (st->bpos != sizeof(st->buf)) { + break; + } + } +} + +static void hda_audio_set_running(HDAAudioStream *st, bool running) +{ + if (st->node == NULL) { + return; + } + if (st->running == running) { + return; + } + st->running = running; + dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name, + st->running ? "on" : "off", st->stream); + if (st->output) { + AUD_set_active_out(st->voice.out, st->running); + } else { + AUD_set_active_in(st->voice.in, st->running); + } +} + +static void hda_audio_set_amp(HDAAudioStream *st) +{ + bool muted; + uint32_t left, right; + + if (st->node == NULL) { + return; + } + + muted = st->mute_left && st->mute_right; + left = st->mute_left ? 0 : st->gain_left; + right = st->mute_right ? 0 : st->gain_right; + + left = left * 255 / QEMU_HDA_AMP_STEPS; + right = right * 255 / QEMU_HDA_AMP_STEPS; + + if (!st->state->mixer) { + return; + } + if (st->output) { + AUD_set_volume_out(st->voice.out, muted, left, right); + } else { + AUD_set_volume_in(st->voice.in, muted, left, right); + } +} + +static void hda_audio_setup(HDAAudioStream *st) +{ + if (st->node == NULL) { + return; + } + + dprint(st->state, 1, "%s: format: %d x %s @ %d Hz\n", + st->node->name, st->as.nchannels, + fmt2name[st->as.fmt], st->as.freq); + + if (st->output) { + st->voice.out = AUD_open_out(&st->state->card, st->voice.out, + st->node->name, st, + hda_audio_output_cb, &st->as); + } else { + st->voice.in = AUD_open_in(&st->state->card, st->voice.in, + st->node->name, st, + hda_audio_input_cb, &st->as); + } +} + +static void hda_audio_command(HDACodecDevice *hda, uint32_t nid, uint32_t data) +{ + HDAAudioState *a = HDA_AUDIO(hda); + HDAAudioStream *st; + const desc_node *node = NULL; + const desc_param *param; + uint32_t verb, payload, response, count, shift; + + if ((data & 0x70000) == 0x70000) { + /* 12/8 id/payload */ + verb = (data >> 8) & 0xfff; + payload = data & 0x00ff; + } else { + /* 4/16 id/payload */ + verb = (data >> 8) & 0xf00; + payload = data & 0xffff; + } + + node = hda_codec_find_node(a->desc, nid); + if (node == NULL) { + goto fail; + } + dprint(a, 2, "%s: nid %d (%s), verb 0x%x, payload 0x%x\n", + __FUNCTION__, nid, node->name, verb, payload); + + switch (verb) { + /* all nodes */ + case AC_VERB_PARAMETERS: + param = hda_codec_find_param(node, payload); + if (param == NULL) { + goto fail; + } + hda_codec_response(hda, true, param->val); + break; + case AC_VERB_GET_SUBSYSTEM_ID: + hda_codec_response(hda, true, a->desc->iid); + break; + + /* all functions */ + case AC_VERB_GET_CONNECT_LIST: + param = hda_codec_find_param(node, AC_PAR_CONNLIST_LEN); + count = param ? param->val : 0; + response = 0; + shift = 0; + while (payload < count && shift < 32) { + response |= node->conn[payload] << shift; + payload++; + shift += 8; + } + hda_codec_response(hda, true, response); + break; + + /* pin widget */ + case AC_VERB_GET_CONFIG_DEFAULT: + hda_codec_response(hda, true, node->config); + break; + case AC_VERB_GET_PIN_WIDGET_CONTROL: + hda_codec_response(hda, true, node->pinctl); + break; + case AC_VERB_SET_PIN_WIDGET_CONTROL: + if (node->pinctl != payload) { + dprint(a, 1, "unhandled pin control bit\n"); + } + hda_codec_response(hda, true, 0); + break; + + /* audio in/out widget */ + case AC_VERB_SET_CHANNEL_STREAMID: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + hda_audio_set_running(st, false); + st->stream = (payload >> 4) & 0x0f; + st->channel = payload & 0x0f; + dprint(a, 2, "%s: stream %d, channel %d\n", + st->node->name, st->stream, st->channel); + hda_audio_set_running(st, a->running_real[st->output * 16 + st->stream]); + hda_codec_response(hda, true, 0); + break; + case AC_VERB_GET_CONV: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + response = st->stream << 4 | st->channel; + hda_codec_response(hda, true, response); + break; + case AC_VERB_SET_STREAM_FORMAT: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + st->format = payload; + hda_codec_parse_fmt(st->format, &st->as); + hda_audio_setup(st); + hda_codec_response(hda, true, 0); + break; + case AC_VERB_GET_STREAM_FORMAT: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + hda_codec_response(hda, true, st->format); + break; + case AC_VERB_GET_AMP_GAIN_MUTE: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + if (payload & AC_AMP_GET_LEFT) { + response = st->gain_left | (st->mute_left ? AC_AMP_MUTE : 0); + } else { + response = st->gain_right | (st->mute_right ? AC_AMP_MUTE : 0); + } + hda_codec_response(hda, true, response); + break; + case AC_VERB_SET_AMP_GAIN_MUTE: + st = a->st + node->stindex; + if (st->node == NULL) { + goto fail; + } + dprint(a, 1, "amp (%s): %s%s%s%s index %d gain %3d %s\n", + st->node->name, + (payload & AC_AMP_SET_OUTPUT) ? "o" : "-", + (payload & AC_AMP_SET_INPUT) ? "i" : "-", + (payload & AC_AMP_SET_LEFT) ? "l" : "-", + (payload & AC_AMP_SET_RIGHT) ? "r" : "-", + (payload & AC_AMP_SET_INDEX) >> AC_AMP_SET_INDEX_SHIFT, + (payload & AC_AMP_GAIN), + (payload & AC_AMP_MUTE) ? "muted" : ""); + if (payload & AC_AMP_SET_LEFT) { + st->gain_left = payload & AC_AMP_GAIN; + st->mute_left = payload & AC_AMP_MUTE; + } + if (payload & AC_AMP_SET_RIGHT) { + st->gain_right = payload & AC_AMP_GAIN; + st->mute_right = payload & AC_AMP_MUTE; + } + hda_audio_set_amp(st); + hda_codec_response(hda, true, 0); + break; + + /* not supported */ + case AC_VERB_SET_POWER_STATE: + case AC_VERB_GET_POWER_STATE: + case AC_VERB_GET_SDI_SELECT: + hda_codec_response(hda, true, 0); + break; + default: + goto fail; + } + return; + +fail: + dprint(a, 1, "%s: not handled: nid %d (%s), verb 0x%x, payload 0x%x\n", + __FUNCTION__, nid, node ? node->name : "?", verb, payload); + hda_codec_response(hda, true, 0); +} + +static void hda_audio_stream(HDACodecDevice *hda, uint32_t stnr, bool running, bool output) +{ + HDAAudioState *a = HDA_AUDIO(hda); + int s; + + a->running_compat[stnr] = running; + a->running_real[output * 16 + stnr] = running; + for (s = 0; s < ARRAY_SIZE(a->st); s++) { + if (a->st[s].node == NULL) { + continue; + } + if (a->st[s].output != output) { + continue; + } + if (a->st[s].stream != stnr) { + continue; + } + hda_audio_set_running(&a->st[s], running); + } +} + +static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc) +{ + HDAAudioState *a = HDA_AUDIO(hda); + HDAAudioStream *st; + const desc_node *node; + const desc_param *param; + uint32_t i, type; + + a->desc = desc; + a->name = object_get_typename(OBJECT(a)); + dprint(a, 1, "%s: cad %d\n", __FUNCTION__, a->hda.cad); + + AUD_register_card("hda", &a->card); + for (i = 0; i < a->desc->nnodes; i++) { + node = a->desc->nodes + i; + param = hda_codec_find_param(node, AC_PAR_AUDIO_WIDGET_CAP); + if (param == NULL) { + continue; + } + type = (param->val & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + switch (type) { + case AC_WID_AUD_OUT: + case AC_WID_AUD_IN: + assert(node->stindex < ARRAY_SIZE(a->st)); + st = a->st + node->stindex; + st->state = a; + st->node = node; + if (type == AC_WID_AUD_OUT) { + /* unmute output by default */ + st->gain_left = QEMU_HDA_AMP_STEPS; + st->gain_right = QEMU_HDA_AMP_STEPS; + st->bpos = sizeof(st->buf); + st->output = true; + } else { + st->output = false; + } + st->format = AC_FMT_TYPE_PCM | AC_FMT_BITS_16 | + (1 << AC_FMT_CHAN_SHIFT); + hda_codec_parse_fmt(st->format, &st->as); + hda_audio_setup(st); + break; + } + } + return 0; +} + +static int hda_audio_exit(HDACodecDevice *hda) +{ + HDAAudioState *a = HDA_AUDIO(hda); + HDAAudioStream *st; + int i; + + dprint(a, 1, "%s\n", __FUNCTION__); + for (i = 0; i < ARRAY_SIZE(a->st); i++) { + st = a->st + i; + if (st->node == NULL) { + continue; + } + if (st->output) { + AUD_close_out(&a->card, st->voice.out); + } else { + AUD_close_in(&a->card, st->voice.in); + } + } + AUD_remove_card(&a->card); + return 0; +} + +static int hda_audio_post_load(void *opaque, int version) +{ + HDAAudioState *a = opaque; + HDAAudioStream *st; + int i; + + dprint(a, 1, "%s\n", __FUNCTION__); + if (version == 1) { + /* assume running_compat[] is for output streams */ + for (i = 0; i < ARRAY_SIZE(a->running_compat); i++) + a->running_real[16 + i] = a->running_compat[i]; + } + + for (i = 0; i < ARRAY_SIZE(a->st); i++) { + st = a->st + i; + if (st->node == NULL) + continue; + hda_codec_parse_fmt(st->format, &st->as); + hda_audio_setup(st); + hda_audio_set_amp(st); + hda_audio_set_running(st, a->running_real[st->output * 16 + st->stream]); + } + return 0; +} + +static void hda_audio_reset(DeviceState *dev) +{ + HDAAudioState *a = HDA_AUDIO(dev); + HDAAudioStream *st; + int i; + + dprint(a, 1, "%s\n", __func__); + for (i = 0; i < ARRAY_SIZE(a->st); i++) { + st = a->st + i; + if (st->node != NULL) { + hda_audio_set_running(st, false); + } + } +} + +static const VMStateDescription vmstate_hda_audio_stream = { + .name = "hda-audio-stream", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(stream, HDAAudioStream), + VMSTATE_UINT32(channel, HDAAudioStream), + VMSTATE_UINT32(format, HDAAudioStream), + VMSTATE_UINT32(gain_left, HDAAudioStream), + VMSTATE_UINT32(gain_right, HDAAudioStream), + VMSTATE_BOOL(mute_left, HDAAudioStream), + VMSTATE_BOOL(mute_right, HDAAudioStream), + VMSTATE_UINT32(bpos, HDAAudioStream), + VMSTATE_BUFFER(buf, HDAAudioStream), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_hda_audio = { + .name = "hda-audio", + .version_id = 2, + .post_load = hda_audio_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(st, HDAAudioState, 4, 0, + vmstate_hda_audio_stream, + HDAAudioStream), + VMSTATE_BOOL_ARRAY(running_compat, HDAAudioState, 16), + VMSTATE_BOOL_ARRAY_V(running_real, HDAAudioState, 2 * 16, 2), + VMSTATE_END_OF_LIST() + } +}; + +static Property hda_audio_properties[] = { + DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0), + DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static int hda_audio_init_output(HDACodecDevice *hda) +{ + HDAAudioState *a = HDA_AUDIO(hda); + + if (!a->mixer) { + return hda_audio_init(hda, &output_nomixemu); + } else { + return hda_audio_init(hda, &output_mixemu); + } +} + +static int hda_audio_init_duplex(HDACodecDevice *hda) +{ + HDAAudioState *a = HDA_AUDIO(hda); + + if (!a->mixer) { + return hda_audio_init(hda, &duplex_nomixemu); + } else { + return hda_audio_init(hda, &duplex_mixemu); + } +} + +static int hda_audio_init_micro(HDACodecDevice *hda) +{ + HDAAudioState *a = HDA_AUDIO(hda); + + if (!a->mixer) { + return hda_audio_init(hda, µ_nomixemu); + } else { + return hda_audio_init(hda, µ_mixemu); + } +} + +static void hda_audio_base_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass); + + k->exit = hda_audio_exit; + k->command = hda_audio_command; + k->stream = hda_audio_stream; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->reset = hda_audio_reset; + dc->vmsd = &vmstate_hda_audio; + dc->props = hda_audio_properties; +} + +static const TypeInfo hda_audio_info = { + .name = TYPE_HDA_AUDIO, + .parent = TYPE_HDA_CODEC_DEVICE, + .class_init = hda_audio_base_class_init, + .abstract = true, +}; + +static void hda_audio_output_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass); + + k->init = hda_audio_init_output; + dc->desc = "HDA Audio Codec, output-only (line-out)"; +} + +static const TypeInfo hda_audio_output_info = { + .name = "hda-output", + .parent = TYPE_HDA_AUDIO, + .instance_size = sizeof(HDAAudioState), + .class_init = hda_audio_output_class_init, +}; + +static void hda_audio_duplex_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass); + + k->init = hda_audio_init_duplex; + dc->desc = "HDA Audio Codec, duplex (line-out, line-in)"; +} + +static const TypeInfo hda_audio_duplex_info = { + .name = "hda-duplex", + .parent = TYPE_HDA_AUDIO, + .instance_size = sizeof(HDAAudioState), + .class_init = hda_audio_duplex_class_init, +}; + +static void hda_audio_micro_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass); + + k->init = hda_audio_init_micro; + dc->desc = "HDA Audio Codec, duplex (speaker, microphone)"; +} + +static const TypeInfo hda_audio_micro_info = { + .name = "hda-micro", + .parent = TYPE_HDA_AUDIO, + .instance_size = sizeof(HDAAudioState), + .class_init = hda_audio_micro_class_init, +}; + +static void hda_audio_register_types(void) +{ + type_register_static(&hda_audio_info); + type_register_static(&hda_audio_output_info); + type_register_static(&hda_audio_duplex_info); + type_register_static(&hda_audio_micro_info); +} + +type_init(hda_audio_register_types) diff --git a/qemu/hw/audio/intel-hda-defs.h b/qemu/hw/audio/intel-hda-defs.h new file mode 100644 index 000000000..2e37e5b87 --- /dev/null +++ b/qemu/hw/audio/intel-hda-defs.h @@ -0,0 +1,717 @@ +#ifndef HW_INTEL_HDA_DEFS_H +#define HW_INTEL_HDA_DEFS_H + +/* qemu */ +#define HDA_BUFFER_SIZE 256 + +/* --------------------------------------------------------------------- */ +/* from linux/sound/pci/hda/hda_intel.c */ + +/* + * registers + */ +#define ICH6_REG_GCAP 0x00 +#define ICH6_GCAP_64OK (1 << 0) /* 64bit address support */ +#define ICH6_GCAP_NSDO (3 << 1) /* # of serial data out signals */ +#define ICH6_GCAP_BSS (31 << 3) /* # of bidirectional streams */ +#define ICH6_GCAP_ISS (15 << 8) /* # of input streams */ +#define ICH6_GCAP_OSS (15 << 12) /* # of output streams */ +#define ICH6_REG_VMIN 0x02 +#define ICH6_REG_VMAJ 0x03 +#define ICH6_REG_OUTPAY 0x04 +#define ICH6_REG_INPAY 0x06 +#define ICH6_REG_GCTL 0x08 +#define ICH6_GCTL_RESET (1 << 0) /* controller reset */ +#define ICH6_GCTL_FCNTRL (1 << 1) /* flush control */ +#define ICH6_GCTL_UNSOL (1 << 8) /* accept unsol. response enable */ +#define ICH6_REG_WAKEEN 0x0c +#define ICH6_REG_STATESTS 0x0e +#define ICH6_REG_GSTS 0x10 +#define ICH6_GSTS_FSTS (1 << 1) /* flush status */ +#define ICH6_REG_INTCTL 0x20 +#define ICH6_REG_INTSTS 0x24 +#define ICH6_REG_WALLCLK 0x30 /* 24Mhz source */ +#define ICH6_REG_SYNC 0x34 +#define ICH6_REG_CORBLBASE 0x40 +#define ICH6_REG_CORBUBASE 0x44 +#define ICH6_REG_CORBWP 0x48 +#define ICH6_REG_CORBRP 0x4a +#define ICH6_CORBRP_RST (1 << 15) /* read pointer reset */ +#define ICH6_REG_CORBCTL 0x4c +#define ICH6_CORBCTL_RUN (1 << 1) /* enable DMA */ +#define ICH6_CORBCTL_CMEIE (1 << 0) /* enable memory error irq */ +#define ICH6_REG_CORBSTS 0x4d +#define ICH6_CORBSTS_CMEI (1 << 0) /* memory error indication */ +#define ICH6_REG_CORBSIZE 0x4e + +#define ICH6_REG_RIRBLBASE 0x50 +#define ICH6_REG_RIRBUBASE 0x54 +#define ICH6_REG_RIRBWP 0x58 +#define ICH6_RIRBWP_RST (1 << 15) /* write pointer reset */ +#define ICH6_REG_RINTCNT 0x5a +#define ICH6_REG_RIRBCTL 0x5c +#define ICH6_RBCTL_IRQ_EN (1 << 0) /* enable IRQ */ +#define ICH6_RBCTL_DMA_EN (1 << 1) /* enable DMA */ +#define ICH6_RBCTL_OVERRUN_EN (1 << 2) /* enable overrun irq */ +#define ICH6_REG_RIRBSTS 0x5d +#define ICH6_RBSTS_IRQ (1 << 0) /* response irq */ +#define ICH6_RBSTS_OVERRUN (1 << 2) /* overrun irq */ +#define ICH6_REG_RIRBSIZE 0x5e + +#define ICH6_REG_IC 0x60 +#define ICH6_REG_IR 0x64 +#define ICH6_REG_IRS 0x68 +#define ICH6_IRS_VALID (1<<1) +#define ICH6_IRS_BUSY (1<<0) + +#define ICH6_REG_DPLBASE 0x70 +#define ICH6_REG_DPUBASE 0x74 +#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */ + +/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ +enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; + +/* stream register offsets from stream base */ +#define ICH6_REG_SD_CTL 0x00 +#define ICH6_REG_SD_STS 0x03 +#define ICH6_REG_SD_LPIB 0x04 +#define ICH6_REG_SD_CBL 0x08 +#define ICH6_REG_SD_LVI 0x0c +#define ICH6_REG_SD_FIFOW 0x0e +#define ICH6_REG_SD_FIFOSIZE 0x10 +#define ICH6_REG_SD_FORMAT 0x12 +#define ICH6_REG_SD_BDLPL 0x18 +#define ICH6_REG_SD_BDLPU 0x1c + +/* PCI space */ +#define ICH6_PCIREG_TCSEL 0x44 + +/* + * other constants + */ + +/* max number of SDs */ +/* ICH, ATI and VIA have 4 playback and 4 capture */ +#define ICH6_NUM_CAPTURE 4 +#define ICH6_NUM_PLAYBACK 4 + +/* ULI has 6 playback and 5 capture */ +#define ULI_NUM_CAPTURE 5 +#define ULI_NUM_PLAYBACK 6 + +/* ATI HDMI has 1 playback and 0 capture */ +#define ATIHDMI_NUM_CAPTURE 0 +#define ATIHDMI_NUM_PLAYBACK 1 + +/* TERA has 4 playback and 3 capture */ +#define TERA_NUM_CAPTURE 3 +#define TERA_NUM_PLAYBACK 4 + +/* this number is statically defined for simplicity */ +#define MAX_AZX_DEV 16 + +/* max number of fragments - we may use more if allocating more pages for BDL */ +#define BDL_SIZE 4096 +#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16) +#define AZX_MAX_FRAG 32 +/* max buffer size - no h/w limit, you can increase as you like */ +#define AZX_MAX_BUF_SIZE (1024*1024*1024) + +/* RIRB int mask: overrun[2], response[0] */ +#define RIRB_INT_RESPONSE 0x01 +#define RIRB_INT_OVERRUN 0x04 +#define RIRB_INT_MASK 0x05 + +/* STATESTS int mask: S3,SD2,SD1,SD0 */ +#define AZX_MAX_CODECS 8 +#define AZX_DEFAULT_CODECS 4 +#define STATESTS_INT_MASK ((1 << AZX_MAX_CODECS) - 1) + +/* SD_CTL bits */ +#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */ +#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */ +#define SD_CTL_STRIPE (3 << 16) /* stripe control */ +#define SD_CTL_TRAFFIC_PRIO (1 << 18) /* traffic priority */ +#define SD_CTL_DIR (1 << 19) /* bi-directional stream */ +#define SD_CTL_STREAM_TAG_MASK (0xf << 20) +#define SD_CTL_STREAM_TAG_SHIFT 20 + +/* SD_CTL and SD_STS */ +#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */ +#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */ +#define SD_INT_COMPLETE 0x04 /* completion interrupt */ +#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\ + SD_INT_COMPLETE) + +/* SD_STS */ +#define SD_STS_FIFO_READY 0x20 /* FIFO ready */ + +/* INTCTL and INTSTS */ +#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */ +#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */ +#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */ + +/* below are so far hardcoded - should read registers in future */ +#define ICH6_MAX_CORB_ENTRIES 256 +#define ICH6_MAX_RIRB_ENTRIES 256 + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, +}; + +/* Defines for ATI HD Audio support in SB450 south bridge */ +#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42 +#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02 + +/* Defines for Nvidia HDA support */ +#define NVIDIA_HDA_TRANSREG_ADDR 0x4e +#define NVIDIA_HDA_ENABLE_COHBITS 0x0f +#define NVIDIA_HDA_ISTRM_COH 0x4d +#define NVIDIA_HDA_OSTRM_COH 0x4c +#define NVIDIA_HDA_ENABLE_COHBIT 0x01 + +/* Defines for Intel SCH HDA snoop control */ +#define INTEL_SCH_HDA_DEVC 0x78 +#define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11) + +/* Define IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 +/* Define VIA HD Audio Device ID*/ +#define VIA_HDAC_DEVICE_ID 0x3288 + +/* HD Audio class code */ +#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403 + +/* --------------------------------------------------------------------- */ +/* from linux/sound/pci/hda/hda_codec.h */ + +/* + * nodes + */ +#define AC_NODE_ROOT 0x00 + +/* + * function group types + */ +enum { + AC_GRP_AUDIO_FUNCTION = 0x01, + AC_GRP_MODEM_FUNCTION = 0x02, +}; + +/* + * widget types + */ +enum { + AC_WID_AUD_OUT, /* Audio Out */ + AC_WID_AUD_IN, /* Audio In */ + AC_WID_AUD_MIX, /* Audio Mixer */ + AC_WID_AUD_SEL, /* Audio Selector */ + AC_WID_PIN, /* Pin Complex */ + AC_WID_POWER, /* Power */ + AC_WID_VOL_KNB, /* Volume Knob */ + AC_WID_BEEP, /* Beep Generator */ + AC_WID_VENDOR = 0x0f /* Vendor specific */ +}; + +/* + * GET verbs + */ +#define AC_VERB_GET_STREAM_FORMAT 0x0a00 +#define AC_VERB_GET_AMP_GAIN_MUTE 0x0b00 +#define AC_VERB_GET_PROC_COEF 0x0c00 +#define AC_VERB_GET_COEF_INDEX 0x0d00 +#define AC_VERB_PARAMETERS 0x0f00 +#define AC_VERB_GET_CONNECT_SEL 0x0f01 +#define AC_VERB_GET_CONNECT_LIST 0x0f02 +#define AC_VERB_GET_PROC_STATE 0x0f03 +#define AC_VERB_GET_SDI_SELECT 0x0f04 +#define AC_VERB_GET_POWER_STATE 0x0f05 +#define AC_VERB_GET_CONV 0x0f06 +#define AC_VERB_GET_PIN_WIDGET_CONTROL 0x0f07 +#define AC_VERB_GET_UNSOLICITED_RESPONSE 0x0f08 +#define AC_VERB_GET_PIN_SENSE 0x0f09 +#define AC_VERB_GET_BEEP_CONTROL 0x0f0a +#define AC_VERB_GET_EAPD_BTLENABLE 0x0f0c +#define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d +#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */ +#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f +/* f10-f1a: GPIO */ +#define AC_VERB_GET_GPIO_DATA 0x0f15 +#define AC_VERB_GET_GPIO_MASK 0x0f16 +#define AC_VERB_GET_GPIO_DIRECTION 0x0f17 +#define AC_VERB_GET_GPIO_WAKE_MASK 0x0f18 +#define AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK 0x0f19 +#define AC_VERB_GET_GPIO_STICKY_MASK 0x0f1a +#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c +/* f20: AFG/MFG */ +#define AC_VERB_GET_SUBSYSTEM_ID 0x0f20 +#define AC_VERB_GET_CVT_CHAN_COUNT 0x0f2d +#define AC_VERB_GET_HDMI_DIP_SIZE 0x0f2e +#define AC_VERB_GET_HDMI_ELDD 0x0f2f +#define AC_VERB_GET_HDMI_DIP_INDEX 0x0f30 +#define AC_VERB_GET_HDMI_DIP_DATA 0x0f31 +#define AC_VERB_GET_HDMI_DIP_XMIT 0x0f32 +#define AC_VERB_GET_HDMI_CP_CTRL 0x0f33 +#define AC_VERB_GET_HDMI_CHAN_SLOT 0x0f34 + +/* + * SET verbs + */ +#define AC_VERB_SET_STREAM_FORMAT 0x200 +#define AC_VERB_SET_AMP_GAIN_MUTE 0x300 +#define AC_VERB_SET_PROC_COEF 0x400 +#define AC_VERB_SET_COEF_INDEX 0x500 +#define AC_VERB_SET_CONNECT_SEL 0x701 +#define AC_VERB_SET_PROC_STATE 0x703 +#define AC_VERB_SET_SDI_SELECT 0x704 +#define AC_VERB_SET_POWER_STATE 0x705 +#define AC_VERB_SET_CHANNEL_STREAMID 0x706 +#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x707 +#define AC_VERB_SET_UNSOLICITED_ENABLE 0x708 +#define AC_VERB_SET_PIN_SENSE 0x709 +#define AC_VERB_SET_BEEP_CONTROL 0x70a +#define AC_VERB_SET_EAPD_BTLENABLE 0x70c +#define AC_VERB_SET_DIGI_CONVERT_1 0x70d +#define AC_VERB_SET_DIGI_CONVERT_2 0x70e +#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f +#define AC_VERB_SET_GPIO_DATA 0x715 +#define AC_VERB_SET_GPIO_MASK 0x716 +#define AC_VERB_SET_GPIO_DIRECTION 0x717 +#define AC_VERB_SET_GPIO_WAKE_MASK 0x718 +#define AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK 0x719 +#define AC_VERB_SET_GPIO_STICKY_MASK 0x71a +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e +#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f +#define AC_VERB_SET_EAPD 0x788 +#define AC_VERB_SET_CODEC_RESET 0x7ff +#define AC_VERB_SET_CVT_CHAN_COUNT 0x72d +#define AC_VERB_SET_HDMI_DIP_INDEX 0x730 +#define AC_VERB_SET_HDMI_DIP_DATA 0x731 +#define AC_VERB_SET_HDMI_DIP_XMIT 0x732 +#define AC_VERB_SET_HDMI_CP_CTRL 0x733 +#define AC_VERB_SET_HDMI_CHAN_SLOT 0x734 + +/* + * Parameter IDs + */ +#define AC_PAR_VENDOR_ID 0x00 +#define AC_PAR_SUBSYSTEM_ID 0x01 +#define AC_PAR_REV_ID 0x02 +#define AC_PAR_NODE_COUNT 0x04 +#define AC_PAR_FUNCTION_TYPE 0x05 +#define AC_PAR_AUDIO_FG_CAP 0x08 +#define AC_PAR_AUDIO_WIDGET_CAP 0x09 +#define AC_PAR_PCM 0x0a +#define AC_PAR_STREAM 0x0b +#define AC_PAR_PIN_CAP 0x0c +#define AC_PAR_AMP_IN_CAP 0x0d +#define AC_PAR_CONNLIST_LEN 0x0e +#define AC_PAR_POWER_STATE 0x0f +#define AC_PAR_PROC_CAP 0x10 +#define AC_PAR_GPIO_CAP 0x11 +#define AC_PAR_AMP_OUT_CAP 0x12 +#define AC_PAR_VOL_KNB_CAP 0x13 +#define AC_PAR_HDMI_LPCM_CAP 0x20 + +/* + * AC_VERB_PARAMETERS results (32bit) + */ + +/* Function Group Type */ +#define AC_FGT_TYPE (0xff<<0) +#define AC_FGT_TYPE_SHIFT 0 +#define AC_FGT_UNSOL_CAP (1<<8) + +/* Audio Function Group Capabilities */ +#define AC_AFG_OUT_DELAY (0xf<<0) +#define AC_AFG_IN_DELAY (0xf<<8) +#define AC_AFG_BEEP_GEN (1<<16) + +/* Audio Widget Capabilities */ +#define AC_WCAP_STEREO (1<<0) /* stereo I/O */ +#define AC_WCAP_IN_AMP (1<<1) /* AMP-in present */ +#define AC_WCAP_OUT_AMP (1<<2) /* AMP-out present */ +#define AC_WCAP_AMP_OVRD (1<<3) /* AMP-parameter override */ +#define AC_WCAP_FORMAT_OVRD (1<<4) /* format override */ +#define AC_WCAP_STRIPE (1<<5) /* stripe */ +#define AC_WCAP_PROC_WID (1<<6) /* Proc Widget */ +#define AC_WCAP_UNSOL_CAP (1<<7) /* Unsol capable */ +#define AC_WCAP_CONN_LIST (1<<8) /* connection list */ +#define AC_WCAP_DIGITAL (1<<9) /* digital I/O */ +#define AC_WCAP_POWER (1<<10) /* power control */ +#define AC_WCAP_LR_SWAP (1<<11) /* L/R swap */ +#define AC_WCAP_CP_CAPS (1<<12) /* content protection */ +#define AC_WCAP_CHAN_CNT_EXT (7<<13) /* channel count ext */ +#define AC_WCAP_DELAY (0xf<<16) +#define AC_WCAP_DELAY_SHIFT 16 +#define AC_WCAP_TYPE (0xf<<20) +#define AC_WCAP_TYPE_SHIFT 20 + +/* supported PCM rates and bits */ +#define AC_SUPPCM_RATES (0xfff << 0) +#define AC_SUPPCM_BITS_8 (1<<16) +#define AC_SUPPCM_BITS_16 (1<<17) +#define AC_SUPPCM_BITS_20 (1<<18) +#define AC_SUPPCM_BITS_24 (1<<19) +#define AC_SUPPCM_BITS_32 (1<<20) + +/* supported PCM stream format */ +#define AC_SUPFMT_PCM (1<<0) +#define AC_SUPFMT_FLOAT32 (1<<1) +#define AC_SUPFMT_AC3 (1<<2) + +/* GP I/O count */ +#define AC_GPIO_IO_COUNT (0xff<<0) +#define AC_GPIO_O_COUNT (0xff<<8) +#define AC_GPIO_O_COUNT_SHIFT 8 +#define AC_GPIO_I_COUNT (0xff<<16) +#define AC_GPIO_I_COUNT_SHIFT 16 +#define AC_GPIO_UNSOLICITED (1<<30) +#define AC_GPIO_WAKE (1<<31) + +/* Converter stream, channel */ +#define AC_CONV_CHANNEL (0xf<<0) +#define AC_CONV_STREAM (0xf<<4) +#define AC_CONV_STREAM_SHIFT 4 + +/* Input converter SDI select */ +#define AC_SDI_SELECT (0xf<<0) + +/* stream format id */ +#define AC_FMT_CHAN_SHIFT 0 +#define AC_FMT_CHAN_MASK (0x0f << 0) +#define AC_FMT_BITS_SHIFT 4 +#define AC_FMT_BITS_MASK (7 << 4) +#define AC_FMT_BITS_8 (0 << 4) +#define AC_FMT_BITS_16 (1 << 4) +#define AC_FMT_BITS_20 (2 << 4) +#define AC_FMT_BITS_24 (3 << 4) +#define AC_FMT_BITS_32 (4 << 4) +#define AC_FMT_DIV_SHIFT 8 +#define AC_FMT_DIV_MASK (7 << 8) +#define AC_FMT_MULT_SHIFT 11 +#define AC_FMT_MULT_MASK (7 << 11) +#define AC_FMT_BASE_SHIFT 14 +#define AC_FMT_BASE_48K (0 << 14) +#define AC_FMT_BASE_44K (1 << 14) +#define AC_FMT_TYPE_SHIFT 15 +#define AC_FMT_TYPE_PCM (0 << 15) +#define AC_FMT_TYPE_NON_PCM (1 << 15) + +/* Unsolicited response control */ +#define AC_UNSOL_TAG (0x3f<<0) +#define AC_UNSOL_ENABLED (1<<7) +#define AC_USRSP_EN AC_UNSOL_ENABLED + +/* Unsolicited responses */ +#define AC_UNSOL_RES_TAG (0x3f<<26) +#define AC_UNSOL_RES_TAG_SHIFT 26 +#define AC_UNSOL_RES_SUBTAG (0x1f<<21) +#define AC_UNSOL_RES_SUBTAG_SHIFT 21 +#define AC_UNSOL_RES_ELDV (1<<1) /* ELD Data valid (for HDMI) */ +#define AC_UNSOL_RES_PD (1<<0) /* pinsense detect */ +#define AC_UNSOL_RES_CP_STATE (1<<1) /* content protection */ +#define AC_UNSOL_RES_CP_READY (1<<0) /* content protection */ + +/* Pin widget capabilies */ +#define AC_PINCAP_IMP_SENSE (1<<0) /* impedance sense capable */ +#define AC_PINCAP_TRIG_REQ (1<<1) /* trigger required */ +#define AC_PINCAP_PRES_DETECT (1<<2) /* presence detect capable */ +#define AC_PINCAP_HP_DRV (1<<3) /* headphone drive capable */ +#define AC_PINCAP_OUT (1<<4) /* output capable */ +#define AC_PINCAP_IN (1<<5) /* input capable */ +#define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */ +/* Note: This LR_SWAP pincap is defined in the Realtek ALC883 specification, + * but is marked reserved in the Intel HDA specification. + */ +#define AC_PINCAP_LR_SWAP (1<<7) /* L/R swap */ +/* Note: The same bit as LR_SWAP is newly defined as HDMI capability + * in HD-audio specification + */ +#define AC_PINCAP_HDMI (1<<7) /* HDMI pin */ +#define AC_PINCAP_DP (1<<24) /* DisplayPort pin, can + * coexist with AC_PINCAP_HDMI + */ +#define AC_PINCAP_VREF (0x37<<8) +#define AC_PINCAP_VREF_SHIFT 8 +#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */ +#define AC_PINCAP_HBR (1<<27) /* High Bit Rate */ +/* Vref status (used in pin cap) */ +#define AC_PINCAP_VREF_HIZ (1<<0) /* Hi-Z */ +#define AC_PINCAP_VREF_50 (1<<1) /* 50% */ +#define AC_PINCAP_VREF_GRD (1<<2) /* ground */ +#define AC_PINCAP_VREF_80 (1<<4) /* 80% */ +#define AC_PINCAP_VREF_100 (1<<5) /* 100% */ + +/* Amplifier capabilities */ +#define AC_AMPCAP_OFFSET (0x7f<<0) /* 0dB offset */ +#define AC_AMPCAP_OFFSET_SHIFT 0 +#define AC_AMPCAP_NUM_STEPS (0x7f<<8) /* number of steps */ +#define AC_AMPCAP_NUM_STEPS_SHIFT 8 +#define AC_AMPCAP_STEP_SIZE (0x7f<<16) /* step size 0-32dB + * in 0.25dB + */ +#define AC_AMPCAP_STEP_SIZE_SHIFT 16 +#define AC_AMPCAP_MUTE (1<<31) /* mute capable */ +#define AC_AMPCAP_MUTE_SHIFT 31 + +/* Connection list */ +#define AC_CLIST_LENGTH (0x7f<<0) +#define AC_CLIST_LONG (1<<7) + +/* Supported power status */ +#define AC_PWRST_D0SUP (1<<0) +#define AC_PWRST_D1SUP (1<<1) +#define AC_PWRST_D2SUP (1<<2) +#define AC_PWRST_D3SUP (1<<3) +#define AC_PWRST_D3COLDSUP (1<<4) +#define AC_PWRST_S3D3COLDSUP (1<<29) +#define AC_PWRST_CLKSTOP (1<<30) +#define AC_PWRST_EPSS (1U<<31) + +/* Power state values */ +#define AC_PWRST_SETTING (0xf<<0) +#define AC_PWRST_ACTUAL (0xf<<4) +#define AC_PWRST_ACTUAL_SHIFT 4 +#define AC_PWRST_D0 0x00 +#define AC_PWRST_D1 0x01 +#define AC_PWRST_D2 0x02 +#define AC_PWRST_D3 0x03 + +/* Processing capabilies */ +#define AC_PCAP_BENIGN (1<<0) +#define AC_PCAP_NUM_COEF (0xff<<8) +#define AC_PCAP_NUM_COEF_SHIFT 8 + +/* Volume knobs capabilities */ +#define AC_KNBCAP_NUM_STEPS (0x7f<<0) +#define AC_KNBCAP_DELTA (1<<7) + +/* HDMI LPCM capabilities */ +#define AC_LPCMCAP_48K_CP_CHNS (0x0f<<0) /* max channels w/ CP-on */ +#define AC_LPCMCAP_48K_NO_CHNS (0x0f<<4) /* max channels w/o CP-on */ +#define AC_LPCMCAP_48K_20BIT (1<<8) /* 20b bitrate supported */ +#define AC_LPCMCAP_48K_24BIT (1<<9) /* 24b bitrate supported */ +#define AC_LPCMCAP_96K_CP_CHNS (0x0f<<10) /* max channels w/ CP-on */ +#define AC_LPCMCAP_96K_NO_CHNS (0x0f<<14) /* max channels w/o CP-on */ +#define AC_LPCMCAP_96K_20BIT (1<<18) /* 20b bitrate supported */ +#define AC_LPCMCAP_96K_24BIT (1<<19) /* 24b bitrate supported */ +#define AC_LPCMCAP_192K_CP_CHNS (0x0f<<20) /* max channels w/ CP-on */ +#define AC_LPCMCAP_192K_NO_CHNS (0x0f<<24) /* max channels w/o CP-on */ +#define AC_LPCMCAP_192K_20BIT (1<<28) /* 20b bitrate supported */ +#define AC_LPCMCAP_192K_24BIT (1<<29) /* 24b bitrate supported */ +#define AC_LPCMCAP_44K (1<<30) /* 44.1kHz support */ +#define AC_LPCMCAP_44K_MS (1<<31) /* 44.1kHz-multiplies support */ + +/* + * Control Parameters + */ + +/* Amp gain/mute */ +#define AC_AMP_MUTE (1<<7) +#define AC_AMP_GAIN (0x7f) +#define AC_AMP_GET_INDEX (0xf<<0) + +#define AC_AMP_GET_LEFT (1<<13) +#define AC_AMP_GET_RIGHT (0<<13) +#define AC_AMP_GET_OUTPUT (1<<15) +#define AC_AMP_GET_INPUT (0<<15) + +#define AC_AMP_SET_INDEX (0xf<<8) +#define AC_AMP_SET_INDEX_SHIFT 8 +#define AC_AMP_SET_RIGHT (1<<12) +#define AC_AMP_SET_LEFT (1<<13) +#define AC_AMP_SET_INPUT (1<<14) +#define AC_AMP_SET_OUTPUT (1<<15) + +/* DIGITAL1 bits */ +#define AC_DIG1_ENABLE (1<<0) +#define AC_DIG1_V (1<<1) +#define AC_DIG1_VCFG (1<<2) +#define AC_DIG1_EMPHASIS (1<<3) +#define AC_DIG1_COPYRIGHT (1<<4) +#define AC_DIG1_NONAUDIO (1<<5) +#define AC_DIG1_PROFESSIONAL (1<<6) +#define AC_DIG1_LEVEL (1<<7) + +/* DIGITAL2 bits */ +#define AC_DIG2_CC (0x7f<<0) + +/* Pin widget control - 8bit */ +#define AC_PINCTL_EPT (0x3<<0) +#define AC_PINCTL_EPT_NATIVE 0 +#define AC_PINCTL_EPT_HBR 3 +#define AC_PINCTL_VREFEN (0x7<<0) +#define AC_PINCTL_VREF_HIZ 0 /* Hi-Z */ +#define AC_PINCTL_VREF_50 1 /* 50% */ +#define AC_PINCTL_VREF_GRD 2 /* ground */ +#define AC_PINCTL_VREF_80 4 /* 80% */ +#define AC_PINCTL_VREF_100 5 /* 100% */ +#define AC_PINCTL_IN_EN (1<<5) +#define AC_PINCTL_OUT_EN (1<<6) +#define AC_PINCTL_HP_EN (1<<7) + +/* Pin sense - 32bit */ +#define AC_PINSENSE_IMPEDANCE_MASK (0x7fffffff) +#define AC_PINSENSE_PRESENCE (1<<31) +#define AC_PINSENSE_ELDV (1<<30) /* ELD valid (HDMI) */ + +/* EAPD/BTL enable - 32bit */ +#define AC_EAPDBTL_BALANCED (1<<0) +#define AC_EAPDBTL_EAPD (1<<1) +#define AC_EAPDBTL_LR_SWAP (1<<2) + +/* HDMI ELD data */ +#define AC_ELDD_ELD_VALID (1<<31) +#define AC_ELDD_ELD_DATA 0xff + +/* HDMI DIP size */ +#define AC_DIPSIZE_ELD_BUF (1<<3) /* ELD buf size of packet size */ +#define AC_DIPSIZE_PACK_IDX (0x07<<0) /* packet index */ + +/* HDMI DIP index */ +#define AC_DIPIDX_PACK_IDX (0x07<<5) /* packet idnex */ +#define AC_DIPIDX_BYTE_IDX (0x1f<<0) /* byte index */ + +/* HDMI DIP xmit (transmit) control */ +#define AC_DIPXMIT_MASK (0x3<<6) +#define AC_DIPXMIT_DISABLE (0x0<<6) /* disable xmit */ +#define AC_DIPXMIT_ONCE (0x2<<6) /* xmit once then disable */ +#define AC_DIPXMIT_BEST (0x3<<6) /* best effort */ + +/* HDMI content protection (CP) control */ +#define AC_CPCTRL_CES (1<<9) /* current encryption state */ +#define AC_CPCTRL_READY (1<<8) /* ready bit */ +#define AC_CPCTRL_SUBTAG (0x1f<<3) /* subtag for unsol-resp */ +#define AC_CPCTRL_STATE (3<<0) /* current CP request state */ + +/* Converter channel <-> HDMI slot mapping */ +#define AC_CVTMAP_HDMI_SLOT (0xf<<0) /* HDMI slot number */ +#define AC_CVTMAP_CHAN (0xf<<4) /* converter channel number */ + +/* configuration default - 32bit */ +#define AC_DEFCFG_SEQUENCE (0xf<<0) +#define AC_DEFCFG_DEF_ASSOC (0xf<<4) +#define AC_DEFCFG_ASSOC_SHIFT 4 +#define AC_DEFCFG_MISC (0xf<<8) +#define AC_DEFCFG_MISC_SHIFT 8 +#define AC_DEFCFG_MISC_NO_PRESENCE (1<<0) +#define AC_DEFCFG_COLOR (0xf<<12) +#define AC_DEFCFG_COLOR_SHIFT 12 +#define AC_DEFCFG_CONN_TYPE (0xf<<16) +#define AC_DEFCFG_CONN_TYPE_SHIFT 16 +#define AC_DEFCFG_DEVICE (0xf<<20) +#define AC_DEFCFG_DEVICE_SHIFT 20 +#define AC_DEFCFG_LOCATION (0x3f<<24) +#define AC_DEFCFG_LOCATION_SHIFT 24 +#define AC_DEFCFG_PORT_CONN (0x3<<30) +#define AC_DEFCFG_PORT_CONN_SHIFT 30 + +/* device device types (0x0-0xf) */ +enum { + AC_JACK_LINE_OUT, + AC_JACK_SPEAKER, + AC_JACK_HP_OUT, + AC_JACK_CD, + AC_JACK_SPDIF_OUT, + AC_JACK_DIG_OTHER_OUT, + AC_JACK_MODEM_LINE_SIDE, + AC_JACK_MODEM_HAND_SIDE, + AC_JACK_LINE_IN, + AC_JACK_AUX, + AC_JACK_MIC_IN, + AC_JACK_TELEPHONY, + AC_JACK_SPDIF_IN, + AC_JACK_DIG_OTHER_IN, + AC_JACK_OTHER = 0xf, +}; + +/* jack connection types (0x0-0xf) */ +enum { + AC_JACK_CONN_UNKNOWN, + AC_JACK_CONN_1_8, + AC_JACK_CONN_1_4, + AC_JACK_CONN_ATAPI, + AC_JACK_CONN_RCA, + AC_JACK_CONN_OPTICAL, + AC_JACK_CONN_OTHER_DIGITAL, + AC_JACK_CONN_OTHER_ANALOG, + AC_JACK_CONN_DIN, + AC_JACK_CONN_XLR, + AC_JACK_CONN_RJ11, + AC_JACK_CONN_COMB, + AC_JACK_CONN_OTHER = 0xf, +}; + +/* jack colors (0x0-0xf) */ +enum { + AC_JACK_COLOR_UNKNOWN, + AC_JACK_COLOR_BLACK, + AC_JACK_COLOR_GREY, + AC_JACK_COLOR_BLUE, + AC_JACK_COLOR_GREEN, + AC_JACK_COLOR_RED, + AC_JACK_COLOR_ORANGE, + AC_JACK_COLOR_YELLOW, + AC_JACK_COLOR_PURPLE, + AC_JACK_COLOR_PINK, + AC_JACK_COLOR_WHITE = 0xe, + AC_JACK_COLOR_OTHER, +}; + +/* Jack location (0x0-0x3f) */ +/* common case */ +enum { + AC_JACK_LOC_NONE, + AC_JACK_LOC_REAR, + AC_JACK_LOC_FRONT, + AC_JACK_LOC_LEFT, + AC_JACK_LOC_RIGHT, + AC_JACK_LOC_TOP, + AC_JACK_LOC_BOTTOM, +}; +/* bits 4-5 */ +enum { + AC_JACK_LOC_EXTERNAL = 0x00, + AC_JACK_LOC_INTERNAL = 0x10, + AC_JACK_LOC_SEPARATE = 0x20, + AC_JACK_LOC_OTHER = 0x30, +}; +enum { + /* external on primary chasis */ + AC_JACK_LOC_REAR_PANEL = 0x07, + AC_JACK_LOC_DRIVE_BAY, + /* internal */ + AC_JACK_LOC_RISER = 0x17, + AC_JACK_LOC_HDMI, + AC_JACK_LOC_ATAPI, + /* others */ + AC_JACK_LOC_MOBILE_IN = 0x37, + AC_JACK_LOC_MOBILE_OUT, +}; + +/* Port connectivity (0-3) */ +enum { + AC_JACK_PORT_COMPLEX, + AC_JACK_PORT_NONE, + AC_JACK_PORT_FIXED, + AC_JACK_PORT_BOTH, +}; + +/* max. connections to a widget */ +#define HDA_MAX_CONNECTIONS 32 + +/* max. codec address */ +#define HDA_MAX_CODEC_ADDRESS 0x0f + +/* max number of PCM devics per card */ +#define HDA_MAX_PCMS 10 + +/* --------------------------------------------------------------------- */ + +#endif diff --git a/qemu/hw/audio/intel-hda.c b/qemu/hw/audio/intel-hda.c new file mode 100644 index 000000000..433463e9c --- /dev/null +++ b/qemu/hw/audio/intel-hda.c @@ -0,0 +1,1343 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Gerd Hoffmann + * + * 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 2 or + * (at your option) version 3 of the License. + * + * 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 . + */ + +#include "hw/hw.h" +#include "hw/pci/pci.h" +#include "hw/pci/msi.h" +#include "qemu/timer.h" +#include "hw/audio/audio.h" +#include "intel-hda.h" +#include "intel-hda-defs.h" +#include "sysemu/dma.h" + +/* --------------------------------------------------------------------- */ +/* hda bus */ + +static Property hda_props[] = { + DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1), + DEFINE_PROP_END_OF_LIST() +}; + +static const TypeInfo hda_codec_bus_info = { + .name = TYPE_HDA_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(HDACodecBus), +}; + +void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, size_t bus_size, + hda_codec_response_func response, + hda_codec_xfer_func xfer) +{ + qbus_create_inplace(bus, bus_size, TYPE_HDA_BUS, dev, NULL); + bus->response = response; + bus->xfer = xfer; +} + +static int hda_codec_dev_init(DeviceState *qdev) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus); + HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev); + HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev); + + if (dev->cad == -1) { + dev->cad = bus->next_cad; + } + if (dev->cad >= 15) { + return -1; + } + bus->next_cad = dev->cad + 1; + return cdc->init(dev); +} + +static int hda_codec_dev_exit(DeviceState *qdev) +{ + HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev); + HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev); + + if (cdc->exit) { + cdc->exit(dev); + } + return 0; +} + +HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad) +{ + BusChild *kid; + HDACodecDevice *cdev; + + QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) { + DeviceState *qdev = kid->child; + cdev = DO_UPCAST(HDACodecDevice, qdev, qdev); + if (cdev->cad == cad) { + return cdev; + } + } + return NULL; +} + +void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus); + bus->response(dev, solicited, response); +} + +bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, + uint8_t *buf, uint32_t len) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus); + return bus->xfer(dev, stnr, output, buf, len); +} + +/* --------------------------------------------------------------------- */ +/* intel hda emulation */ + +typedef struct IntelHDAStream IntelHDAStream; +typedef struct IntelHDAState IntelHDAState; +typedef struct IntelHDAReg IntelHDAReg; + +typedef struct bpl { + uint64_t addr; + uint32_t len; + uint32_t flags; +} bpl; + +struct IntelHDAStream { + /* registers */ + uint32_t ctl; + uint32_t lpib; + uint32_t cbl; + uint32_t lvi; + uint32_t fmt; + uint32_t bdlp_lbase; + uint32_t bdlp_ubase; + + /* state */ + bpl *bpl; + uint32_t bentries; + uint32_t bsize, be, bp; +}; + +struct IntelHDAState { + PCIDevice pci; + const char *name; + HDACodecBus codecs; + + /* registers */ + uint32_t g_ctl; + uint32_t wake_en; + uint32_t state_sts; + uint32_t int_ctl; + uint32_t int_sts; + uint32_t wall_clk; + + uint32_t corb_lbase; + uint32_t corb_ubase; + uint32_t corb_rp; + uint32_t corb_wp; + uint32_t corb_ctl; + uint32_t corb_sts; + uint32_t corb_size; + + uint32_t rirb_lbase; + uint32_t rirb_ubase; + uint32_t rirb_wp; + uint32_t rirb_cnt; + uint32_t rirb_ctl; + uint32_t rirb_sts; + uint32_t rirb_size; + + uint32_t dp_lbase; + uint32_t dp_ubase; + + uint32_t icw; + uint32_t irr; + uint32_t ics; + + /* streams */ + IntelHDAStream st[8]; + + /* state */ + MemoryRegion mmio; + uint32_t rirb_count; + int64_t wall_base_ns; + + /* debug logging */ + const IntelHDAReg *last_reg; + uint32_t last_val; + uint32_t last_write; + uint32_t last_sec; + uint32_t repeat_count; + + /* properties */ + uint32_t debug; + uint32_t msi; + bool old_msi_addr; +}; + +#define TYPE_INTEL_HDA_GENERIC "intel-hda-generic" + +#define INTEL_HDA(obj) \ + OBJECT_CHECK(IntelHDAState, (obj), TYPE_INTEL_HDA_GENERIC) + +struct IntelHDAReg { + const char *name; /* register name */ + uint32_t size; /* size in bytes */ + uint32_t reset; /* reset value */ + uint32_t wmask; /* write mask */ + uint32_t wclear; /* write 1 to clear bits */ + uint32_t offset; /* location in IntelHDAState */ + uint32_t shift; /* byte access entries for dwords */ + uint32_t stream; + void (*whandler)(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old); + void (*rhandler)(IntelHDAState *d, const IntelHDAReg *reg); +}; + +static void intel_hda_reset(DeviceState *dev); + +/* --------------------------------------------------------------------- */ + +static hwaddr intel_hda_addr(uint32_t lbase, uint32_t ubase) +{ + hwaddr addr; + + addr = ((uint64_t)ubase << 32) | lbase; + return addr; +} + +static void intel_hda_update_int_sts(IntelHDAState *d) +{ + uint32_t sts = 0; + uint32_t i; + + /* update controller status */ + if (d->rirb_sts & ICH6_RBSTS_IRQ) { + sts |= (1 << 30); + } + if (d->rirb_sts & ICH6_RBSTS_OVERRUN) { + sts |= (1 << 30); + } + if (d->state_sts & d->wake_en) { + sts |= (1 << 30); + } + + /* update stream status */ + for (i = 0; i < 8; i++) { + /* buffer completion interrupt */ + if (d->st[i].ctl & (1 << 26)) { + sts |= (1 << i); + } + } + + /* update global status */ + if (sts & d->int_ctl) { + sts |= (1U << 31); + } + + d->int_sts = sts; +} + +static void intel_hda_update_irq(IntelHDAState *d) +{ + int msi = d->msi && msi_enabled(&d->pci); + int level; + + intel_hda_update_int_sts(d); + if (d->int_sts & (1U << 31) && d->int_ctl & (1U << 31)) { + level = 1; + } else { + level = 0; + } + dprint(d, 2, "%s: level %d [%s]\n", __FUNCTION__, + level, msi ? "msi" : "intx"); + if (msi) { + if (level) { + msi_notify(&d->pci, 0); + } + } else { + pci_set_irq(&d->pci, level); + } +} + +static int intel_hda_send_command(IntelHDAState *d, uint32_t verb) +{ + uint32_t cad, nid, data; + HDACodecDevice *codec; + HDACodecDeviceClass *cdc; + + cad = (verb >> 28) & 0x0f; + if (verb & (1 << 27)) { + /* indirect node addressing, not specified in HDA 1.0 */ + dprint(d, 1, "%s: indirect node addressing (guest bug?)\n", __FUNCTION__); + return -1; + } + nid = (verb >> 20) & 0x7f; + data = verb & 0xfffff; + + codec = hda_codec_find(&d->codecs, cad); + if (codec == NULL) { + dprint(d, 1, "%s: addressed non-existing codec\n", __FUNCTION__); + return -1; + } + cdc = HDA_CODEC_DEVICE_GET_CLASS(codec); + cdc->command(codec, nid, data); + return 0; +} + +static void intel_hda_corb_run(IntelHDAState *d) +{ + hwaddr addr; + uint32_t rp, verb; + + if (d->ics & ICH6_IRS_BUSY) { + dprint(d, 2, "%s: [icw] verb 0x%08x\n", __FUNCTION__, d->icw); + intel_hda_send_command(d, d->icw); + return; + } + + for (;;) { + if (!(d->corb_ctl & ICH6_CORBCTL_RUN)) { + dprint(d, 2, "%s: !run\n", __FUNCTION__); + return; + } + if ((d->corb_rp & 0xff) == d->corb_wp) { + dprint(d, 2, "%s: corb ring empty\n", __FUNCTION__); + return; + } + if (d->rirb_count == d->rirb_cnt) { + dprint(d, 2, "%s: rirb count reached\n", __FUNCTION__); + return; + } + + rp = (d->corb_rp + 1) & 0xff; + addr = intel_hda_addr(d->corb_lbase, d->corb_ubase); + verb = ldl_le_pci_dma(&d->pci, addr + 4*rp); + d->corb_rp = rp; + + dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __FUNCTION__, rp, verb); + intel_hda_send_command(d, verb); + } +} + +static void intel_hda_response(HDACodecDevice *dev, bool solicited, uint32_t response) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus); + IntelHDAState *d = container_of(bus, IntelHDAState, codecs); + hwaddr addr; + uint32_t wp, ex; + + if (d->ics & ICH6_IRS_BUSY) { + dprint(d, 2, "%s: [irr] response 0x%x, cad 0x%x\n", + __FUNCTION__, response, dev->cad); + d->irr = response; + d->ics &= ~(ICH6_IRS_BUSY | 0xf0); + d->ics |= (ICH6_IRS_VALID | (dev->cad << 4)); + return; + } + + if (!(d->rirb_ctl & ICH6_RBCTL_DMA_EN)) { + dprint(d, 1, "%s: rirb dma disabled, drop codec response\n", __FUNCTION__); + return; + } + + ex = (solicited ? 0 : (1 << 4)) | dev->cad; + wp = (d->rirb_wp + 1) & 0xff; + addr = intel_hda_addr(d->rirb_lbase, d->rirb_ubase); + stl_le_pci_dma(&d->pci, addr + 8*wp, response); + stl_le_pci_dma(&d->pci, addr + 8*wp + 4, ex); + d->rirb_wp = wp; + + dprint(d, 2, "%s: [wp 0x%x] response 0x%x, extra 0x%x\n", + __FUNCTION__, wp, response, ex); + + d->rirb_count++; + if (d->rirb_count == d->rirb_cnt) { + dprint(d, 2, "%s: rirb count reached (%d)\n", __FUNCTION__, d->rirb_count); + if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) { + d->rirb_sts |= ICH6_RBSTS_IRQ; + intel_hda_update_irq(d); + } + } else if ((d->corb_rp & 0xff) == d->corb_wp) { + dprint(d, 2, "%s: corb ring empty (%d/%d)\n", __FUNCTION__, + d->rirb_count, d->rirb_cnt); + if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) { + d->rirb_sts |= ICH6_RBSTS_IRQ; + intel_hda_update_irq(d); + } + } +} + +static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, + uint8_t *buf, uint32_t len) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus); + IntelHDAState *d = container_of(bus, IntelHDAState, codecs); + hwaddr addr; + uint32_t s, copy, left; + IntelHDAStream *st; + bool irq = false; + + st = output ? d->st + 4 : d->st; + for (s = 0; s < 4; s++) { + if (stnr == ((st[s].ctl >> 20) & 0x0f)) { + st = st + s; + break; + } + } + if (s == 4) { + return false; + } + if (st->bpl == NULL) { + return false; + } + if (st->ctl & (1 << 26)) { + /* + * Wait with the next DMA xfer until the guest + * has acked the buffer completion interrupt + */ + return false; + } + + left = len; + while (left > 0) { + copy = left; + if (copy > st->bsize - st->lpib) + copy = st->bsize - st->lpib; + if (copy > st->bpl[st->be].len - st->bp) + copy = st->bpl[st->be].len - st->bp; + + dprint(d, 3, "dma: entry %d, pos %d/%d, copy %d\n", + st->be, st->bp, st->bpl[st->be].len, copy); + + pci_dma_rw(&d->pci, st->bpl[st->be].addr + st->bp, buf, copy, !output); + st->lpib += copy; + st->bp += copy; + buf += copy; + left -= copy; + + if (st->bpl[st->be].len == st->bp) { + /* bpl entry filled */ + if (st->bpl[st->be].flags & 0x01) { + irq = true; + } + st->bp = 0; + st->be++; + if (st->be == st->bentries) { + /* bpl wrap around */ + st->be = 0; + st->lpib = 0; + } + } + } + if (d->dp_lbase & 0x01) { + s = st - d->st; + addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase); + stl_le_pci_dma(&d->pci, addr + 8*s, st->lpib); + } + dprint(d, 3, "dma: --\n"); + + if (irq) { + st->ctl |= (1 << 26); /* buffer completion interrupt */ + intel_hda_update_irq(d); + } + return true; +} + +static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st) +{ + hwaddr addr; + uint8_t buf[16]; + uint32_t i; + + addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase); + st->bentries = st->lvi +1; + g_free(st->bpl); + st->bpl = g_malloc(sizeof(bpl) * st->bentries); + for (i = 0; i < st->bentries; i++, addr += 16) { + pci_dma_read(&d->pci, addr, buf, 16); + st->bpl[i].addr = le64_to_cpu(*(uint64_t *)buf); + st->bpl[i].len = le32_to_cpu(*(uint32_t *)(buf + 8)); + st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12)); + dprint(d, 1, "bdl/%d: 0x%" PRIx64 " +0x%x, 0x%x\n", + i, st->bpl[i].addr, st->bpl[i].len, st->bpl[i].flags); + } + + st->bsize = st->cbl; + st->lpib = 0; + st->be = 0; + st->bp = 0; +} + +static void intel_hda_notify_codecs(IntelHDAState *d, uint32_t stream, bool running, bool output) +{ + BusChild *kid; + HDACodecDevice *cdev; + + QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) { + DeviceState *qdev = kid->child; + HDACodecDeviceClass *cdc; + + cdev = DO_UPCAST(HDACodecDevice, qdev, qdev); + cdc = HDA_CODEC_DEVICE_GET_CLASS(cdev); + if (cdc->stream) { + cdc->stream(cdev, stream, running, output); + } + } +} + +/* --------------------------------------------------------------------- */ + +static void intel_hda_set_g_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + if ((d->g_ctl & ICH6_GCTL_RESET) == 0) { + intel_hda_reset(DEVICE(d)); + } +} + +static void intel_hda_set_wake_en(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_update_irq(d); +} + +static void intel_hda_set_state_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_update_irq(d); +} + +static void intel_hda_set_int_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_update_irq(d); +} + +static void intel_hda_get_wall_clk(IntelHDAState *d, const IntelHDAReg *reg) +{ + int64_t ns; + + ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - d->wall_base_ns; + d->wall_clk = (uint32_t)(ns * 24 / 1000); /* 24 MHz */ +} + +static void intel_hda_set_corb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_corb_run(d); +} + +static void intel_hda_set_corb_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_corb_run(d); +} + +static void intel_hda_set_rirb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + if (d->rirb_wp & ICH6_RIRBWP_RST) { + d->rirb_wp = 0; + } +} + +static void intel_hda_set_rirb_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + intel_hda_update_irq(d); + + if ((old & ICH6_RBSTS_IRQ) && !(d->rirb_sts & ICH6_RBSTS_IRQ)) { + /* cleared ICH6_RBSTS_IRQ */ + d->rirb_count = 0; + intel_hda_corb_run(d); + } +} + +static void intel_hda_set_ics(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + if (d->ics & ICH6_IRS_BUSY) { + intel_hda_corb_run(d); + } +} + +static void intel_hda_set_st_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old) +{ + bool output = reg->stream >= 4; + IntelHDAStream *st = d->st + reg->stream; + + if (st->ctl & 0x01) { + /* reset */ + dprint(d, 1, "st #%d: reset\n", reg->stream); + st->ctl = SD_STS_FIFO_READY << 24; + } + if ((st->ctl & 0x02) != (old & 0x02)) { + uint32_t stnr = (st->ctl >> 20) & 0x0f; + /* run bit flipped */ + if (st->ctl & 0x02) { + /* start */ + dprint(d, 1, "st #%d: start %d (ring buf %d bytes)\n", + reg->stream, stnr, st->cbl); + intel_hda_parse_bdl(d, st); + intel_hda_notify_codecs(d, stnr, true, output); + } else { + /* stop */ + dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr); + intel_hda_notify_codecs(d, stnr, false, output); + } + } + intel_hda_update_irq(d); +} + +/* --------------------------------------------------------------------- */ + +#define ST_REG(_n, _o) (0x80 + (_n) * 0x20 + (_o)) + +static const struct IntelHDAReg regtab[] = { + /* global */ + [ ICH6_REG_GCAP ] = { + .name = "GCAP", + .size = 2, + .reset = 0x4401, + }, + [ ICH6_REG_VMIN ] = { + .name = "VMIN", + .size = 1, + }, + [ ICH6_REG_VMAJ ] = { + .name = "VMAJ", + .size = 1, + .reset = 1, + }, + [ ICH6_REG_OUTPAY ] = { + .name = "OUTPAY", + .size = 2, + .reset = 0x3c, + }, + [ ICH6_REG_INPAY ] = { + .name = "INPAY", + .size = 2, + .reset = 0x1d, + }, + [ ICH6_REG_GCTL ] = { + .name = "GCTL", + .size = 4, + .wmask = 0x0103, + .offset = offsetof(IntelHDAState, g_ctl), + .whandler = intel_hda_set_g_ctl, + }, + [ ICH6_REG_WAKEEN ] = { + .name = "WAKEEN", + .size = 2, + .wmask = 0x7fff, + .offset = offsetof(IntelHDAState, wake_en), + .whandler = intel_hda_set_wake_en, + }, + [ ICH6_REG_STATESTS ] = { + .name = "STATESTS", + .size = 2, + .wmask = 0x7fff, + .wclear = 0x7fff, + .offset = offsetof(IntelHDAState, state_sts), + .whandler = intel_hda_set_state_sts, + }, + + /* interrupts */ + [ ICH6_REG_INTCTL ] = { + .name = "INTCTL", + .size = 4, + .wmask = 0xc00000ff, + .offset = offsetof(IntelHDAState, int_ctl), + .whandler = intel_hda_set_int_ctl, + }, + [ ICH6_REG_INTSTS ] = { + .name = "INTSTS", + .size = 4, + .wmask = 0xc00000ff, + .wclear = 0xc00000ff, + .offset = offsetof(IntelHDAState, int_sts), + }, + + /* misc */ + [ ICH6_REG_WALLCLK ] = { + .name = "WALLCLK", + .size = 4, + .offset = offsetof(IntelHDAState, wall_clk), + .rhandler = intel_hda_get_wall_clk, + }, + [ ICH6_REG_WALLCLK + 0x2000 ] = { + .name = "WALLCLK(alias)", + .size = 4, + .offset = offsetof(IntelHDAState, wall_clk), + .rhandler = intel_hda_get_wall_clk, + }, + + /* dma engine */ + [ ICH6_REG_CORBLBASE ] = { + .name = "CORBLBASE", + .size = 4, + .wmask = 0xffffff80, + .offset = offsetof(IntelHDAState, corb_lbase), + }, + [ ICH6_REG_CORBUBASE ] = { + .name = "CORBUBASE", + .size = 4, + .wmask = 0xffffffff, + .offset = offsetof(IntelHDAState, corb_ubase), + }, + [ ICH6_REG_CORBWP ] = { + .name = "CORBWP", + .size = 2, + .wmask = 0xff, + .offset = offsetof(IntelHDAState, corb_wp), + .whandler = intel_hda_set_corb_wp, + }, + [ ICH6_REG_CORBRP ] = { + .name = "CORBRP", + .size = 2, + .wmask = 0x80ff, + .offset = offsetof(IntelHDAState, corb_rp), + }, + [ ICH6_REG_CORBCTL ] = { + .name = "CORBCTL", + .size = 1, + .wmask = 0x03, + .offset = offsetof(IntelHDAState, corb_ctl), + .whandler = intel_hda_set_corb_ctl, + }, + [ ICH6_REG_CORBSTS ] = { + .name = "CORBSTS", + .size = 1, + .wmask = 0x01, + .wclear = 0x01, + .offset = offsetof(IntelHDAState, corb_sts), + }, + [ ICH6_REG_CORBSIZE ] = { + .name = "CORBSIZE", + .size = 1, + .reset = 0x42, + .offset = offsetof(IntelHDAState, corb_size), + }, + [ ICH6_REG_RIRBLBASE ] = { + .name = "RIRBLBASE", + .size = 4, + .wmask = 0xffffff80, + .offset = offsetof(IntelHDAState, rirb_lbase), + }, + [ ICH6_REG_RIRBUBASE ] = { + .name = "RIRBUBASE", + .size = 4, + .wmask = 0xffffffff, + .offset = offsetof(IntelHDAState, rirb_ubase), + }, + [ ICH6_REG_RIRBWP ] = { + .name = "RIRBWP", + .size = 2, + .wmask = 0x8000, + .offset = offsetof(IntelHDAState, rirb_wp), + .whandler = intel_hda_set_rirb_wp, + }, + [ ICH6_REG_RINTCNT ] = { + .name = "RINTCNT", + .size = 2, + .wmask = 0xff, + .offset = offsetof(IntelHDAState, rirb_cnt), + }, + [ ICH6_REG_RIRBCTL ] = { + .name = "RIRBCTL", + .size = 1, + .wmask = 0x07, + .offset = offsetof(IntelHDAState, rirb_ctl), + }, + [ ICH6_REG_RIRBSTS ] = { + .name = "RIRBSTS", + .size = 1, + .wmask = 0x05, + .wclear = 0x05, + .offset = offsetof(IntelHDAState, rirb_sts), + .whandler = intel_hda_set_rirb_sts, + }, + [ ICH6_REG_RIRBSIZE ] = { + .name = "RIRBSIZE", + .size = 1, + .reset = 0x42, + .offset = offsetof(IntelHDAState, rirb_size), + }, + + [ ICH6_REG_DPLBASE ] = { + .name = "DPLBASE", + .size = 4, + .wmask = 0xffffff81, + .offset = offsetof(IntelHDAState, dp_lbase), + }, + [ ICH6_REG_DPUBASE ] = { + .name = "DPUBASE", + .size = 4, + .wmask = 0xffffffff, + .offset = offsetof(IntelHDAState, dp_ubase), + }, + + [ ICH6_REG_IC ] = { + .name = "ICW", + .size = 4, + .wmask = 0xffffffff, + .offset = offsetof(IntelHDAState, icw), + }, + [ ICH6_REG_IR ] = { + .name = "IRR", + .size = 4, + .offset = offsetof(IntelHDAState, irr), + }, + [ ICH6_REG_IRS ] = { + .name = "ICS", + .size = 2, + .wmask = 0x0003, + .wclear = 0x0002, + .offset = offsetof(IntelHDAState, ics), + .whandler = intel_hda_set_ics, + }, + +#define HDA_STREAM(_t, _i) \ + [ ST_REG(_i, ICH6_REG_SD_CTL) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " CTL", \ + .size = 4, \ + .wmask = 0x1cff001f, \ + .offset = offsetof(IntelHDAState, st[_i].ctl), \ + .whandler = intel_hda_set_st_ctl, \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_CTL) + 2] = { \ + .stream = _i, \ + .name = _t stringify(_i) " CTL(stnr)", \ + .size = 1, \ + .shift = 16, \ + .wmask = 0x00ff0000, \ + .offset = offsetof(IntelHDAState, st[_i].ctl), \ + .whandler = intel_hda_set_st_ctl, \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_STS)] = { \ + .stream = _i, \ + .name = _t stringify(_i) " CTL(sts)", \ + .size = 1, \ + .shift = 24, \ + .wmask = 0x1c000000, \ + .wclear = 0x1c000000, \ + .offset = offsetof(IntelHDAState, st[_i].ctl), \ + .whandler = intel_hda_set_st_ctl, \ + .reset = SD_STS_FIFO_READY << 24 \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_LPIB) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " LPIB", \ + .size = 4, \ + .offset = offsetof(IntelHDAState, st[_i].lpib), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_LPIB) + 0x2000 ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " LPIB(alias)", \ + .size = 4, \ + .offset = offsetof(IntelHDAState, st[_i].lpib), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_CBL) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " CBL", \ + .size = 4, \ + .wmask = 0xffffffff, \ + .offset = offsetof(IntelHDAState, st[_i].cbl), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_LVI) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " LVI", \ + .size = 2, \ + .wmask = 0x00ff, \ + .offset = offsetof(IntelHDAState, st[_i].lvi), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_FIFOSIZE) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " FIFOS", \ + .size = 2, \ + .reset = HDA_BUFFER_SIZE, \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_FORMAT) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " FMT", \ + .size = 2, \ + .wmask = 0x7f7f, \ + .offset = offsetof(IntelHDAState, st[_i].fmt), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_BDLPL) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " BDLPL", \ + .size = 4, \ + .wmask = 0xffffff80, \ + .offset = offsetof(IntelHDAState, st[_i].bdlp_lbase), \ + }, \ + [ ST_REG(_i, ICH6_REG_SD_BDLPU) ] = { \ + .stream = _i, \ + .name = _t stringify(_i) " BDLPU", \ + .size = 4, \ + .wmask = 0xffffffff, \ + .offset = offsetof(IntelHDAState, st[_i].bdlp_ubase), \ + }, \ + + HDA_STREAM("IN", 0) + HDA_STREAM("IN", 1) + HDA_STREAM("IN", 2) + HDA_STREAM("IN", 3) + + HDA_STREAM("OUT", 4) + HDA_STREAM("OUT", 5) + HDA_STREAM("OUT", 6) + HDA_STREAM("OUT", 7) + +}; + +static const IntelHDAReg *intel_hda_reg_find(IntelHDAState *d, hwaddr addr) +{ + const IntelHDAReg *reg; + + if (addr >= ARRAY_SIZE(regtab)) { + goto noreg; + } + reg = regtab+addr; + if (reg->name == NULL) { + goto noreg; + } + return reg; + +noreg: + dprint(d, 1, "unknown register, addr 0x%x\n", (int) addr); + return NULL; +} + +static uint32_t *intel_hda_reg_addr(IntelHDAState *d, const IntelHDAReg *reg) +{ + uint8_t *addr = (void*)d; + + addr += reg->offset; + return (uint32_t*)addr; +} + +static void intel_hda_reg_write(IntelHDAState *d, const IntelHDAReg *reg, uint32_t val, + uint32_t wmask) +{ + uint32_t *addr; + uint32_t old; + + if (!reg) { + return; + } + + if (d->debug) { + time_t now = time(NULL); + if (d->last_write && d->last_reg == reg && d->last_val == val) { + d->repeat_count++; + if (d->last_sec != now) { + dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count); + d->last_sec = now; + d->repeat_count = 0; + } + } else { + if (d->repeat_count) { + dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count); + } + dprint(d, 2, "write %-16s: 0x%x (%x)\n", reg->name, val, wmask); + d->last_write = 1; + d->last_reg = reg; + d->last_val = val; + d->last_sec = now; + d->repeat_count = 0; + } + } + assert(reg->offset != 0); + + addr = intel_hda_reg_addr(d, reg); + old = *addr; + + if (reg->shift) { + val <<= reg->shift; + wmask <<= reg->shift; + } + wmask &= reg->wmask; + *addr &= ~wmask; + *addr |= wmask & val; + *addr &= ~(val & reg->wclear); + + if (reg->whandler) { + reg->whandler(d, reg, old); + } +} + +static uint32_t intel_hda_reg_read(IntelHDAState *d, const IntelHDAReg *reg, + uint32_t rmask) +{ + uint32_t *addr, ret; + + if (!reg) { + return 0; + } + + if (reg->rhandler) { + reg->rhandler(d, reg); + } + + if (reg->offset == 0) { + /* constant read-only register */ + ret = reg->reset; + } else { + addr = intel_hda_reg_addr(d, reg); + ret = *addr; + if (reg->shift) { + ret >>= reg->shift; + } + ret &= rmask; + } + if (d->debug) { + time_t now = time(NULL); + if (!d->last_write && d->last_reg == reg && d->last_val == ret) { + d->repeat_count++; + if (d->last_sec != now) { + dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count); + d->last_sec = now; + d->repeat_count = 0; + } + } else { + if (d->repeat_count) { + dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count); + } + dprint(d, 2, "read %-16s: 0x%x (%x)\n", reg->name, ret, rmask); + d->last_write = 0; + d->last_reg = reg; + d->last_val = ret; + d->last_sec = now; + d->repeat_count = 0; + } + } + return ret; +} + +static void intel_hda_regs_reset(IntelHDAState *d) +{ + uint32_t *addr; + int i; + + for (i = 0; i < ARRAY_SIZE(regtab); i++) { + if (regtab[i].name == NULL) { + continue; + } + if (regtab[i].offset == 0) { + continue; + } + addr = intel_hda_reg_addr(d, regtab + i); + *addr = regtab[i].reset; + } +} + +/* --------------------------------------------------------------------- */ + +static void intel_hda_mmio_writeb(void *opaque, hwaddr addr, uint32_t val) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + intel_hda_reg_write(d, reg, val, 0xff); +} + +static void intel_hda_mmio_writew(void *opaque, hwaddr addr, uint32_t val) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + intel_hda_reg_write(d, reg, val, 0xffff); +} + +static void intel_hda_mmio_writel(void *opaque, hwaddr addr, uint32_t val) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + intel_hda_reg_write(d, reg, val, 0xffffffff); +} + +static uint32_t intel_hda_mmio_readb(void *opaque, hwaddr addr) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + return intel_hda_reg_read(d, reg, 0xff); +} + +static uint32_t intel_hda_mmio_readw(void *opaque, hwaddr addr) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + return intel_hda_reg_read(d, reg, 0xffff); +} + +static uint32_t intel_hda_mmio_readl(void *opaque, hwaddr addr) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + return intel_hda_reg_read(d, reg, 0xffffffff); +} + +static const MemoryRegionOps intel_hda_mmio_ops = { + .old_mmio = { + .read = { + intel_hda_mmio_readb, + intel_hda_mmio_readw, + intel_hda_mmio_readl, + }, + .write = { + intel_hda_mmio_writeb, + intel_hda_mmio_writew, + intel_hda_mmio_writel, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* --------------------------------------------------------------------- */ + +static void intel_hda_reset(DeviceState *dev) +{ + BusChild *kid; + IntelHDAState *d = INTEL_HDA(dev); + HDACodecDevice *cdev; + + intel_hda_regs_reset(d); + d->wall_base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + /* reset codecs */ + QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) { + DeviceState *qdev = kid->child; + cdev = DO_UPCAST(HDACodecDevice, qdev, qdev); + device_reset(DEVICE(cdev)); + d->state_sts |= (1 << cdev->cad); + } + intel_hda_update_irq(d); +} + +static void intel_hda_realize(PCIDevice *pci, Error **errp) +{ + IntelHDAState *d = INTEL_HDA(pci); + uint8_t *conf = d->pci.config; + + d->name = object_get_typename(OBJECT(d)); + + pci_config_set_interrupt_pin(conf, 1); + + /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */ + conf[0x40] = 0x01; + + memory_region_init_io(&d->mmio, OBJECT(d), &intel_hda_mmio_ops, d, + "intel-hda", 0x4000); + pci_register_bar(&d->pci, 0, 0, &d->mmio); + if (d->msi) { + msi_init(&d->pci, d->old_msi_addr ? 0x50 : 0x60, 1, true, false); + } + + hda_codec_bus_init(DEVICE(pci), &d->codecs, sizeof(d->codecs), + intel_hda_response, intel_hda_xfer); +} + +static void intel_hda_exit(PCIDevice *pci) +{ + IntelHDAState *d = INTEL_HDA(pci); + + msi_uninit(&d->pci); +} + +static int intel_hda_post_load(void *opaque, int version) +{ + IntelHDAState* d = opaque; + int i; + + dprint(d, 1, "%s\n", __FUNCTION__); + for (i = 0; i < ARRAY_SIZE(d->st); i++) { + if (d->st[i].ctl & 0x02) { + intel_hda_parse_bdl(d, &d->st[i]); + } + } + intel_hda_update_irq(d); + return 0; +} + +static const VMStateDescription vmstate_intel_hda_stream = { + .name = "intel-hda-stream", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(ctl, IntelHDAStream), + VMSTATE_UINT32(lpib, IntelHDAStream), + VMSTATE_UINT32(cbl, IntelHDAStream), + VMSTATE_UINT32(lvi, IntelHDAStream), + VMSTATE_UINT32(fmt, IntelHDAStream), + VMSTATE_UINT32(bdlp_lbase, IntelHDAStream), + VMSTATE_UINT32(bdlp_ubase, IntelHDAStream), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_intel_hda = { + .name = "intel-hda", + .version_id = 1, + .post_load = intel_hda_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(pci, IntelHDAState), + + /* registers */ + VMSTATE_UINT32(g_ctl, IntelHDAState), + VMSTATE_UINT32(wake_en, IntelHDAState), + VMSTATE_UINT32(state_sts, IntelHDAState), + VMSTATE_UINT32(int_ctl, IntelHDAState), + VMSTATE_UINT32(int_sts, IntelHDAState), + VMSTATE_UINT32(wall_clk, IntelHDAState), + VMSTATE_UINT32(corb_lbase, IntelHDAState), + VMSTATE_UINT32(corb_ubase, IntelHDAState), + VMSTATE_UINT32(corb_rp, IntelHDAState), + VMSTATE_UINT32(corb_wp, IntelHDAState), + VMSTATE_UINT32(corb_ctl, IntelHDAState), + VMSTATE_UINT32(corb_sts, IntelHDAState), + VMSTATE_UINT32(corb_size, IntelHDAState), + VMSTATE_UINT32(rirb_lbase, IntelHDAState), + VMSTATE_UINT32(rirb_ubase, IntelHDAState), + VMSTATE_UINT32(rirb_wp, IntelHDAState), + VMSTATE_UINT32(rirb_cnt, IntelHDAState), + VMSTATE_UINT32(rirb_ctl, IntelHDAState), + VMSTATE_UINT32(rirb_sts, IntelHDAState), + VMSTATE_UINT32(rirb_size, IntelHDAState), + VMSTATE_UINT32(dp_lbase, IntelHDAState), + VMSTATE_UINT32(dp_ubase, IntelHDAState), + VMSTATE_UINT32(icw, IntelHDAState), + VMSTATE_UINT32(irr, IntelHDAState), + VMSTATE_UINT32(ics, IntelHDAState), + VMSTATE_STRUCT_ARRAY(st, IntelHDAState, 8, 0, + vmstate_intel_hda_stream, + IntelHDAStream), + + /* additional state info */ + VMSTATE_UINT32(rirb_count, IntelHDAState), + VMSTATE_INT64(wall_base_ns, IntelHDAState), + + VMSTATE_END_OF_LIST() + } +}; + +static Property intel_hda_properties[] = { + DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0), + DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1), + DEFINE_PROP_BOOL("old_msi_addr", IntelHDAState, old_msi_addr, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void intel_hda_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = intel_hda_realize; + k->exit = intel_hda_exit; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO; + dc->reset = intel_hda_reset; + dc->vmsd = &vmstate_intel_hda; + dc->props = intel_hda_properties; +} + +static void intel_hda_class_init_ich6(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->device_id = 0x2668; + k->revision = 1; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Intel HD Audio Controller (ich6)"; +} + +static void intel_hda_class_init_ich9(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->device_id = 0x293e; + k->revision = 3; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Intel HD Audio Controller (ich9)"; +} + +static const TypeInfo intel_hda_info = { + .name = TYPE_INTEL_HDA_GENERIC, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(IntelHDAState), + .class_init = intel_hda_class_init, + .abstract = true, +}; + +static const TypeInfo intel_hda_info_ich6 = { + .name = "intel-hda", + .parent = TYPE_INTEL_HDA_GENERIC, + .class_init = intel_hda_class_init_ich6, +}; + +static const TypeInfo intel_hda_info_ich9 = { + .name = "ich9-intel-hda", + .parent = TYPE_INTEL_HDA_GENERIC, + .class_init = intel_hda_class_init_ich9, +}; + +static void hda_codec_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->init = hda_codec_dev_init; + k->exit = hda_codec_dev_exit; + set_bit(DEVICE_CATEGORY_SOUND, k->categories); + k->bus_type = TYPE_HDA_BUS; + k->props = hda_props; +} + +static const TypeInfo hda_codec_device_type_info = { + .name = TYPE_HDA_CODEC_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(HDACodecDevice), + .abstract = true, + .class_size = sizeof(HDACodecDeviceClass), + .class_init = hda_codec_device_class_init, +}; + +/* + * create intel hda controller with codec attached to it, + * so '-soundhw hda' works. + */ +static int intel_hda_and_codec_init(PCIBus *bus) +{ + DeviceState *controller; + BusState *hdabus; + DeviceState *codec; + + controller = DEVICE(pci_create_simple(bus, -1, "intel-hda")); + hdabus = QLIST_FIRST(&controller->child_bus); + codec = qdev_create(hdabus, "hda-duplex"); + qdev_init_nofail(codec); + return 0; +} + +static void intel_hda_register_types(void) +{ + type_register_static(&hda_codec_bus_info); + type_register_static(&intel_hda_info); + type_register_static(&intel_hda_info_ich6); + type_register_static(&intel_hda_info_ich9); + type_register_static(&hda_codec_device_type_info); + pci_register_soundhw("hda", "Intel HD Audio", intel_hda_and_codec_init); +} + +type_init(intel_hda_register_types) diff --git a/qemu/hw/audio/intel-hda.h b/qemu/hw/audio/intel-hda.h new file mode 100644 index 000000000..d784bcf5f --- /dev/null +++ b/qemu/hw/audio/intel-hda.h @@ -0,0 +1,72 @@ +#ifndef HW_INTEL_HDA_H +#define HW_INTEL_HDA_H + +#include "hw/qdev.h" + +/* --------------------------------------------------------------------- */ +/* hda bus */ + +#define TYPE_HDA_CODEC_DEVICE "hda-codec" +#define HDA_CODEC_DEVICE(obj) \ + OBJECT_CHECK(HDACodecDevice, (obj), TYPE_HDA_CODEC_DEVICE) +#define HDA_CODEC_DEVICE_CLASS(klass) \ + OBJECT_CLASS_CHECK(HDACodecDeviceClass, (klass), TYPE_HDA_CODEC_DEVICE) +#define HDA_CODEC_DEVICE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(HDACodecDeviceClass, (obj), TYPE_HDA_CODEC_DEVICE) + +#define TYPE_HDA_BUS "HDA" +#define HDA_BUS(obj) OBJECT_CHECK(HDACodecBus, (obj), TYPE_HDA_BUS) + +typedef struct HDACodecBus HDACodecBus; +typedef struct HDACodecDevice HDACodecDevice; + +typedef void (*hda_codec_response_func)(HDACodecDevice *dev, + bool solicited, uint32_t response); +typedef bool (*hda_codec_xfer_func)(HDACodecDevice *dev, + uint32_t stnr, bool output, + uint8_t *buf, uint32_t len); + +struct HDACodecBus { + BusState qbus; + uint32_t next_cad; + hda_codec_response_func response; + hda_codec_xfer_func xfer; +}; + +typedef struct HDACodecDeviceClass +{ + DeviceClass parent_class; + + int (*init)(HDACodecDevice *dev); + int (*exit)(HDACodecDevice *dev); + void (*command)(HDACodecDevice *dev, uint32_t nid, uint32_t data); + void (*stream)(HDACodecDevice *dev, uint32_t stnr, bool running, bool output); +} HDACodecDeviceClass; + +struct HDACodecDevice { + DeviceState qdev; + uint32_t cad; /* codec address */ +}; + +void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, size_t bus_size, + hda_codec_response_func response, + hda_codec_xfer_func xfer); +HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad); + +void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response); +bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, + uint8_t *buf, uint32_t len); + +/* --------------------------------------------------------------------- */ + +#define dprint(_dev, _level, _fmt, ...) \ + do { \ + if (_dev->debug >= _level) { \ + fprintf(stderr, "%s: ", _dev->name); \ + fprintf(stderr, _fmt, ## __VA_ARGS__); \ + } \ + } while (0) + +/* --------------------------------------------------------------------- */ + +#endif diff --git a/qemu/hw/audio/lm4549.c b/qemu/hw/audio/lm4549.c new file mode 100644 index 000000000..380ef603b --- /dev/null +++ b/qemu/hw/audio/lm4549.c @@ -0,0 +1,335 @@ +/* + * LM4549 Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licensed under the GPL. + * + * ***************************************************************** + * + * This driver emulates the LM4549 codec. + * + * It supports only one playback voice and no record voice. + */ + +#include "hw/hw.h" +#include "audio/audio.h" +#include "lm4549.h" + +#if 0 +#define LM4549_DEBUG 1 +#endif + +#if 0 +#define LM4549_DUMP_DAC_INPUT 1 +#endif + +#ifdef LM4549_DEBUG +#define DPRINTF(fmt, ...) \ +do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#if defined(LM4549_DUMP_DAC_INPUT) +#include +static FILE *fp_dac_input; +#endif + +/* LM4549 register list */ +enum { + LM4549_Reset = 0x00, + LM4549_Master_Volume = 0x02, + LM4549_Line_Out_Volume = 0x04, + LM4549_Master_Volume_Mono = 0x06, + LM4549_PC_Beep_Volume = 0x0A, + LM4549_Phone_Volume = 0x0C, + LM4549_Mic_Volume = 0x0E, + LM4549_Line_In_Volume = 0x10, + LM4549_CD_Volume = 0x12, + LM4549_Video_Volume = 0x14, + LM4549_Aux_Volume = 0x16, + LM4549_PCM_Out_Volume = 0x18, + LM4549_Record_Select = 0x1A, + LM4549_Record_Gain = 0x1C, + LM4549_General_Purpose = 0x20, + LM4549_3D_Control = 0x22, + LM4549_Powerdown_Ctrl_Stat = 0x26, + LM4549_Ext_Audio_ID = 0x28, + LM4549_Ext_Audio_Stat_Ctrl = 0x2A, + LM4549_PCM_Front_DAC_Rate = 0x2C, + LM4549_PCM_ADC_Rate = 0x32, + LM4549_Vendor_ID1 = 0x7C, + LM4549_Vendor_ID2 = 0x7E +}; + +static void lm4549_reset(lm4549_state *s) +{ + uint16_t *regfile = s->regfile; + + regfile[LM4549_Reset] = 0x0d50; + regfile[LM4549_Master_Volume] = 0x8008; + regfile[LM4549_Line_Out_Volume] = 0x8000; + regfile[LM4549_Master_Volume_Mono] = 0x8000; + regfile[LM4549_PC_Beep_Volume] = 0x0000; + regfile[LM4549_Phone_Volume] = 0x8008; + regfile[LM4549_Mic_Volume] = 0x8008; + regfile[LM4549_Line_In_Volume] = 0x8808; + regfile[LM4549_CD_Volume] = 0x8808; + regfile[LM4549_Video_Volume] = 0x8808; + regfile[LM4549_Aux_Volume] = 0x8808; + regfile[LM4549_PCM_Out_Volume] = 0x8808; + regfile[LM4549_Record_Select] = 0x0000; + regfile[LM4549_Record_Gain] = 0x8000; + regfile[LM4549_General_Purpose] = 0x0000; + regfile[LM4549_3D_Control] = 0x0101; + regfile[LM4549_Powerdown_Ctrl_Stat] = 0x000f; + regfile[LM4549_Ext_Audio_ID] = 0x0001; + regfile[LM4549_Ext_Audio_Stat_Ctrl] = 0x0000; + regfile[LM4549_PCM_Front_DAC_Rate] = 0xbb80; + regfile[LM4549_PCM_ADC_Rate] = 0xbb80; + regfile[LM4549_Vendor_ID1] = 0x4e53; + regfile[LM4549_Vendor_ID2] = 0x4331; +} + +static void lm4549_audio_transfer(lm4549_state *s) +{ + uint32_t written_bytes, written_samples; + uint32_t i; + + /* Activate the voice */ + AUD_set_active_out(s->voice, 1); + s->voice_is_active = 1; + + /* Try to write the buffer content */ + written_bytes = AUD_write(s->voice, s->buffer, + s->buffer_level * sizeof(uint16_t)); + written_samples = written_bytes >> 1; + +#if defined(LM4549_DUMP_DAC_INPUT) + fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input); +#endif + + s->buffer_level -= written_samples; + + if (s->buffer_level > 0) { + /* Move the data back to the start of the buffer */ + for (i = 0; i < s->buffer_level; i++) { + s->buffer[i] = s->buffer[i + written_samples]; + } + } +} + +static void lm4549_audio_out_callback(void *opaque, int free) +{ + lm4549_state *s = (lm4549_state *)opaque; + static uint32_t prev_buffer_level; + +#ifdef LM4549_DEBUG + int size = AUD_get_buffer_size_out(s->voice); + DPRINTF("audio_out_callback size = %i free = %i\n", size, free); +#endif + + /* Detect that no data are consumed + => disable the voice */ + if (s->buffer_level == prev_buffer_level) { + AUD_set_active_out(s->voice, 0); + s->voice_is_active = 0; + } + prev_buffer_level = s->buffer_level; + + /* Check if a buffer transfer is pending */ + if (s->buffer_level == LM4549_BUFFER_SIZE) { + lm4549_audio_transfer(s); + + /* Request more data */ + if (s->data_req_cb != NULL) { + (s->data_req_cb)(s->opaque); + } + } +} + +uint32_t lm4549_read(lm4549_state *s, hwaddr offset) +{ + uint16_t *regfile = s->regfile; + uint32_t value = 0; + + /* Read the stored value */ + assert(offset < 128); + value = regfile[offset]; + + DPRINTF("read [0x%02x] = 0x%04x\n", offset, value); + + return value; +} + +void lm4549_write(lm4549_state *s, + hwaddr offset, uint32_t value) +{ + uint16_t *regfile = s->regfile; + + assert(offset < 128); + DPRINTF("write [0x%02x] = 0x%04x\n", offset, value); + + switch (offset) { + case LM4549_Reset: + lm4549_reset(s); + break; + + case LM4549_PCM_Front_DAC_Rate: + regfile[LM4549_PCM_Front_DAC_Rate] = value; + DPRINTF("DAC rate change = %i\n", value); + + /* Re-open a voice with the new sample rate */ + struct audsettings as; + as.freq = value; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + s->voice = AUD_open_out( + &s->card, + s->voice, + "lm4549.out", + s, + lm4549_audio_out_callback, + &as + ); + break; + + case LM4549_Powerdown_Ctrl_Stat: + value &= ~0xf; + value |= regfile[LM4549_Powerdown_Ctrl_Stat] & 0xf; + regfile[LM4549_Powerdown_Ctrl_Stat] = value; + break; + + case LM4549_Ext_Audio_ID: + case LM4549_Vendor_ID1: + case LM4549_Vendor_ID2: + DPRINTF("Write to read-only register 0x%x\n", (int)offset); + break; + + default: + /* Store the new value */ + regfile[offset] = value; + break; + } +} + +uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right) +{ + /* The left and right samples are in 20-bit resolution. + The LM4549 has 18-bit resolution and only uses the bits [19:2]. + This model supports 16-bit playback. + */ + + if (s->buffer_level > LM4549_BUFFER_SIZE - 2) { + DPRINTF("write_sample Buffer full\n"); + return 0; + } + + /* Store 16-bit samples in the buffer */ + s->buffer[s->buffer_level++] = (left >> 4); + s->buffer[s->buffer_level++] = (right >> 4); + + if (s->buffer_level == LM4549_BUFFER_SIZE) { + /* Trigger the transfer of the buffer to the audio host */ + lm4549_audio_transfer(s); + } + + return 1; +} + +static int lm4549_post_load(void *opaque, int version_id) +{ + lm4549_state *s = (lm4549_state *)opaque; + uint16_t *regfile = s->regfile; + + /* Re-open a voice with the current sample rate */ + uint32_t freq = regfile[LM4549_PCM_Front_DAC_Rate]; + + DPRINTF("post_load freq = %i\n", freq); + DPRINTF("post_load voice_is_active = %i\n", s->voice_is_active); + + struct audsettings as; + as.freq = freq; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + s->voice = AUD_open_out( + &s->card, + s->voice, + "lm4549.out", + s, + lm4549_audio_out_callback, + &as + ); + + /* Request data */ + if (s->voice_is_active == 1) { + lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice)); + } + + return 0; +} + +void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque) +{ + struct audsettings as; + + /* Store the callback and opaque pointer */ + s->data_req_cb = data_req_cb; + s->opaque = opaque; + + /* Init the registers */ + lm4549_reset(s); + + /* Register an audio card */ + AUD_register_card("lm4549", &s->card); + + /* Open a default voice */ + as.freq = 48000; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + s->voice = AUD_open_out( + &s->card, + s->voice, + "lm4549.out", + s, + lm4549_audio_out_callback, + &as + ); + + AUD_set_volume_out(s->voice, 0, 255, 255); + + s->voice_is_active = 0; + + /* Reset the input buffer */ + memset(s->buffer, 0x00, sizeof(s->buffer)); + s->buffer_level = 0; + +#if defined(LM4549_DUMP_DAC_INPUT) + fp_dac_input = fopen("lm4549_dac_input.pcm", "wb"); + if (!fp_dac_input) { + hw_error("Unable to open lm4549_dac_input.pcm for writing\n"); + } +#endif +} + +const VMStateDescription vmstate_lm4549_state = { + .name = "lm4549_state", + .version_id = 1, + .minimum_version_id = 1, + .post_load = lm4549_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(voice_is_active, lm4549_state), + VMSTATE_UINT16_ARRAY(regfile, lm4549_state, 128), + VMSTATE_UINT16_ARRAY(buffer, lm4549_state, LM4549_BUFFER_SIZE), + VMSTATE_UINT32(buffer_level, lm4549_state), + VMSTATE_END_OF_LIST() + } +}; diff --git a/qemu/hw/audio/lm4549.h b/qemu/hw/audio/lm4549.h new file mode 100644 index 000000000..812a7a444 --- /dev/null +++ b/qemu/hw/audio/lm4549.h @@ -0,0 +1,43 @@ +/* + * LM4549 Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licensed under the GPL. + * + * ***************************************************************** + */ + +#ifndef HW_LM4549_H +#define HW_LM4549_H + +#include "audio/audio.h" + +typedef void (*lm4549_callback)(void *opaque); + +#define LM4549_BUFFER_SIZE (512 * 2) /* 512 16-bit stereo samples */ + + +typedef struct { + QEMUSoundCard card; + SWVoiceOut *voice; + uint32_t voice_is_active; + + uint16_t regfile[128]; + lm4549_callback data_req_cb; + void *opaque; + + uint16_t buffer[LM4549_BUFFER_SIZE]; + uint32_t buffer_level; +} lm4549_state; + +extern const VMStateDescription vmstate_lm4549_state; + + +void lm4549_init(lm4549_state *s, lm4549_callback data_req, void *opaque); +uint32_t lm4549_read(lm4549_state *s, hwaddr offset); +void lm4549_write(lm4549_state *s, hwaddr offset, uint32_t value); +uint32_t lm4549_write_samples(lm4549_state *s, uint32_t left, uint32_t right); + +#endif /* #ifndef HW_LM4549_H */ diff --git a/qemu/hw/audio/marvell_88w8618.c b/qemu/hw/audio/marvell_88w8618.c new file mode 100644 index 000000000..86992677e --- /dev/null +++ b/qemu/hw/audio/marvell_88w8618.c @@ -0,0 +1,306 @@ +/* + * Marvell 88w8618 audio emulation extracted from + * Marvell MV88w8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ +#include "hw/sysbus.h" +#include "hw/hw.h" +#include "hw/i2c/i2c.h" +#include "audio/audio.h" + +#define MP_AUDIO_SIZE 0x00001000 + +/* Audio register offsets */ +#define MP_AUDIO_PLAYBACK_MODE 0x00 +#define MP_AUDIO_CLOCK_DIV 0x18 +#define MP_AUDIO_IRQ_STATUS 0x20 +#define MP_AUDIO_IRQ_ENABLE 0x24 +#define MP_AUDIO_TX_START_LO 0x28 +#define MP_AUDIO_TX_THRESHOLD 0x2C +#define MP_AUDIO_TX_STATUS 0x38 +#define MP_AUDIO_TX_START_HI 0x40 + +/* Status register and IRQ enable bits */ +#define MP_AUDIO_TX_HALF (1 << 6) +#define MP_AUDIO_TX_FULL (1 << 7) + +/* Playback mode bits */ +#define MP_AUDIO_16BIT_SAMPLE (1 << 0) +#define MP_AUDIO_PLAYBACK_EN (1 << 7) +#define MP_AUDIO_CLOCK_24MHZ (1 << 9) +#define MP_AUDIO_MONO (1 << 14) + +#define TYPE_MV88W8618_AUDIO "mv88w8618_audio" +#define MV88W8618_AUDIO(obj) \ + OBJECT_CHECK(mv88w8618_audio_state, (obj), TYPE_MV88W8618_AUDIO) + +typedef struct mv88w8618_audio_state { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + uint32_t playback_mode; + uint32_t status; + uint32_t irq_enable; + uint32_t phys_buf; + uint32_t target_buffer; + uint32_t threshold; + uint32_t play_pos; + uint32_t last_free; + uint32_t clock_div; + void *wm; +} mv88w8618_audio_state; + +static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in) +{ + mv88w8618_audio_state *s = opaque; + int16_t *codec_buffer; + int8_t buf[4096]; + int8_t *mem_buffer; + int pos, block_size; + + if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) { + return; + } + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) { + free_out <<= 1; + } + if (!(s->playback_mode & MP_AUDIO_MONO)) { + free_out <<= 1; + } + block_size = s->threshold / 2; + if (free_out - s->last_free < block_size) { + return; + } + if (block_size > 4096) { + return; + } + cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size); + mem_buffer = buf; + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = *(int16_t *)mem_buffer; + *codec_buffer++ = *(int16_t *)mem_buffer; + mem_buffer += 2; + } + } else { + memcpy(wm8750_dac_buffer(s->wm, block_size >> 2), + (uint32_t *)mem_buffer, block_size); + } + } else { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size); + for (pos = 0; pos < block_size; pos++) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } else { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } + } + wm8750_dac_commit(s->wm); + + s->last_free = free_out - block_size; + + if (s->play_pos == 0) { + s->status |= MP_AUDIO_TX_HALF; + s->play_pos = block_size; + } else { + s->status |= MP_AUDIO_TX_FULL; + s->play_pos = 0; + } + + if (s->status & s->irq_enable) { + qemu_irq_raise(s->irq); + } +} + +static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s) +{ + int rate; + + if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) { + rate = 24576000 / 64; /* 24.576MHz */ + } else { + rate = 11289600 / 64; /* 11.2896MHz */ + } + rate /= ((s->clock_div >> 8) & 0xff) + 1; + + wm8750_set_bclk_in(s->wm, rate); +} + +static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset, + unsigned size) +{ + mv88w8618_audio_state *s = opaque; + + switch (offset) { + case MP_AUDIO_PLAYBACK_MODE: + return s->playback_mode; + + case MP_AUDIO_CLOCK_DIV: + return s->clock_div; + + case MP_AUDIO_IRQ_STATUS: + return s->status; + + case MP_AUDIO_IRQ_ENABLE: + return s->irq_enable; + + case MP_AUDIO_TX_STATUS: + return s->play_pos >> 2; + + default: + return 0; + } +} + +static void mv88w8618_audio_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + mv88w8618_audio_state *s = opaque; + + switch (offset) { + case MP_AUDIO_PLAYBACK_MODE: + if (value & MP_AUDIO_PLAYBACK_EN && + !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) { + s->status = 0; + s->last_free = 0; + s->play_pos = 0; + } + s->playback_mode = value; + mv88w8618_audio_clock_update(s); + break; + + case MP_AUDIO_CLOCK_DIV: + s->clock_div = value; + s->last_free = 0; + s->play_pos = 0; + mv88w8618_audio_clock_update(s); + break; + + case MP_AUDIO_IRQ_STATUS: + s->status &= ~value; + break; + + case MP_AUDIO_IRQ_ENABLE: + s->irq_enable = value; + if (s->status & s->irq_enable) { + qemu_irq_raise(s->irq); + } + break; + + case MP_AUDIO_TX_START_LO: + s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF); + s->target_buffer = s->phys_buf; + s->play_pos = 0; + s->last_free = 0; + break; + + case MP_AUDIO_TX_THRESHOLD: + s->threshold = (value + 1) * 4; + break; + + case MP_AUDIO_TX_START_HI: + s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16); + s->target_buffer = s->phys_buf; + s->play_pos = 0; + s->last_free = 0; + break; + } +} + +static void mv88w8618_audio_reset(DeviceState *d) +{ + mv88w8618_audio_state *s = MV88W8618_AUDIO(d); + + s->playback_mode = 0; + s->status = 0; + s->irq_enable = 0; + s->clock_div = 0; + s->threshold = 0; + s->phys_buf = 0; +} + +static const MemoryRegionOps mv88w8618_audio_ops = { + .read = mv88w8618_audio_read, + .write = mv88w8618_audio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int mv88w8618_audio_init(SysBusDevice *dev) +{ + mv88w8618_audio_state *s = MV88W8618_AUDIO(dev); + + sysbus_init_irq(dev, &s->irq); + + wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s); + + memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_audio_ops, s, + "audio", MP_AUDIO_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static const VMStateDescription mv88w8618_audio_vmsd = { + .name = "mv88w8618_audio", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(playback_mode, mv88w8618_audio_state), + VMSTATE_UINT32(status, mv88w8618_audio_state), + VMSTATE_UINT32(irq_enable, mv88w8618_audio_state), + VMSTATE_UINT32(phys_buf, mv88w8618_audio_state), + VMSTATE_UINT32(target_buffer, mv88w8618_audio_state), + VMSTATE_UINT32(threshold, mv88w8618_audio_state), + VMSTATE_UINT32(play_pos, mv88w8618_audio_state), + VMSTATE_UINT32(last_free, mv88w8618_audio_state), + VMSTATE_UINT32(clock_div, mv88w8618_audio_state), + VMSTATE_END_OF_LIST() + } +}; + +static Property mv88w8618_audio_properties[] = { + DEFINE_PROP_PTR("wm8750", mv88w8618_audio_state, wm), + {/* end of list */}, +}; + +static void mv88w8618_audio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = mv88w8618_audio_init; + dc->reset = mv88w8618_audio_reset; + dc->vmsd = &mv88w8618_audio_vmsd; + dc->props = mv88w8618_audio_properties; + /* Reason: pointer property "wm8750" */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo mv88w8618_audio_info = { + .name = TYPE_MV88W8618_AUDIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mv88w8618_audio_state), + .class_init = mv88w8618_audio_class_init, +}; + +static void mv88w8618_register_types(void) +{ + type_register_static(&mv88w8618_audio_info); +} + +type_init(mv88w8618_register_types) diff --git a/qemu/hw/audio/milkymist-ac97.c b/qemu/hw/audio/milkymist-ac97.c new file mode 100644 index 000000000..28f55e853 --- /dev/null +++ b/qemu/hw/audio/milkymist-ac97.c @@ -0,0 +1,348 @@ +/* + * QEMU model of the Milkymist System Controller. + * + * Copyright (c) 2010 Michael Walle + * + * 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 . + * + * + * Specification available at: + * http://www.milkymist.org/socdoc/ac97.pdf + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "audio/audio.h" +#include "qemu/error-report.h" + +enum { + R_AC97_CTRL = 0, + R_AC97_ADDR, + R_AC97_DATAOUT, + R_AC97_DATAIN, + R_D_CTRL, + R_D_ADDR, + R_D_REMAINING, + R_RESERVED, + R_U_CTRL, + R_U_ADDR, + R_U_REMAINING, + R_MAX +}; + +enum { + AC97_CTRL_RQEN = (1<<0), + AC97_CTRL_WRITE = (1<<1), +}; + +enum { + CTRL_EN = (1<<0), +}; + +#define TYPE_MILKYMIST_AC97 "milkymist-ac97" +#define MILKYMIST_AC97(obj) \ + OBJECT_CHECK(MilkymistAC97State, (obj), TYPE_MILKYMIST_AC97) + +struct MilkymistAC97State { + SysBusDevice parent_obj; + + MemoryRegion regs_region; + + QEMUSoundCard card; + SWVoiceIn *voice_in; + SWVoiceOut *voice_out; + + uint32_t regs[R_MAX]; + + qemu_irq crrequest_irq; + qemu_irq crreply_irq; + qemu_irq dmar_irq; + qemu_irq dmaw_irq; +}; +typedef struct MilkymistAC97State MilkymistAC97State; + +static void update_voices(MilkymistAC97State *s) +{ + if (s->regs[R_D_CTRL] & CTRL_EN) { + AUD_set_active_out(s->voice_out, 1); + } else { + AUD_set_active_out(s->voice_out, 0); + } + + if (s->regs[R_U_CTRL] & CTRL_EN) { + AUD_set_active_in(s->voice_in, 1); + } else { + AUD_set_active_in(s->voice_in, 0); + } +} + +static uint64_t ac97_read(void *opaque, hwaddr addr, + unsigned size) +{ + MilkymistAC97State *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_AC97_CTRL: + case R_AC97_ADDR: + case R_AC97_DATAOUT: + case R_AC97_DATAIN: + case R_D_CTRL: + case R_D_ADDR: + case R_D_REMAINING: + case R_U_CTRL: + case R_U_ADDR: + case R_U_REMAINING: + r = s->regs[addr]; + break; + + default: + error_report("milkymist_ac97: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_milkymist_ac97_memory_read(addr << 2, r); + + return r; +} + +static void ac97_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + MilkymistAC97State *s = opaque; + + trace_milkymist_ac97_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_AC97_CTRL: + /* always raise an IRQ according to the direction */ + if (value & AC97_CTRL_RQEN) { + if (value & AC97_CTRL_WRITE) { + trace_milkymist_ac97_pulse_irq_crrequest(); + qemu_irq_pulse(s->crrequest_irq); + } else { + trace_milkymist_ac97_pulse_irq_crreply(); + qemu_irq_pulse(s->crreply_irq); + } + } + + /* RQEN is self clearing */ + s->regs[addr] = value & ~AC97_CTRL_RQEN; + break; + case R_D_CTRL: + case R_U_CTRL: + s->regs[addr] = value; + update_voices(s); + break; + case R_AC97_ADDR: + case R_AC97_DATAOUT: + case R_AC97_DATAIN: + case R_D_ADDR: + case R_D_REMAINING: + case R_U_ADDR: + case R_U_REMAINING: + s->regs[addr] = value; + break; + + default: + error_report("milkymist_ac97: write access to unknown register 0x" + TARGET_FMT_plx, addr); + break; + } + +} + +static const MemoryRegionOps ac97_mmio_ops = { + .read = ac97_read, + .write = ac97_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void ac97_in_cb(void *opaque, int avail_b) +{ + MilkymistAC97State *s = opaque; + uint8_t buf[4096]; + uint32_t remaining = s->regs[R_U_REMAINING]; + int temp = audio_MIN(remaining, avail_b); + uint32_t addr = s->regs[R_U_ADDR]; + int transferred = 0; + + trace_milkymist_ac97_in_cb(avail_b, remaining); + + /* prevent from raising an IRQ */ + if (temp == 0) { + return; + } + + while (temp) { + int acquired, to_copy; + + to_copy = audio_MIN(temp, sizeof(buf)); + acquired = AUD_read(s->voice_in, buf, to_copy); + if (!acquired) { + break; + } + + cpu_physical_memory_write(addr, buf, acquired); + + temp -= acquired; + addr += acquired; + transferred += acquired; + } + + trace_milkymist_ac97_in_cb_transferred(transferred); + + s->regs[R_U_ADDR] = addr; + s->regs[R_U_REMAINING] -= transferred; + + if ((s->regs[R_U_CTRL] & CTRL_EN) && (s->regs[R_U_REMAINING] == 0)) { + trace_milkymist_ac97_pulse_irq_dmaw(); + qemu_irq_pulse(s->dmaw_irq); + } +} + +static void ac97_out_cb(void *opaque, int free_b) +{ + MilkymistAC97State *s = opaque; + uint8_t buf[4096]; + uint32_t remaining = s->regs[R_D_REMAINING]; + int temp = audio_MIN(remaining, free_b); + uint32_t addr = s->regs[R_D_ADDR]; + int transferred = 0; + + trace_milkymist_ac97_out_cb(free_b, remaining); + + /* prevent from raising an IRQ */ + if (temp == 0) { + return; + } + + while (temp) { + int copied, to_copy; + + to_copy = audio_MIN(temp, sizeof(buf)); + cpu_physical_memory_read(addr, buf, to_copy); + copied = AUD_write(s->voice_out, buf, to_copy); + if (!copied) { + break; + } + temp -= copied; + addr += copied; + transferred += copied; + } + + trace_milkymist_ac97_out_cb_transferred(transferred); + + s->regs[R_D_ADDR] = addr; + s->regs[R_D_REMAINING] -= transferred; + + if ((s->regs[R_D_CTRL] & CTRL_EN) && (s->regs[R_D_REMAINING] == 0)) { + trace_milkymist_ac97_pulse_irq_dmar(); + qemu_irq_pulse(s->dmar_irq); + } +} + +static void milkymist_ac97_reset(DeviceState *d) +{ + MilkymistAC97State *s = MILKYMIST_AC97(d); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } + + AUD_set_active_in(s->voice_in, 0); + AUD_set_active_out(s->voice_out, 0); +} + +static int ac97_post_load(void *opaque, int version_id) +{ + MilkymistAC97State *s = opaque; + + update_voices(s); + + return 0; +} + +static int milkymist_ac97_init(SysBusDevice *dev) +{ + MilkymistAC97State *s = MILKYMIST_AC97(dev); + + struct audsettings as; + sysbus_init_irq(dev, &s->crrequest_irq); + sysbus_init_irq(dev, &s->crreply_irq); + sysbus_init_irq(dev, &s->dmar_irq); + sysbus_init_irq(dev, &s->dmaw_irq); + + AUD_register_card("Milkymist AC'97", &s->card); + + as.freq = 48000; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 1; + + s->voice_in = AUD_open_in(&s->card, s->voice_in, + "mm_ac97.in", s, ac97_in_cb, &as); + s->voice_out = AUD_open_out(&s->card, s->voice_out, + "mm_ac97.out", s, ac97_out_cb, &as); + + memory_region_init_io(&s->regs_region, OBJECT(s), &ac97_mmio_ops, s, + "milkymist-ac97", R_MAX * 4); + sysbus_init_mmio(dev, &s->regs_region); + + return 0; +} + +static const VMStateDescription vmstate_milkymist_ac97 = { + .name = "milkymist-ac97", + .version_id = 1, + .minimum_version_id = 1, + .post_load = ac97_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void milkymist_ac97_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = milkymist_ac97_init; + dc->reset = milkymist_ac97_reset; + dc->vmsd = &vmstate_milkymist_ac97; +} + +static const TypeInfo milkymist_ac97_info = { + .name = TYPE_MILKYMIST_AC97, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MilkymistAC97State), + .class_init = milkymist_ac97_class_init, +}; + +static void milkymist_ac97_register_types(void) +{ + type_register_static(&milkymist_ac97_info); +} + +type_init(milkymist_ac97_register_types) diff --git a/qemu/hw/audio/pcspk.c b/qemu/hw/audio/pcspk.c new file mode 100644 index 000000000..5266fb545 --- /dev/null +++ b/qemu/hw/audio/pcspk.c @@ -0,0 +1,213 @@ +/* + * QEMU PC speaker emulation + * + * Copyright (c) 2006 Joachim Henke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/isa/isa.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "qemu/timer.h" +#include "hw/timer/i8254.h" +#include "hw/audio/pcspk.h" + +#define PCSPK_BUF_LEN 1792 +#define PCSPK_SAMPLE_RATE 32000 +#define PCSPK_MAX_FREQ (PCSPK_SAMPLE_RATE >> 1) +#define PCSPK_MIN_COUNT ((PIT_FREQ + PCSPK_MAX_FREQ - 1) / PCSPK_MAX_FREQ) + +#define PC_SPEAKER(obj) OBJECT_CHECK(PCSpkState, (obj), TYPE_PC_SPEAKER) + +typedef struct { + ISADevice parent_obj; + + MemoryRegion ioport; + uint32_t iobase; + uint8_t sample_buf[PCSPK_BUF_LEN]; + QEMUSoundCard card; + SWVoiceOut *voice; + void *pit; + unsigned int pit_count; + unsigned int samples; + unsigned int play_pos; + int data_on; + int dummy_refresh_clock; +} PCSpkState; + +static const char *s_spk = "pcspk"; +static PCSpkState *pcspk_state; + +static inline void generate_samples(PCSpkState *s) +{ + unsigned int i; + + if (s->pit_count) { + const uint32_t m = PCSPK_SAMPLE_RATE * s->pit_count; + const uint32_t n = ((uint64_t)PIT_FREQ << 32) / m; + + /* multiple of wavelength for gapless looping */ + s->samples = (PCSPK_BUF_LEN * PIT_FREQ / m * m / (PIT_FREQ >> 1) + 1) >> 1; + for (i = 0; i < s->samples; ++i) + s->sample_buf[i] = (64 & (n * i >> 25)) - 32; + } else { + s->samples = PCSPK_BUF_LEN; + for (i = 0; i < PCSPK_BUF_LEN; ++i) + s->sample_buf[i] = 128; /* silence */ + } +} + +static void pcspk_callback(void *opaque, int free) +{ + PCSpkState *s = opaque; + PITChannelInfo ch; + unsigned int n; + + pit_get_channel_info(s->pit, 2, &ch); + + if (ch.mode != 3) { + return; + } + + n = ch.initial_count; + /* avoid frequencies that are not reproducible with sample rate */ + if (n < PCSPK_MIN_COUNT) + n = 0; + + if (s->pit_count != n) { + s->pit_count = n; + s->play_pos = 0; + generate_samples(s); + } + + while (free > 0) { + n = audio_MIN(s->samples - s->play_pos, (unsigned int)free); + n = AUD_write(s->voice, &s->sample_buf[s->play_pos], n); + if (!n) + break; + s->play_pos = (s->play_pos + n) % s->samples; + free -= n; + } +} + +static int pcspk_audio_init(ISABus *bus) +{ + PCSpkState *s = pcspk_state; + struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUD_FMT_U8, 0}; + + AUD_register_card(s_spk, &s->card); + + s->voice = AUD_open_out(&s->card, s->voice, s_spk, s, pcspk_callback, &as); + if (!s->voice) { + AUD_log(s_spk, "Could not open voice\n"); + return -1; + } + + return 0; +} + +static uint64_t pcspk_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + PCSpkState *s = opaque; + PITChannelInfo ch; + + pit_get_channel_info(s->pit, 2, &ch); + + s->dummy_refresh_clock ^= (1 << 4); + + return ch.gate | (s->data_on << 1) | s->dummy_refresh_clock | + (ch.out << 5); +} + +static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + PCSpkState *s = opaque; + const int gate = val & 1; + + s->data_on = (val >> 1) & 1; + pit_set_gate(s->pit, 2, gate); + if (s->voice) { + if (gate) /* restart */ + s->play_pos = 0; + AUD_set_active_out(s->voice, gate & s->data_on); + } +} + +static const MemoryRegionOps pcspk_io_ops = { + .read = pcspk_io_read, + .write = pcspk_io_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void pcspk_initfn(Object *obj) +{ + PCSpkState *s = PC_SPEAKER(obj); + + memory_region_init_io(&s->ioport, OBJECT(s), &pcspk_io_ops, s, "pcspk", 1); +} + +static void pcspk_realizefn(DeviceState *dev, Error **errp) +{ + ISADevice *isadev = ISA_DEVICE(dev); + PCSpkState *s = PC_SPEAKER(dev); + + isa_register_ioport(isadev, &s->ioport, s->iobase); + + pcspk_state = s; +} + +static Property pcspk_properties[] = { + DEFINE_PROP_UINT32("iobase", PCSpkState, iobase, -1), + DEFINE_PROP_PTR("pit", PCSpkState, pit), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pcspk_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = pcspk_realizefn; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->props = pcspk_properties; + /* Reason: pointer property "pit", realize sets global pcspk_state */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo pcspk_info = { + .name = TYPE_PC_SPEAKER, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(PCSpkState), + .instance_init = pcspk_initfn, + .class_init = pcspk_class_initfn, +}; + +static void pcspk_register(void) +{ + type_register_static(&pcspk_info); + isa_register_soundhw("pcspk", "PC speaker", pcspk_audio_init); +} +type_init(pcspk_register) diff --git a/qemu/hw/audio/pl041.c b/qemu/hw/audio/pl041.c new file mode 100644 index 000000000..19982f247 --- /dev/null +++ b/qemu/hw/audio/pl041.c @@ -0,0 +1,649 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licensed under the GPL. + * + * ***************************************************************** + * + * This driver emulates the ARM AACI interface + * connected to a LM4549 codec. + * + * Limitations: + * - Supports only a playback on one channel (Versatile/Vexpress) + * - Supports only one TX FIFO in compact-mode or non-compact mode. + * - Supports playback of 12, 16, 18 and 20 bits samples. + * - Record is not supported. + * - The PL041 is hardwired to a LM4549 codec. + * + */ + +#include "hw/sysbus.h" + +#include "pl041.h" +#include "lm4549.h" + +#if 0 +#define PL041_DEBUG_LEVEL 1 +#endif + +#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1) +#define DBG_L1(fmt, ...) \ +do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DBG_L1(fmt, ...) \ +do { } while (0) +#endif + +#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 2) +#define DBG_L2(fmt, ...) \ +do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DBG_L2(fmt, ...) \ +do { } while (0) +#endif + + +#define MAX_FIFO_DEPTH (1024) +#define DEFAULT_FIFO_DEPTH (8) + +#define SLOT1_RW (1 << 19) + +/* This FIFO only stores 20-bit samples on 32-bit words. + So its level is independent of the selected mode */ +typedef struct { + uint32_t level; + uint32_t data[MAX_FIFO_DEPTH]; +} pl041_fifo; + +typedef struct { + pl041_fifo tx_fifo; + uint8_t tx_enabled; + uint8_t tx_compact_mode; + uint8_t tx_sample_size; + + pl041_fifo rx_fifo; + uint8_t rx_enabled; + uint8_t rx_compact_mode; + uint8_t rx_sample_size; +} pl041_channel; + +#define TYPE_PL041 "pl041" +#define PL041(obj) OBJECT_CHECK(PL041State, (obj), TYPE_PL041) + +typedef struct PL041State { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + + uint32_t fifo_depth; /* FIFO depth in non-compact mode */ + + pl041_regfile regs; + pl041_channel fifo1; + lm4549_state codec; +} PL041State; + + +static const unsigned char pl041_default_id[8] = { + 0x41, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 +}; + +#if defined(PL041_DEBUG_LEVEL) +#define REGISTER(name, offset) #name, +static const char *pl041_regs_name[] = { + #include "pl041.hx" +}; +#undef REGISTER +#endif + + +#if defined(PL041_DEBUG_LEVEL) +static const char *get_reg_name(hwaddr offset) +{ + if (offset <= PL041_dr1_7) { + return pl041_regs_name[offset >> 2]; + } + + return "unknown"; +} +#endif + +static uint8_t pl041_compute_periphid3(PL041State *s) +{ + uint8_t id3 = 1; /* One channel */ + + /* Add the fifo depth information */ + switch (s->fifo_depth) { + case 8: + id3 |= 0 << 3; + break; + case 32: + id3 |= 1 << 3; + break; + case 64: + id3 |= 2 << 3; + break; + case 128: + id3 |= 3 << 3; + break; + case 256: + id3 |= 4 << 3; + break; + case 512: + id3 |= 5 << 3; + break; + case 1024: + id3 |= 6 << 3; + break; + case 2048: + id3 |= 7 << 3; + break; + } + + return id3; +} + +static void pl041_reset(PL041State *s) +{ + DBG_L1("pl041_reset\n"); + + memset(&s->regs, 0x00, sizeof(pl041_regfile)); + + s->regs.slfr = SL1TXEMPTY | SL2TXEMPTY | SL12TXEMPTY; + s->regs.sr1 = TXFE | RXFE | TXHE; + s->regs.isr1 = 0; + + memset(&s->fifo1, 0x00, sizeof(s->fifo1)); +} + + +static void pl041_fifo1_write(PL041State *s, uint32_t value) +{ + pl041_channel *channel = &s->fifo1; + pl041_fifo *fifo = &s->fifo1.tx_fifo; + + /* Push the value in the FIFO */ + if (channel->tx_compact_mode == 0) { + /* Non-compact mode */ + + if (fifo->level < s->fifo_depth) { + /* Pad the value with 0 to obtain a 20-bit sample */ + switch (channel->tx_sample_size) { + case 12: + value = (value << 8) & 0xFFFFF; + break; + case 16: + value = (value << 4) & 0xFFFFF; + break; + case 18: + value = (value << 2) & 0xFFFFF; + break; + case 20: + default: + break; + } + + /* Store the sample in the FIFO */ + fifo->data[fifo->level++] = value; + } +#if defined(PL041_DEBUG_LEVEL) + else { + DBG_L1("fifo1 write: overrun\n"); + } +#endif + } else { + /* Compact mode */ + + if ((fifo->level + 2) < s->fifo_depth) { + uint32_t i = 0; + uint32_t sample = 0; + + for (i = 0; i < 2; i++) { + sample = value & 0xFFFF; + value = value >> 16; + + /* Pad each sample with 0 to obtain a 20-bit sample */ + switch (channel->tx_sample_size) { + case 12: + sample = sample << 8; + break; + case 16: + default: + sample = sample << 4; + break; + } + + /* Store the sample in the FIFO */ + fifo->data[fifo->level++] = sample; + } + } +#if defined(PL041_DEBUG_LEVEL) + else { + DBG_L1("fifo1 write: overrun\n"); + } +#endif + } + + /* Update the status register */ + if (fifo->level > 0) { + s->regs.sr1 &= ~(TXUNDERRUN | TXFE); + } + + if (fifo->level >= (s->fifo_depth / 2)) { + s->regs.sr1 &= ~TXHE; + } + + if (fifo->level >= s->fifo_depth) { + s->regs.sr1 |= TXFF; + } + + DBG_L2("fifo1_push sr1 = 0x%08x\n", s->regs.sr1); +} + +static void pl041_fifo1_transmit(PL041State *s) +{ + pl041_channel *channel = &s->fifo1; + pl041_fifo *fifo = &s->fifo1.tx_fifo; + uint32_t slots = s->regs.txcr1 & TXSLOT_MASK; + uint32_t written_samples; + + /* Check if FIFO1 transmit is enabled */ + if ((channel->tx_enabled) && (slots & (TXSLOT3 | TXSLOT4))) { + if (fifo->level >= (s->fifo_depth / 2)) { + int i; + + DBG_L1("Transfer FIFO level = %i\n", fifo->level); + + /* Try to transfer the whole FIFO */ + for (i = 0; i < (fifo->level / 2); i++) { + uint32_t left = fifo->data[i * 2]; + uint32_t right = fifo->data[i * 2 + 1]; + + /* Transmit two 20-bit samples to the codec */ + if (lm4549_write_samples(&s->codec, left, right) == 0) { + DBG_L1("Codec buffer full\n"); + break; + } + } + + written_samples = i * 2; + if (written_samples > 0) { + /* Update the FIFO level */ + fifo->level -= written_samples; + + /* Move back the pending samples to the start of the FIFO */ + for (i = 0; i < fifo->level; i++) { + fifo->data[i] = fifo->data[written_samples + i]; + } + + /* Update the status register */ + s->regs.sr1 &= ~TXFF; + + if (fifo->level <= (s->fifo_depth / 2)) { + s->regs.sr1 |= TXHE; + } + + if (fifo->level == 0) { + s->regs.sr1 |= TXFE | TXUNDERRUN; + DBG_L1("Empty FIFO\n"); + } + } + } + } +} + +static void pl041_isr1_update(PL041State *s) +{ + /* Update ISR1 */ + if (s->regs.sr1 & TXUNDERRUN) { + s->regs.isr1 |= URINTR; + } else { + s->regs.isr1 &= ~URINTR; + } + + if (s->regs.sr1 & TXHE) { + s->regs.isr1 |= TXINTR; + } else { + s->regs.isr1 &= ~TXINTR; + } + + if (!(s->regs.sr1 & TXBUSY) && (s->regs.sr1 & TXFE)) { + s->regs.isr1 |= TXCINTR; + } else { + s->regs.isr1 &= ~TXCINTR; + } + + /* Update the irq state */ + qemu_set_irq(s->irq, ((s->regs.isr1 & s->regs.ie1) > 0) ? 1 : 0); + DBG_L2("Set interrupt sr1 = 0x%08x isr1 = 0x%08x masked = 0x%08x\n", + s->regs.sr1, s->regs.isr1, s->regs.isr1 & s->regs.ie1); +} + +static void pl041_request_data(void *opaque) +{ + PL041State *s = (PL041State *)opaque; + + /* Trigger pending transfers */ + pl041_fifo1_transmit(s); + pl041_isr1_update(s); +} + +static uint64_t pl041_read(void *opaque, hwaddr offset, + unsigned size) +{ + PL041State *s = (PL041State *)opaque; + int value; + + if ((offset >= PL041_periphid0) && (offset <= PL041_pcellid3)) { + if (offset == PL041_periphid3) { + value = pl041_compute_periphid3(s); + } else { + value = pl041_default_id[(offset - PL041_periphid0) >> 2]; + } + + DBG_L1("pl041_read [0x%08x] => 0x%08x\n", offset, value); + return value; + } else if (offset <= PL041_dr4_7) { + value = *((uint32_t *)&s->regs + (offset >> 2)); + } else { + DBG_L1("pl041_read: Reserved offset %x\n", (int)offset); + return 0; + } + + switch (offset) { + case PL041_allints: + value = s->regs.isr1 & 0x7F; + break; + } + + DBG_L1("pl041_read [0x%08x] %s => 0x%08x\n", offset, + get_reg_name(offset), value); + + return value; +} + +static void pl041_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PL041State *s = (PL041State *)opaque; + uint16_t control, data; + uint32_t result; + + DBG_L1("pl041_write [0x%08x] %s <= 0x%08x\n", offset, + get_reg_name(offset), (unsigned int)value); + + /* Write the register */ + if (offset <= PL041_dr4_7) { + *((uint32_t *)&s->regs + (offset >> 2)) = value; + } else { + DBG_L1("pl041_write: Reserved offset %x\n", (int)offset); + return; + } + + /* Execute the actions */ + switch (offset) { + case PL041_txcr1: + { + pl041_channel *channel = &s->fifo1; + + uint32_t txen = s->regs.txcr1 & TXEN; + uint32_t tsize = (s->regs.txcr1 & TSIZE_MASK) >> TSIZE_MASK_BIT; + uint32_t compact_mode = (s->regs.txcr1 & TXCOMPACT) ? 1 : 0; +#if defined(PL041_DEBUG_LEVEL) + uint32_t slots = (s->regs.txcr1 & TXSLOT_MASK) >> TXSLOT_MASK_BIT; + uint32_t txfen = (s->regs.txcr1 & TXFEN) > 0 ? 1 : 0; +#endif + + DBG_L1("=> txen = %i slots = 0x%01x tsize = %i compact = %i " + "txfen = %i\n", txen, slots, tsize, compact_mode, txfen); + + channel->tx_enabled = txen; + channel->tx_compact_mode = compact_mode; + + switch (tsize) { + case 0: + channel->tx_sample_size = 16; + break; + case 1: + channel->tx_sample_size = 18; + break; + case 2: + channel->tx_sample_size = 20; + break; + case 3: + channel->tx_sample_size = 12; + break; + } + + DBG_L1("TX enabled = %i\n", channel->tx_enabled); + DBG_L1("TX compact mode = %i\n", channel->tx_compact_mode); + DBG_L1("TX sample width = %i\n", channel->tx_sample_size); + + /* Check if compact mode is allowed with selected tsize */ + if (channel->tx_compact_mode == 1) { + if ((channel->tx_sample_size == 18) || + (channel->tx_sample_size == 20)) { + channel->tx_compact_mode = 0; + DBG_L1("Compact mode not allowed with 18/20-bit sample size\n"); + } + } + + break; + } + case PL041_sl1tx: + s->regs.slfr &= ~SL1TXEMPTY; + + control = (s->regs.sl1tx >> 12) & 0x7F; + data = (s->regs.sl2tx >> 4) & 0xFFFF; + + if ((s->regs.sl1tx & SLOT1_RW) == 0) { + /* Write operation */ + lm4549_write(&s->codec, control, data); + } else { + /* Read operation */ + result = lm4549_read(&s->codec, control); + + /* Store the returned value */ + s->regs.sl1rx = s->regs.sl1tx & ~SLOT1_RW; + s->regs.sl2rx = result << 4; + + s->regs.slfr &= ~(SL1RXBUSY | SL2RXBUSY); + s->regs.slfr |= SL1RXVALID | SL2RXVALID; + } + break; + + case PL041_sl2tx: + s->regs.sl2tx = value; + s->regs.slfr &= ~SL2TXEMPTY; + break; + + case PL041_intclr: + DBG_L1("=> Clear interrupt intclr = 0x%08x isr1 = 0x%08x\n", + s->regs.intclr, s->regs.isr1); + + if (s->regs.intclr & TXUEC1) { + s->regs.sr1 &= ~TXUNDERRUN; + } + break; + + case PL041_maincr: + { +#if defined(PL041_DEBUG_LEVEL) + char debug[] = " AACIFE SL1RXEN SL1TXEN"; + if (!(value & AACIFE)) { + debug[0] = '!'; + } + if (!(value & SL1RXEN)) { + debug[8] = '!'; + } + if (!(value & SL1TXEN)) { + debug[17] = '!'; + } + DBG_L1("%s\n", debug); +#endif + + if ((s->regs.maincr & AACIFE) == 0) { + pl041_reset(s); + } + break; + } + + case PL041_dr1_0: + case PL041_dr1_1: + case PL041_dr1_2: + case PL041_dr1_3: + pl041_fifo1_write(s, value); + break; + } + + /* Transmit the FIFO content */ + pl041_fifo1_transmit(s); + + /* Update the ISR1 register */ + pl041_isr1_update(s); +} + +static void pl041_device_reset(DeviceState *d) +{ + PL041State *s = PL041(d); + + pl041_reset(s); +} + +static const MemoryRegionOps pl041_ops = { + .read = pl041_read, + .write = pl041_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int pl041_init(SysBusDevice *dev) +{ + PL041State *s = PL041(dev); + + DBG_L1("pl041_init 0x%08x\n", (uint32_t)s); + + /* Check the device properties */ + switch (s->fifo_depth) { + case 8: + case 32: + case 64: + case 128: + case 256: + case 512: + case 1024: + case 2048: + break; + case 16: + default: + /* NC FIFO depth of 16 is not allowed because its id bits in + AACIPERIPHID3 overlap with the id for the default NC FIFO depth */ + qemu_log_mask(LOG_UNIMP, + "pl041: unsupported non-compact fifo depth [%i]\n", + s->fifo_depth); + return -1; + } + + /* Connect the device to the sysbus */ + memory_region_init_io(&s->iomem, OBJECT(s), &pl041_ops, s, "pl041", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + /* Init the codec */ + lm4549_init(&s->codec, &pl041_request_data, (void *)s); + + return 0; +} + +static const VMStateDescription vmstate_pl041_regfile = { + .name = "pl041_regfile", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { +#define REGISTER(name, offset) VMSTATE_UINT32(name, pl041_regfile), + #include "pl041.hx" +#undef REGISTER + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pl041_fifo = { + .name = "pl041_fifo", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(level, pl041_fifo), + VMSTATE_UINT32_ARRAY(data, pl041_fifo, MAX_FIFO_DEPTH), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pl041_channel = { + .name = "pl041_channel", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(tx_fifo, pl041_channel, 0, + vmstate_pl041_fifo, pl041_fifo), + VMSTATE_UINT8(tx_enabled, pl041_channel), + VMSTATE_UINT8(tx_compact_mode, pl041_channel), + VMSTATE_UINT8(tx_sample_size, pl041_channel), + VMSTATE_STRUCT(rx_fifo, pl041_channel, 0, + vmstate_pl041_fifo, pl041_fifo), + VMSTATE_UINT8(rx_enabled, pl041_channel), + VMSTATE_UINT8(rx_compact_mode, pl041_channel), + VMSTATE_UINT8(rx_sample_size, pl041_channel), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pl041 = { + .name = "pl041", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(fifo_depth, PL041State), + VMSTATE_STRUCT(regs, PL041State, 0, + vmstate_pl041_regfile, pl041_regfile), + VMSTATE_STRUCT(fifo1, PL041State, 0, + vmstate_pl041_channel, pl041_channel), + VMSTATE_STRUCT(codec, PL041State, 0, + vmstate_lm4549_state, lm4549_state), + VMSTATE_END_OF_LIST() + } +}; + +static Property pl041_device_properties[] = { + /* Non-compact FIFO depth property */ + DEFINE_PROP_UINT32("nc_fifo_depth", PL041State, fifo_depth, + DEFAULT_FIFO_DEPTH), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pl041_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl041_init; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->reset = pl041_device_reset; + dc->vmsd = &vmstate_pl041; + dc->props = pl041_device_properties; +} + +static const TypeInfo pl041_device_info = { + .name = TYPE_PL041, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PL041State), + .class_init = pl041_device_class_init, +}; + +static void pl041_register_types(void) +{ + type_register_static(&pl041_device_info); +} + +type_init(pl041_register_types) diff --git a/qemu/hw/audio/pl041.h b/qemu/hw/audio/pl041.h new file mode 100644 index 000000000..427ab6d6f --- /dev/null +++ b/qemu/hw/audio/pl041.h @@ -0,0 +1,135 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licensed under the GPL. + * + * ***************************************************************** + */ + +#ifndef HW_PL041_H +#define HW_PL041_H + +/* Register file */ +#define REGISTER(name, offset) uint32_t name; +typedef struct { + #include "pl041.hx" +} pl041_regfile; +#undef REGISTER + +/* Register addresses */ +#define REGISTER(name, offset) PL041_##name = offset, +enum { + #include "pl041.hx" + + PL041_periphid0 = 0xFE0, + PL041_periphid1 = 0xFE4, + PL041_periphid2 = 0xFE8, + PL041_periphid3 = 0xFEC, + PL041_pcellid0 = 0xFF0, + PL041_pcellid1 = 0xFF4, + PL041_pcellid2 = 0xFF8, + PL041_pcellid3 = 0xFFC, +}; +#undef REGISTER + +/* Register bits */ + +/* IEx */ +#define TXCIE (1 << 0) +#define RXTIE (1 << 1) +#define TXIE (1 << 2) +#define RXIE (1 << 3) +#define RXOIE (1 << 4) +#define TXUIE (1 << 5) +#define RXTOIE (1 << 6) + +/* TXCRx */ +#define TXEN (1 << 0) +#define TXSLOT1 (1 << 1) +#define TXSLOT2 (1 << 2) +#define TXSLOT3 (1 << 3) +#define TXSLOT4 (1 << 4) +#define TXCOMPACT (1 << 15) +#define TXFEN (1 << 16) + +#define TXSLOT_MASK_BIT (1) +#define TXSLOT_MASK (0xFFF << TXSLOT_MASK_BIT) + +#define TSIZE_MASK_BIT (13) +#define TSIZE_MASK (0x3 << TSIZE_MASK_BIT) + +#define TSIZE_16BITS (0x0 << TSIZE_MASK_BIT) +#define TSIZE_18BITS (0x1 << TSIZE_MASK_BIT) +#define TSIZE_20BITS (0x2 << TSIZE_MASK_BIT) +#define TSIZE_12BITS (0x3 << TSIZE_MASK_BIT) + +/* SRx */ +#define RXFE (1 << 0) +#define TXFE (1 << 1) +#define RXHF (1 << 2) +#define TXHE (1 << 3) +#define RXFF (1 << 4) +#define TXFF (1 << 5) +#define RXBUSY (1 << 6) +#define TXBUSY (1 << 7) +#define RXOVERRUN (1 << 8) +#define TXUNDERRUN (1 << 9) +#define RXTIMEOUT (1 << 10) +#define RXTOFE (1 << 11) + +/* ISRx */ +#define TXCINTR (1 << 0) +#define RXTOINTR (1 << 1) +#define TXINTR (1 << 2) +#define RXINTR (1 << 3) +#define ORINTR (1 << 4) +#define URINTR (1 << 5) +#define RXTOFEINTR (1 << 6) + +/* SLFR */ +#define SL1RXBUSY (1 << 0) +#define SL1TXBUSY (1 << 1) +#define SL2RXBUSY (1 << 2) +#define SL2TXBUSY (1 << 3) +#define SL12RXBUSY (1 << 4) +#define SL12TXBUSY (1 << 5) +#define SL1RXVALID (1 << 6) +#define SL1TXEMPTY (1 << 7) +#define SL2RXVALID (1 << 8) +#define SL2TXEMPTY (1 << 9) +#define SL12RXVALID (1 << 10) +#define SL12TXEMPTY (1 << 11) +#define RAWGPIOINT (1 << 12) +#define RWIS (1 << 13) + +/* MAINCR */ +#define AACIFE (1 << 0) +#define LOOPBACK (1 << 1) +#define LOWPOWER (1 << 2) +#define SL1RXEN (1 << 3) +#define SL1TXEN (1 << 4) +#define SL2RXEN (1 << 5) +#define SL2TXEN (1 << 6) +#define SL12RXEN (1 << 7) +#define SL12TXEN (1 << 8) +#define DMAENABLE (1 << 9) + +/* INTCLR */ +#define WISC (1 << 0) +#define RXOEC1 (1 << 1) +#define RXOEC2 (1 << 2) +#define RXOEC3 (1 << 3) +#define RXOEC4 (1 << 4) +#define TXUEC1 (1 << 5) +#define TXUEC2 (1 << 6) +#define TXUEC3 (1 << 7) +#define TXUEC4 (1 << 8) +#define RXTOFEC1 (1 << 9) +#define RXTOFEC2 (1 << 10) +#define RXTOFEC3 (1 << 11) +#define RXTOFEC4 (1 << 12) + +#endif /* #ifndef HW_PL041_H */ diff --git a/qemu/hw/audio/pl041.hx b/qemu/hw/audio/pl041.hx new file mode 100644 index 000000000..dd7188cbc --- /dev/null +++ b/qemu/hw/audio/pl041.hx @@ -0,0 +1,81 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licensed under the GPL. + * + * ***************************************************************** + */ + +/* PL041 register file description */ + +REGISTER( rxcr1, 0x00 ) +REGISTER( txcr1, 0x04 ) +REGISTER( sr1, 0x08 ) +REGISTER( isr1, 0x0C ) +REGISTER( ie1, 0x10 ) +REGISTER( rxcr2, 0x14 ) +REGISTER( txcr2, 0x18 ) +REGISTER( sr2, 0x1C ) +REGISTER( isr2, 0x20 ) +REGISTER( ie2, 0x24 ) +REGISTER( rxcr3, 0x28 ) +REGISTER( txcr3, 0x2C ) +REGISTER( sr3, 0x30 ) +REGISTER( isr3, 0x34 ) +REGISTER( ie3, 0x38 ) +REGISTER( rxcr4, 0x3C ) +REGISTER( txcr4, 0x40 ) +REGISTER( sr4, 0x44 ) +REGISTER( isr4, 0x48 ) +REGISTER( ie4, 0x4C ) +REGISTER( sl1rx, 0x50 ) +REGISTER( sl1tx, 0x54 ) +REGISTER( sl2rx, 0x58 ) +REGISTER( sl2tx, 0x5C ) +REGISTER( sl12rx, 0x60 ) +REGISTER( sl12tx, 0x64 ) +REGISTER( slfr, 0x68 ) +REGISTER( slistat, 0x6C ) +REGISTER( slien, 0x70 ) +REGISTER( intclr, 0x74 ) +REGISTER( maincr, 0x78 ) +REGISTER( reset, 0x7C ) +REGISTER( sync, 0x80 ) +REGISTER( allints, 0x84 ) +REGISTER( mainfr, 0x88 ) +REGISTER( unused, 0x8C ) +REGISTER( dr1_0, 0x90 ) +REGISTER( dr1_1, 0x94 ) +REGISTER( dr1_2, 0x98 ) +REGISTER( dr1_3, 0x9C ) +REGISTER( dr1_4, 0xA0 ) +REGISTER( dr1_5, 0xA4 ) +REGISTER( dr1_6, 0xA8 ) +REGISTER( dr1_7, 0xAC ) +REGISTER( dr2_0, 0xB0 ) +REGISTER( dr2_1, 0xB4 ) +REGISTER( dr2_2, 0xB8 ) +REGISTER( dr2_3, 0xBC ) +REGISTER( dr2_4, 0xC0 ) +REGISTER( dr2_5, 0xC4 ) +REGISTER( dr2_6, 0xC8 ) +REGISTER( dr2_7, 0xCC ) +REGISTER( dr3_0, 0xD0 ) +REGISTER( dr3_1, 0xD4 ) +REGISTER( dr3_2, 0xD8 ) +REGISTER( dr3_3, 0xDC ) +REGISTER( dr3_4, 0xE0 ) +REGISTER( dr3_5, 0xE4 ) +REGISTER( dr3_6, 0xE8 ) +REGISTER( dr3_7, 0xEC ) +REGISTER( dr4_0, 0xF0 ) +REGISTER( dr4_1, 0xF4 ) +REGISTER( dr4_2, 0xF8 ) +REGISTER( dr4_3, 0xFC ) +REGISTER( dr4_4, 0x100 ) +REGISTER( dr4_5, 0x104 ) +REGISTER( dr4_6, 0x108 ) +REGISTER( dr4_7, 0x10C ) diff --git a/qemu/hw/audio/sb16.c b/qemu/hw/audio/sb16.c new file mode 100644 index 000000000..b052de5f7 --- /dev/null +++ b/qemu/hw/audio/sb16.c @@ -0,0 +1,1427 @@ +/* + * QEMU Soundblaster 16 emulation + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/audio/audio.h" +#include "audio/audio.h" +#include "hw/isa/isa.h" +#include "hw/qdev.h" +#include "qemu/timer.h" +#include "qemu/host-utils.h" + +#define dolog(...) AUD_log ("sb16", __VA_ARGS__) + +/* #define DEBUG */ +/* #define DEBUG_SB16_MOST */ + +#ifdef DEBUG +#define ldebug(...) dolog (__VA_ARGS__) +#else +#define ldebug(...) +#endif + +#define IO_READ_PROTO(name) \ + uint32_t name (void *opaque, uint32_t nport) +#define IO_WRITE_PROTO(name) \ + void name (void *opaque, uint32_t nport, uint32_t val) + +static const char e3[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992."; + +#define TYPE_SB16 "sb16" +#define SB16(obj) OBJECT_CHECK (SB16State, (obj), TYPE_SB16) + +typedef struct SB16State { + ISADevice parent_obj; + + QEMUSoundCard card; + qemu_irq pic; + uint32_t irq; + uint32_t dma; + uint32_t hdma; + uint32_t port; + uint32_t ver; + + int in_index; + int out_data_len; + int fmt_stereo; + int fmt_signed; + int fmt_bits; + audfmt_e fmt; + int dma_auto; + int block_size; + int fifo; + int freq; + int time_const; + int speaker; + int needed_bytes; + int cmd; + int use_hdma; + int highspeed; + int can_write; + + int v2x6; + + uint8_t csp_param; + uint8_t csp_value; + uint8_t csp_mode; + uint8_t csp_regs[256]; + uint8_t csp_index; + uint8_t csp_reg83[4]; + int csp_reg83r; + int csp_reg83w; + + uint8_t in2_data[10]; + uint8_t out_data[50]; + uint8_t test_reg; + uint8_t last_read_byte; + int nzero; + + int left_till_irq; + + int dma_running; + int bytes_per_second; + int align; + int audio_free; + SWVoiceOut *voice; + + QEMUTimer *aux_ts; + /* mixer state */ + int mixer_nreg; + uint8_t mixer_regs[256]; +} SB16State; + +static void SB_audio_callback (void *opaque, int free); + +static int magic_of_irq (int irq) +{ + switch (irq) { + case 5: + return 2; + case 7: + return 4; + case 9: + return 1; + case 10: + return 8; + default: + dolog ("bad irq %d\n", irq); + return 2; + } +} + +static int irq_of_magic (int magic) +{ + switch (magic) { + case 1: + return 9; + case 2: + return 5; + case 4: + return 7; + case 8: + return 10; + default: + dolog ("bad irq magic %d\n", magic); + return -1; + } +} + +#if 0 +static void log_dsp (SB16State *dsp) +{ + ldebug ("%s:%s:%d:%s:dmasize=%d:freq=%d:const=%d:speaker=%d\n", + dsp->fmt_stereo ? "Stereo" : "Mono", + dsp->fmt_signed ? "Signed" : "Unsigned", + dsp->fmt_bits, + dsp->dma_auto ? "Auto" : "Single", + dsp->block_size, + dsp->freq, + dsp->time_const, + dsp->speaker); +} +#endif + +static void speaker (SB16State *s, int on) +{ + s->speaker = on; + /* AUD_enable (s->voice, on); */ +} + +static void control (SB16State *s, int hold) +{ + int dma = s->use_hdma ? s->hdma : s->dma; + s->dma_running = hold; + + ldebug ("hold %d high %d dma %d\n", hold, s->use_hdma, dma); + + if (hold) { + DMA_hold_DREQ (dma); + AUD_set_active_out (s->voice, 1); + } + else { + DMA_release_DREQ (dma); + AUD_set_active_out (s->voice, 0); + } +} + +static void aux_timer (void *opaque) +{ + SB16State *s = opaque; + s->can_write = 1; + qemu_irq_raise (s->pic); +} + +#define DMA8_AUTO 1 +#define DMA8_HIGH 2 + +static void continue_dma8 (SB16State *s) +{ + if (s->freq > 0) { + struct audsettings as; + + s->audio_free = 0; + + as.freq = s->freq; + as.nchannels = 1 << s->fmt_stereo; + as.fmt = s->fmt; + as.endianness = 0; + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "sb16", + s, + SB_audio_callback, + &as + ); + } + + control (s, 1); +} + +static void dma_cmd8 (SB16State *s, int mask, int dma_len) +{ + s->fmt = AUD_FMT_U8; + s->use_hdma = 0; + s->fmt_bits = 8; + s->fmt_signed = 0; + s->fmt_stereo = (s->mixer_regs[0x0e] & 2) != 0; + if (-1 == s->time_const) { + if (s->freq <= 0) + s->freq = 11025; + } + else { + int tmp = (256 - s->time_const); + s->freq = (1000000 + (tmp / 2)) / tmp; + } + + if (dma_len != -1) { + s->block_size = dma_len << s->fmt_stereo; + } + else { + /* This is apparently the only way to make both Act1/PL + and SecondReality/FC work + + Act1 sets block size via command 0x48 and it's an odd number + SR does the same with even number + Both use stereo, and Creatives own documentation states that + 0x48 sets block size in bytes less one.. go figure */ + s->block_size &= ~s->fmt_stereo; + } + + s->freq >>= s->fmt_stereo; + s->left_till_irq = s->block_size; + s->bytes_per_second = (s->freq << s->fmt_stereo); + /* s->highspeed = (mask & DMA8_HIGH) != 0; */ + s->dma_auto = (mask & DMA8_AUTO) != 0; + s->align = (1 << s->fmt_stereo) - 1; + + if (s->block_size & s->align) { + dolog ("warning: misaligned block size %d, alignment %d\n", + s->block_size, s->align + 1); + } + + ldebug ("freq %d, stereo %d, sign %d, bits %d, " + "dma %d, auto %d, fifo %d, high %d\n", + s->freq, s->fmt_stereo, s->fmt_signed, s->fmt_bits, + s->block_size, s->dma_auto, s->fifo, s->highspeed); + + continue_dma8 (s); + speaker (s, 1); +} + +static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len) +{ + s->use_hdma = cmd < 0xc0; + s->fifo = (cmd >> 1) & 1; + s->dma_auto = (cmd >> 2) & 1; + s->fmt_signed = (d0 >> 4) & 1; + s->fmt_stereo = (d0 >> 5) & 1; + + switch (cmd >> 4) { + case 11: + s->fmt_bits = 16; + break; + + case 12: + s->fmt_bits = 8; + break; + } + + if (-1 != s->time_const) { +#if 1 + int tmp = 256 - s->time_const; + s->freq = (1000000 + (tmp / 2)) / tmp; +#else + /* s->freq = 1000000 / ((255 - s->time_const) << s->fmt_stereo); */ + s->freq = 1000000 / ((255 - s->time_const)); +#endif + s->time_const = -1; + } + + s->block_size = dma_len + 1; + s->block_size <<= (s->fmt_bits == 16); + if (!s->dma_auto) { + /* It is clear that for DOOM and auto-init this value + shouldn't take stereo into account, while Miles Sound Systems + setsound.exe with single transfer mode wouldn't work without it + wonders of SB16 yet again */ + s->block_size <<= s->fmt_stereo; + } + + ldebug ("freq %d, stereo %d, sign %d, bits %d, " + "dma %d, auto %d, fifo %d, high %d\n", + s->freq, s->fmt_stereo, s->fmt_signed, s->fmt_bits, + s->block_size, s->dma_auto, s->fifo, s->highspeed); + + if (16 == s->fmt_bits) { + if (s->fmt_signed) { + s->fmt = AUD_FMT_S16; + } + else { + s->fmt = AUD_FMT_U16; + } + } + else { + if (s->fmt_signed) { + s->fmt = AUD_FMT_S8; + } + else { + s->fmt = AUD_FMT_U8; + } + } + + s->left_till_irq = s->block_size; + + s->bytes_per_second = (s->freq << s->fmt_stereo) << (s->fmt_bits == 16); + s->highspeed = 0; + s->align = (1 << (s->fmt_stereo + (s->fmt_bits == 16))) - 1; + if (s->block_size & s->align) { + dolog ("warning: misaligned block size %d, alignment %d\n", + s->block_size, s->align + 1); + } + + if (s->freq) { + struct audsettings as; + + s->audio_free = 0; + + as.freq = s->freq; + as.nchannels = 1 << s->fmt_stereo; + as.fmt = s->fmt; + as.endianness = 0; + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "sb16", + s, + SB_audio_callback, + &as + ); + } + + control (s, 1); + speaker (s, 1); +} + +static inline void dsp_out_data (SB16State *s, uint8_t val) +{ + ldebug ("outdata %#x\n", val); + if ((size_t) s->out_data_len < sizeof (s->out_data)) { + s->out_data[s->out_data_len++] = val; + } +} + +static inline uint8_t dsp_get_data (SB16State *s) +{ + if (s->in_index) { + return s->in2_data[--s->in_index]; + } + else { + dolog ("buffer underflow\n"); + return 0; + } +} + +static void command (SB16State *s, uint8_t cmd) +{ + ldebug ("command %#x\n", cmd); + + if (cmd > 0xaf && cmd < 0xd0) { + if (cmd & 8) { + dolog ("ADC not yet supported (command %#x)\n", cmd); + } + + switch (cmd >> 4) { + case 11: + case 12: + break; + default: + dolog ("%#x wrong bits\n", cmd); + } + s->needed_bytes = 3; + } + else { + s->needed_bytes = 0; + + switch (cmd) { + case 0x03: + dsp_out_data (s, 0x10); /* s->csp_param); */ + goto warn; + + case 0x04: + s->needed_bytes = 1; + goto warn; + + case 0x05: + s->needed_bytes = 2; + goto warn; + + case 0x08: + /* __asm__ ("int3"); */ + goto warn; + + case 0x0e: + s->needed_bytes = 2; + goto warn; + + case 0x09: + dsp_out_data (s, 0xf8); + goto warn; + + case 0x0f: + s->needed_bytes = 1; + goto warn; + + case 0x10: + s->needed_bytes = 1; + goto warn; + + case 0x14: + s->needed_bytes = 2; + s->block_size = 0; + break; + + case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */ + dma_cmd8 (s, DMA8_AUTO, -1); + break; + + case 0x20: /* Direct ADC, Juice/PL */ + dsp_out_data (s, 0xff); + goto warn; + + case 0x35: + dolog ("0x35 - MIDI command not implemented\n"); + break; + + case 0x40: + s->freq = -1; + s->time_const = -1; + s->needed_bytes = 1; + break; + + case 0x41: + s->freq = -1; + s->time_const = -1; + s->needed_bytes = 2; + break; + + case 0x42: + s->freq = -1; + s->time_const = -1; + s->needed_bytes = 2; + goto warn; + + case 0x45: + dsp_out_data (s, 0xaa); + goto warn; + + case 0x47: /* Continue Auto-Initialize DMA 16bit */ + break; + + case 0x48: + s->needed_bytes = 2; + break; + + case 0x74: + s->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */ + dolog ("0x75 - DMA DAC, 4-bit ADPCM not implemented\n"); + break; + + case 0x75: /* DMA DAC, 4-bit ADPCM Reference */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n"); + break; + + case 0x76: /* DMA DAC, 2.6-bit ADPCM */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n"); + break; + + case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n"); + break; + + case 0x7d: + dolog ("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n"); + dolog ("not implemented\n"); + break; + + case 0x7f: + dolog ( + "0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n" + ); + dolog ("not implemented\n"); + break; + + case 0x80: + s->needed_bytes = 2; + break; + + case 0x90: + case 0x91: + dma_cmd8 (s, ((cmd & 1) == 0) | DMA8_HIGH, -1); + break; + + case 0xd0: /* halt DMA operation. 8bit */ + control (s, 0); + break; + + case 0xd1: /* speaker on */ + speaker (s, 1); + break; + + case 0xd3: /* speaker off */ + speaker (s, 0); + break; + + case 0xd4: /* continue DMA operation. 8bit */ + /* KQ6 (or maybe Sierras audblst.drv in general) resets + the frequency between halt/continue */ + continue_dma8 (s); + break; + + case 0xd5: /* halt DMA operation. 16bit */ + control (s, 0); + break; + + case 0xd6: /* continue DMA operation. 16bit */ + control (s, 1); + break; + + case 0xd9: /* exit auto-init DMA after this block. 16bit */ + s->dma_auto = 0; + break; + + case 0xda: /* exit auto-init DMA after this block. 8bit */ + s->dma_auto = 0; + break; + + case 0xe0: /* DSP identification */ + s->needed_bytes = 1; + break; + + case 0xe1: + dsp_out_data (s, s->ver & 0xff); + dsp_out_data (s, s->ver >> 8); + break; + + case 0xe2: + s->needed_bytes = 1; + goto warn; + + case 0xe3: + { + int i; + for (i = sizeof (e3) - 1; i >= 0; --i) + dsp_out_data (s, e3[i]); + } + break; + + case 0xe4: /* write test reg */ + s->needed_bytes = 1; + break; + + case 0xe7: + dolog ("Attempt to probe for ESS (0xe7)?\n"); + break; + + case 0xe8: /* read test reg */ + dsp_out_data (s, s->test_reg); + break; + + case 0xf2: + case 0xf3: + dsp_out_data (s, 0xaa); + s->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2; + qemu_irq_raise (s->pic); + break; + + case 0xf9: + s->needed_bytes = 1; + goto warn; + + case 0xfa: + dsp_out_data (s, 0); + goto warn; + + case 0xfc: /* FIXME */ + dsp_out_data (s, 0); + goto warn; + + default: + dolog ("Unrecognized command %#x\n", cmd); + break; + } + } + + if (!s->needed_bytes) { + ldebug ("\n"); + } + + exit: + if (!s->needed_bytes) { + s->cmd = -1; + } + else { + s->cmd = cmd; + } + return; + + warn: + dolog ("warning: command %#x,%d is not truly understood yet\n", + cmd, s->needed_bytes); + goto exit; + +} + +static uint16_t dsp_get_lohi (SB16State *s) +{ + uint8_t hi = dsp_get_data (s); + uint8_t lo = dsp_get_data (s); + return (hi << 8) | lo; +} + +static uint16_t dsp_get_hilo (SB16State *s) +{ + uint8_t lo = dsp_get_data (s); + uint8_t hi = dsp_get_data (s); + return (hi << 8) | lo; +} + +static void complete (SB16State *s) +{ + int d0, d1, d2; + ldebug ("complete command %#x, in_index %d, needed_bytes %d\n", + s->cmd, s->in_index, s->needed_bytes); + + if (s->cmd > 0xaf && s->cmd < 0xd0) { + d2 = dsp_get_data (s); + d1 = dsp_get_data (s); + d0 = dsp_get_data (s); + + if (s->cmd & 8) { + dolog ("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", + s->cmd, d0, d1, d2); + } + else { + ldebug ("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", + s->cmd, d0, d1, d2); + dma_cmd (s, s->cmd, d0, d1 + (d2 << 8)); + } + } + else { + switch (s->cmd) { + case 0x04: + s->csp_mode = dsp_get_data (s); + s->csp_reg83r = 0; + s->csp_reg83w = 0; + ldebug ("CSP command 0x04: mode=%#x\n", s->csp_mode); + break; + + case 0x05: + s->csp_param = dsp_get_data (s); + s->csp_value = dsp_get_data (s); + ldebug ("CSP command 0x05: param=%#x value=%#x\n", + s->csp_param, + s->csp_value); + break; + + case 0x0e: + d0 = dsp_get_data (s); + d1 = dsp_get_data (s); + ldebug ("write CSP register %d <- %#x\n", d1, d0); + if (d1 == 0x83) { + ldebug ("0x83[%d] <- %#x\n", s->csp_reg83r, d0); + s->csp_reg83[s->csp_reg83r % 4] = d0; + s->csp_reg83r += 1; + } + else { + s->csp_regs[d1] = d0; + } + break; + + case 0x0f: + d0 = dsp_get_data (s); + ldebug ("read CSP register %#x -> %#x, mode=%#x\n", + d0, s->csp_regs[d0], s->csp_mode); + if (d0 == 0x83) { + ldebug ("0x83[%d] -> %#x\n", + s->csp_reg83w, + s->csp_reg83[s->csp_reg83w % 4]); + dsp_out_data (s, s->csp_reg83[s->csp_reg83w % 4]); + s->csp_reg83w += 1; + } + else { + dsp_out_data (s, s->csp_regs[d0]); + } + break; + + case 0x10: + d0 = dsp_get_data (s); + dolog ("cmd 0x10 d0=%#x\n", d0); + break; + + case 0x14: + dma_cmd8 (s, 0, dsp_get_lohi (s) + 1); + break; + + case 0x40: + s->time_const = dsp_get_data (s); + ldebug ("set time const %d\n", s->time_const); + break; + + case 0x42: /* FT2 sets output freq with this, go figure */ +#if 0 + dolog ("cmd 0x42 might not do what it think it should\n"); +#endif + case 0x41: + s->freq = dsp_get_hilo (s); + ldebug ("set freq %d\n", s->freq); + break; + + case 0x48: + s->block_size = dsp_get_lohi (s) + 1; + ldebug ("set dma block len %d\n", s->block_size); + break; + + case 0x74: + case 0x75: + case 0x76: + case 0x77: + /* ADPCM stuff, ignore */ + break; + + case 0x80: + { + int freq, samples, bytes; + int64_t ticks; + + freq = s->freq > 0 ? s->freq : 11025; + samples = dsp_get_lohi (s) + 1; + bytes = samples << s->fmt_stereo << (s->fmt_bits == 16); + ticks = muldiv64 (bytes, get_ticks_per_sec (), freq); + if (ticks < get_ticks_per_sec () / 1024) { + qemu_irq_raise (s->pic); + } + else { + if (s->aux_ts) { + timer_mod ( + s->aux_ts, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ticks + ); + } + } + ldebug ("mix silence %d %d %" PRId64 "\n", samples, bytes, ticks); + } + break; + + case 0xe0: + d0 = dsp_get_data (s); + s->out_data_len = 0; + ldebug ("E0 data = %#x\n", d0); + dsp_out_data (s, ~d0); + break; + + case 0xe2: +#ifdef DEBUG + d0 = dsp_get_data (s); + dolog ("E2 = %#x\n", d0); +#endif + break; + + case 0xe4: + s->test_reg = dsp_get_data (s); + break; + + case 0xf9: + d0 = dsp_get_data (s); + ldebug ("command 0xf9 with %#x\n", d0); + switch (d0) { + case 0x0e: + dsp_out_data (s, 0xff); + break; + + case 0x0f: + dsp_out_data (s, 0x07); + break; + + case 0x37: + dsp_out_data (s, 0x38); + break; + + default: + dsp_out_data (s, 0x00); + break; + } + break; + + default: + dolog ("complete: unrecognized command %#x\n", s->cmd); + return; + } + } + + ldebug ("\n"); + s->cmd = -1; +} + +static void legacy_reset (SB16State *s) +{ + struct audsettings as; + + s->freq = 11025; + s->fmt_signed = 0; + s->fmt_bits = 8; + s->fmt_stereo = 0; + + as.freq = s->freq; + as.nchannels = 1; + as.fmt = AUD_FMT_U8; + as.endianness = 0; + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "sb16", + s, + SB_audio_callback, + &as + ); + + /* Not sure about that... */ + /* AUD_set_active_out (s->voice, 1); */ +} + +static void reset (SB16State *s) +{ + qemu_irq_lower (s->pic); + if (s->dma_auto) { + qemu_irq_raise (s->pic); + qemu_irq_lower (s->pic); + } + + s->mixer_regs[0x82] = 0; + s->dma_auto = 0; + s->in_index = 0; + s->out_data_len = 0; + s->left_till_irq = 0; + s->needed_bytes = 0; + s->block_size = -1; + s->nzero = 0; + s->highspeed = 0; + s->v2x6 = 0; + s->cmd = -1; + + dsp_out_data (s, 0xaa); + speaker (s, 0); + control (s, 0); + legacy_reset (s); +} + +static IO_WRITE_PROTO (dsp_write) +{ + SB16State *s = opaque; + int iport; + + iport = nport - s->port; + + ldebug ("write %#x <- %#x\n", nport, val); + switch (iport) { + case 0x06: + switch (val) { + case 0x00: + if (s->v2x6 == 1) { + reset (s); + } + s->v2x6 = 0; + break; + + case 0x01: + case 0x03: /* FreeBSD kludge */ + s->v2x6 = 1; + break; + + case 0xc6: + s->v2x6 = 0; /* Prince of Persia, csp.sys, diagnose.exe */ + break; + + case 0xb8: /* Panic */ + reset (s); + break; + + case 0x39: + dsp_out_data (s, 0x38); + reset (s); + s->v2x6 = 0x39; + break; + + default: + s->v2x6 = val; + break; + } + break; + + case 0x0c: /* write data or command | write status */ +/* if (s->highspeed) */ +/* break; */ + + if (s->needed_bytes == 0) { + command (s, val); +#if 0 + if (0 == s->needed_bytes) { + log_dsp (s); + } +#endif + } + else { + if (s->in_index == sizeof (s->in2_data)) { + dolog ("in data overrun\n"); + } + else { + s->in2_data[s->in_index++] = val; + if (s->in_index == s->needed_bytes) { + s->needed_bytes = 0; + complete (s); +#if 0 + log_dsp (s); +#endif + } + } + } + break; + + default: + ldebug ("(nport=%#x, val=%#x)\n", nport, val); + break; + } +} + +static IO_READ_PROTO (dsp_read) +{ + SB16State *s = opaque; + int iport, retval, ack = 0; + + iport = nport - s->port; + + switch (iport) { + case 0x06: /* reset */ + retval = 0xff; + break; + + case 0x0a: /* read data */ + if (s->out_data_len) { + retval = s->out_data[--s->out_data_len]; + s->last_read_byte = retval; + } + else { + if (s->cmd != -1) { + dolog ("empty output buffer for command %#x\n", + s->cmd); + } + retval = s->last_read_byte; + /* goto error; */ + } + break; + + case 0x0c: /* 0 can write */ + retval = s->can_write ? 0 : 0x80; + break; + + case 0x0d: /* timer interrupt clear */ + /* dolog ("timer interrupt clear\n"); */ + retval = 0; + break; + + case 0x0e: /* data available status | irq 8 ack */ + retval = (!s->out_data_len || s->highspeed) ? 0 : 0x80; + if (s->mixer_regs[0x82] & 1) { + ack = 1; + s->mixer_regs[0x82] &= ~1; + qemu_irq_lower (s->pic); + } + break; + + case 0x0f: /* irq 16 ack */ + retval = 0xff; + if (s->mixer_regs[0x82] & 2) { + ack = 1; + s->mixer_regs[0x82] &= ~2; + qemu_irq_lower (s->pic); + } + break; + + default: + goto error; + } + + if (!ack) { + ldebug ("read %#x -> %#x\n", nport, retval); + } + + return retval; + + error: + dolog ("warning: dsp_read %#x error\n", nport); + return 0xff; +} + +static void reset_mixer (SB16State *s) +{ + int i; + + memset (s->mixer_regs, 0xff, 0x7f); + memset (s->mixer_regs + 0x83, 0xff, sizeof (s->mixer_regs) - 0x83); + + s->mixer_regs[0x02] = 4; /* master volume 3bits */ + s->mixer_regs[0x06] = 4; /* MIDI volume 3bits */ + s->mixer_regs[0x08] = 0; /* CD volume 3bits */ + s->mixer_regs[0x0a] = 0; /* voice volume 2bits */ + + /* d5=input filt, d3=lowpass filt, d1,d2=input source */ + s->mixer_regs[0x0c] = 0; + + /* d5=output filt, d1=stereo switch */ + s->mixer_regs[0x0e] = 0; + + /* voice volume L d5,d7, R d1,d3 */ + s->mixer_regs[0x04] = (4 << 5) | (4 << 1); + /* master ... */ + s->mixer_regs[0x22] = (4 << 5) | (4 << 1); + /* MIDI ... */ + s->mixer_regs[0x26] = (4 << 5) | (4 << 1); + + for (i = 0x30; i < 0x48; i++) { + s->mixer_regs[i] = 0x20; + } +} + +static IO_WRITE_PROTO (mixer_write_indexb) +{ + SB16State *s = opaque; + (void) nport; + s->mixer_nreg = val; +} + +static IO_WRITE_PROTO (mixer_write_datab) +{ + SB16State *s = opaque; + + (void) nport; + ldebug ("mixer_write [%#x] <- %#x\n", s->mixer_nreg, val); + + switch (s->mixer_nreg) { + case 0x00: + reset_mixer (s); + break; + + case 0x80: + { + int irq = irq_of_magic (val); + ldebug ("setting irq to %d (val=%#x)\n", irq, val); + if (irq > 0) { + s->irq = irq; + } + } + break; + + case 0x81: + { + int dma, hdma; + + dma = ctz32 (val & 0xf); + hdma = ctz32 (val & 0xf0); + if (dma != s->dma || hdma != s->hdma) { + dolog ( + "attempt to change DMA " + "8bit %d(%d), 16bit %d(%d) (val=%#x)\n", + dma, s->dma, hdma, s->hdma, val); + } +#if 0 + s->dma = dma; + s->hdma = hdma; +#endif + } + break; + + case 0x82: + dolog ("attempt to write into IRQ status register (val=%#x)\n", + val); + return; + + default: + if (s->mixer_nreg >= 0x80) { + ldebug ("attempt to write mixer[%#x] <- %#x\n", s->mixer_nreg, val); + } + break; + } + + s->mixer_regs[s->mixer_nreg] = val; +} + +static IO_READ_PROTO (mixer_read) +{ + SB16State *s = opaque; + + (void) nport; +#ifndef DEBUG_SB16_MOST + if (s->mixer_nreg != 0x82) { + ldebug ("mixer_read[%#x] -> %#x\n", + s->mixer_nreg, s->mixer_regs[s->mixer_nreg]); + } +#else + ldebug ("mixer_read[%#x] -> %#x\n", + s->mixer_nreg, s->mixer_regs[s->mixer_nreg]); +#endif + return s->mixer_regs[s->mixer_nreg]; +} + +static int write_audio (SB16State *s, int nchan, int dma_pos, + int dma_len, int len) +{ + int temp, net; + uint8_t tmpbuf[4096]; + + temp = len; + net = 0; + + while (temp) { + int left = dma_len - dma_pos; + int copied; + size_t to_copy; + + to_copy = audio_MIN (temp, left); + if (to_copy > sizeof (tmpbuf)) { + to_copy = sizeof (tmpbuf); + } + + copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy); + copied = AUD_write (s->voice, tmpbuf, copied); + + temp -= copied; + dma_pos = (dma_pos + copied) % dma_len; + net += copied; + + if (!copied) { + break; + } + } + + return net; +} + +static int SB_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len) +{ + SB16State *s = opaque; + int till, copy, written, free; + + if (s->block_size <= 0) { + dolog ("invalid block size=%d nchan=%d dma_pos=%d dma_len=%d\n", + s->block_size, nchan, dma_pos, dma_len); + return dma_pos; + } + + if (s->left_till_irq < 0) { + s->left_till_irq = s->block_size; + } + + if (s->voice) { + free = s->audio_free & ~s->align; + if ((free <= 0) || !dma_len) { + return dma_pos; + } + } + else { + free = dma_len; + } + + copy = free; + till = s->left_till_irq; + +#ifdef DEBUG_SB16_MOST + dolog ("pos:%06d %d till:%d len:%d\n", + dma_pos, free, till, dma_len); +#endif + + if (till <= copy) { + if (s->dma_auto == 0) { + copy = till; + } + } + + written = write_audio (s, nchan, dma_pos, dma_len, copy); + dma_pos = (dma_pos + written) % dma_len; + s->left_till_irq -= written; + + if (s->left_till_irq <= 0) { + s->mixer_regs[0x82] |= (nchan & 4) ? 2 : 1; + qemu_irq_raise (s->pic); + if (s->dma_auto == 0) { + control (s, 0); + speaker (s, 0); + } + } + +#ifdef DEBUG_SB16_MOST + ldebug ("pos %5d free %5d size %5d till % 5d copy %5d written %5d size %5d\n", + dma_pos, free, dma_len, s->left_till_irq, copy, written, + s->block_size); +#endif + + while (s->left_till_irq <= 0) { + s->left_till_irq = s->block_size + s->left_till_irq; + } + + return dma_pos; +} + +static void SB_audio_callback (void *opaque, int free) +{ + SB16State *s = opaque; + s->audio_free = free; +} + +static int sb16_post_load (void *opaque, int version_id) +{ + SB16State *s = opaque; + + if (s->voice) { + AUD_close_out (&s->card, s->voice); + s->voice = NULL; + } + + if (s->dma_running) { + if (s->freq) { + struct audsettings as; + + s->audio_free = 0; + + as.freq = s->freq; + as.nchannels = 1 << s->fmt_stereo; + as.fmt = s->fmt; + as.endianness = 0; + + s->voice = AUD_open_out ( + &s->card, + s->voice, + "sb16", + s, + SB_audio_callback, + &as + ); + } + + control (s, 1); + speaker (s, s->speaker); + } + return 0; +} + +static const VMStateDescription vmstate_sb16 = { + .name = "sb16", + .version_id = 1, + .minimum_version_id = 1, + .post_load = sb16_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32 (irq, SB16State), + VMSTATE_UINT32 (dma, SB16State), + VMSTATE_UINT32 (hdma, SB16State), + VMSTATE_UINT32 (port, SB16State), + VMSTATE_UINT32 (ver, SB16State), + VMSTATE_INT32 (in_index, SB16State), + VMSTATE_INT32 (out_data_len, SB16State), + VMSTATE_INT32 (fmt_stereo, SB16State), + VMSTATE_INT32 (fmt_signed, SB16State), + VMSTATE_INT32 (fmt_bits, SB16State), + VMSTATE_UINT32 (fmt, SB16State), + VMSTATE_INT32 (dma_auto, SB16State), + VMSTATE_INT32 (block_size, SB16State), + VMSTATE_INT32 (fifo, SB16State), + VMSTATE_INT32 (freq, SB16State), + VMSTATE_INT32 (time_const, SB16State), + VMSTATE_INT32 (speaker, SB16State), + VMSTATE_INT32 (needed_bytes, SB16State), + VMSTATE_INT32 (cmd, SB16State), + VMSTATE_INT32 (use_hdma, SB16State), + VMSTATE_INT32 (highspeed, SB16State), + VMSTATE_INT32 (can_write, SB16State), + VMSTATE_INT32 (v2x6, SB16State), + + VMSTATE_UINT8 (csp_param, SB16State), + VMSTATE_UINT8 (csp_value, SB16State), + VMSTATE_UINT8 (csp_mode, SB16State), + VMSTATE_UINT8 (csp_param, SB16State), + VMSTATE_BUFFER (csp_regs, SB16State), + VMSTATE_UINT8 (csp_index, SB16State), + VMSTATE_BUFFER (csp_reg83, SB16State), + VMSTATE_INT32 (csp_reg83r, SB16State), + VMSTATE_INT32 (csp_reg83w, SB16State), + + VMSTATE_BUFFER (in2_data, SB16State), + VMSTATE_BUFFER (out_data, SB16State), + VMSTATE_UINT8 (test_reg, SB16State), + VMSTATE_UINT8 (last_read_byte, SB16State), + + VMSTATE_INT32 (nzero, SB16State), + VMSTATE_INT32 (left_till_irq, SB16State), + VMSTATE_INT32 (dma_running, SB16State), + VMSTATE_INT32 (bytes_per_second, SB16State), + VMSTATE_INT32 (align, SB16State), + + VMSTATE_INT32 (mixer_nreg, SB16State), + VMSTATE_BUFFER (mixer_regs, SB16State), + + VMSTATE_END_OF_LIST () + } +}; + +static const MemoryRegionPortio sb16_ioport_list[] = { + { 4, 1, 1, .write = mixer_write_indexb }, + { 5, 1, 1, .read = mixer_read, .write = mixer_write_datab }, + { 6, 1, 1, .read = dsp_read, .write = dsp_write }, + { 10, 1, 1, .read = dsp_read }, + { 12, 1, 1, .write = dsp_write }, + { 12, 4, 1, .read = dsp_read }, + PORTIO_END_OF_LIST (), +}; + + +static void sb16_initfn (Object *obj) +{ + SB16State *s = SB16 (obj); + + s->cmd = -1; +} + +static void sb16_realizefn (DeviceState *dev, Error **errp) +{ + ISADevice *isadev = ISA_DEVICE (dev); + SB16State *s = SB16 (dev); + + isa_init_irq (isadev, &s->pic, s->irq); + + s->mixer_regs[0x80] = magic_of_irq (s->irq); + s->mixer_regs[0x81] = (1 << s->dma) | (1 << s->hdma); + s->mixer_regs[0x82] = 2 << 5; + + s->csp_regs[5] = 1; + s->csp_regs[9] = 0xf8; + + reset_mixer (s); + s->aux_ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, aux_timer, s); + if (!s->aux_ts) { + dolog ("warning: Could not create auxiliary timer\n"); + } + + isa_register_portio_list (isadev, s->port, sb16_ioport_list, s, "sb16"); + + DMA_register_channel (s->hdma, SB_read_DMA, s); + DMA_register_channel (s->dma, SB_read_DMA, s); + s->can_write = 1; + + AUD_register_card ("sb16", &s->card); +} + +static int SB16_init (ISABus *bus) +{ + isa_create_simple (bus, TYPE_SB16); + return 0; +} + +static Property sb16_properties[] = { + DEFINE_PROP_UINT32 ("version", SB16State, ver, 0x0405), /* 4.5 */ + DEFINE_PROP_UINT32 ("iobase", SB16State, port, 0x220), + DEFINE_PROP_UINT32 ("irq", SB16State, irq, 5), + DEFINE_PROP_UINT32 ("dma", SB16State, dma, 1), + DEFINE_PROP_UINT32 ("dma16", SB16State, hdma, 5), + DEFINE_PROP_END_OF_LIST (), +}; + +static void sb16_class_initfn (ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS (klass); + + dc->realize = sb16_realizefn; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Creative Sound Blaster 16"; + dc->vmsd = &vmstate_sb16; + dc->props = sb16_properties; +} + +static const TypeInfo sb16_info = { + .name = TYPE_SB16, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof (SB16State), + .instance_init = sb16_initfn, + .class_init = sb16_class_initfn, +}; + +static void sb16_register_types (void) +{ + type_register_static (&sb16_info); + isa_register_soundhw("sb16", "Creative Sound Blaster 16", SB16_init); +} + +type_init (sb16_register_types) diff --git a/qemu/hw/audio/wm8750.c b/qemu/hw/audio/wm8750.c new file mode 100644 index 000000000..b50b33140 --- /dev/null +++ b/qemu/hw/audio/wm8750.c @@ -0,0 +1,722 @@ +/* + * WM8750 audio CODEC. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This file is licensed under GNU GPL. + */ + +#include "hw/hw.h" +#include "hw/i2c/i2c.h" +#include "audio/audio.h" + +#define IN_PORT_N 3 +#define OUT_PORT_N 3 + +#define CODEC "wm8750" + +typedef struct { + int adc; + int adc_hz; + int dac; + int dac_hz; +} WMRate; + +#define TYPE_WM8750 "wm8750" +#define WM8750(obj) OBJECT_CHECK(WM8750State, (obj), TYPE_WM8750) + +typedef struct WM8750State { + I2CSlave parent_obj; + + uint8_t i2c_data[2]; + int i2c_len; + QEMUSoundCard card; + SWVoiceIn *adc_voice[IN_PORT_N]; + SWVoiceOut *dac_voice[OUT_PORT_N]; + int enable; + void (*data_req)(void *, int, int); + void *opaque; + uint8_t data_in[4096]; + uint8_t data_out[4096]; + int idx_in, req_in; + int idx_out, req_out; + + SWVoiceOut **out[2]; + uint8_t outvol[7], outmute[2]; + SWVoiceIn **in[2]; + uint8_t invol[4], inmute[2]; + + uint8_t diff[2], pol, ds, monomix[2], alc, mute; + uint8_t path[4], mpath[2], power, format; + const WMRate *rate; + uint8_t rate_vmstate; + int adc_hz, dac_hz, ext_adc_hz, ext_dac_hz, master; +} WM8750State; + +/* pow(10.0, -i / 20.0) * 255, i = 0..42 */ +static const uint8_t wm8750_vol_db_table[] = { + 255, 227, 203, 181, 161, 143, 128, 114, 102, 90, 81, 72, 64, 57, 51, 45, + 40, 36, 32, 29, 26, 23, 20, 18, 16, 14, 13, 11, 10, 9, 8, 7, 6, 6, 5, 5, + 4, 4, 3, 3, 3, 2, 2 +}; + +#define WM8750_OUTVOL_TRANSFORM(x) wm8750_vol_db_table[(0x7f - x) / 3] +#define WM8750_INVOL_TRANSFORM(x) (x << 2) + +static inline void wm8750_in_load(WM8750State *s) +{ + if (s->idx_in + s->req_in <= sizeof(s->data_in)) + return; + s->idx_in = audio_MAX(0, (int) sizeof(s->data_in) - s->req_in); + AUD_read(*s->in[0], s->data_in + s->idx_in, + sizeof(s->data_in) - s->idx_in); +} + +static inline void wm8750_out_flush(WM8750State *s) +{ + int sent = 0; + while (sent < s->idx_out) + sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent) + ?: s->idx_out; + s->idx_out = 0; +} + +static void wm8750_audio_in_cb(void *opaque, int avail_b) +{ + WM8750State *s = (WM8750State *) opaque; + s->req_in = avail_b; + s->data_req(s->opaque, s->req_out >> 2, avail_b >> 2); +} + +static void wm8750_audio_out_cb(void *opaque, int free_b) +{ + WM8750State *s = (WM8750State *) opaque; + + if (s->idx_out >= free_b) { + s->idx_out = free_b; + s->req_out = 0; + wm8750_out_flush(s); + } else + s->req_out = free_b - s->idx_out; + + s->data_req(s->opaque, s->req_out >> 2, s->req_in >> 2); +} + +static const WMRate wm_rate_table[] = { + { 256, 48000, 256, 48000 }, /* SR: 00000 */ + { 384, 48000, 384, 48000 }, /* SR: 00001 */ + { 256, 48000, 1536, 8000 }, /* SR: 00010 */ + { 384, 48000, 2304, 8000 }, /* SR: 00011 */ + { 1536, 8000, 256, 48000 }, /* SR: 00100 */ + { 2304, 8000, 384, 48000 }, /* SR: 00101 */ + { 1536, 8000, 1536, 8000 }, /* SR: 00110 */ + { 2304, 8000, 2304, 8000 }, /* SR: 00111 */ + { 1024, 12000, 1024, 12000 }, /* SR: 01000 */ + { 1526, 12000, 1536, 12000 }, /* SR: 01001 */ + { 768, 16000, 768, 16000 }, /* SR: 01010 */ + { 1152, 16000, 1152, 16000 }, /* SR: 01011 */ + { 384, 32000, 384, 32000 }, /* SR: 01100 */ + { 576, 32000, 576, 32000 }, /* SR: 01101 */ + { 128, 96000, 128, 96000 }, /* SR: 01110 */ + { 192, 96000, 192, 96000 }, /* SR: 01111 */ + { 256, 44100, 256, 44100 }, /* SR: 10000 */ + { 384, 44100, 384, 44100 }, /* SR: 10001 */ + { 256, 44100, 1408, 8018 }, /* SR: 10010 */ + { 384, 44100, 2112, 8018 }, /* SR: 10011 */ + { 1408, 8018, 256, 44100 }, /* SR: 10100 */ + { 2112, 8018, 384, 44100 }, /* SR: 10101 */ + { 1408, 8018, 1408, 8018 }, /* SR: 10110 */ + { 2112, 8018, 2112, 8018 }, /* SR: 10111 */ + { 1024, 11025, 1024, 11025 }, /* SR: 11000 */ + { 1536, 11025, 1536, 11025 }, /* SR: 11001 */ + { 512, 22050, 512, 22050 }, /* SR: 11010 */ + { 768, 22050, 768, 22050 }, /* SR: 11011 */ + { 512, 24000, 512, 24000 }, /* SR: 11100 */ + { 768, 24000, 768, 24000 }, /* SR: 11101 */ + { 128, 88200, 128, 88200 }, /* SR: 11110 */ + { 192, 88200, 192, 88200 }, /* SR: 11111 */ +}; + +static void wm8750_vol_update(WM8750State *s) +{ + /* FIXME: multiply all volumes by s->invol[2], s->invol[3] */ + + AUD_set_volume_in(s->adc_voice[0], s->mute, + s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), + s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); + AUD_set_volume_in(s->adc_voice[1], s->mute, + s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), + s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); + AUD_set_volume_in(s->adc_voice[2], s->mute, + s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]), + s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1])); + + /* FIXME: multiply all volumes by s->outvol[0], s->outvol[1] */ + + /* Speaker: LOUT2VOL ROUT2VOL */ + AUD_set_volume_out(s->dac_voice[0], s->mute, + s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[4]), + s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[5])); + + /* Headphone: LOUT1VOL ROUT1VOL */ + AUD_set_volume_out(s->dac_voice[1], s->mute, + s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[2]), + s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[3])); + + /* MONOOUT: MONOVOL MONOVOL */ + AUD_set_volume_out(s->dac_voice[2], s->mute, + s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]), + s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6])); +} + +static void wm8750_set_format(WM8750State *s) +{ + int i; + struct audsettings in_fmt; + struct audsettings out_fmt; + + wm8750_out_flush(s); + + if (s->in[0] && *s->in[0]) + AUD_set_active_in(*s->in[0], 0); + if (s->out[0] && *s->out[0]) + AUD_set_active_out(*s->out[0], 0); + + for (i = 0; i < IN_PORT_N; i ++) + if (s->adc_voice[i]) { + AUD_close_in(&s->card, s->adc_voice[i]); + s->adc_voice[i] = NULL; + } + for (i = 0; i < OUT_PORT_N; i ++) + if (s->dac_voice[i]) { + AUD_close_out(&s->card, s->dac_voice[i]); + s->dac_voice[i] = NULL; + } + + if (!s->enable) + return; + + /* Setup input */ + in_fmt.endianness = 0; + in_fmt.nchannels = 2; + in_fmt.freq = s->adc_hz; + in_fmt.fmt = AUD_FMT_S16; + + s->adc_voice[0] = AUD_open_in(&s->card, s->adc_voice[0], + CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt); + s->adc_voice[1] = AUD_open_in(&s->card, s->adc_voice[1], + CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt); + s->adc_voice[2] = AUD_open_in(&s->card, s->adc_voice[2], + CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt); + + /* Setup output */ + out_fmt.endianness = 0; + out_fmt.nchannels = 2; + out_fmt.freq = s->dac_hz; + out_fmt.fmt = AUD_FMT_S16; + + s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0], + CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt); + s->dac_voice[1] = AUD_open_out(&s->card, s->dac_voice[1], + CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt); + /* MONOMIX is also in stereo for simplicity */ + s->dac_voice[2] = AUD_open_out(&s->card, s->dac_voice[2], + CODEC ".monomix", s, wm8750_audio_out_cb, &out_fmt); + /* no sense emulating OUT3 which is a mix of other outputs */ + + wm8750_vol_update(s); + + /* We should connect the left and right channels to their + * respective inputs/outputs but we have completely no need + * for mixing or combining paths to different ports, so we + * connect both channels to where the left channel is routed. */ + if (s->in[0] && *s->in[0]) + AUD_set_active_in(*s->in[0], 1); + if (s->out[0] && *s->out[0]) + AUD_set_active_out(*s->out[0], 1); +} + +static void wm8750_clk_update(WM8750State *s, int ext) +{ + if (s->master || !s->ext_dac_hz) + s->dac_hz = s->rate->dac_hz; + else + s->dac_hz = s->ext_dac_hz; + + if (s->master || !s->ext_adc_hz) + s->adc_hz = s->rate->adc_hz; + else + s->adc_hz = s->ext_adc_hz; + + if (s->master || (!s->ext_dac_hz && !s->ext_adc_hz)) { + if (!ext) + wm8750_set_format(s); + } else { + if (ext) + wm8750_set_format(s); + } +} + +static void wm8750_reset(I2CSlave *i2c) +{ + WM8750State *s = WM8750(i2c); + + s->rate = &wm_rate_table[0]; + s->enable = 0; + wm8750_clk_update(s, 1); + s->diff[0] = 0; + s->diff[1] = 0; + s->ds = 0; + s->alc = 0; + s->in[0] = &s->adc_voice[0]; + s->invol[0] = 0x17; + s->invol[1] = 0x17; + s->invol[2] = 0xc3; + s->invol[3] = 0xc3; + s->out[0] = &s->dac_voice[0]; + s->outvol[0] = 0xff; + s->outvol[1] = 0xff; + s->outvol[2] = 0x79; + s->outvol[3] = 0x79; + s->outvol[4] = 0x79; + s->outvol[5] = 0x79; + s->outvol[6] = 0x79; + s->inmute[0] = 0; + s->inmute[1] = 0; + s->outmute[0] = 0; + s->outmute[1] = 0; + s->mute = 1; + s->path[0] = 0; + s->path[1] = 0; + s->path[2] = 0; + s->path[3] = 0; + s->mpath[0] = 0; + s->mpath[1] = 0; + s->format = 0x0a; + s->idx_in = sizeof(s->data_in); + s->req_in = 0; + s->idx_out = 0; + s->req_out = 0; + wm8750_vol_update(s); + s->i2c_len = 0; +} + +static void wm8750_event(I2CSlave *i2c, enum i2c_event event) +{ + WM8750State *s = WM8750(i2c); + + switch (event) { + case I2C_START_SEND: + s->i2c_len = 0; + break; + case I2C_FINISH: +#ifdef VERBOSE + if (s->i2c_len < 2) + printf("%s: message too short (%i bytes)\n", + __FUNCTION__, s->i2c_len); +#endif + break; + default: + break; + } +} + +#define WM8750_LINVOL 0x00 +#define WM8750_RINVOL 0x01 +#define WM8750_LOUT1V 0x02 +#define WM8750_ROUT1V 0x03 +#define WM8750_ADCDAC 0x05 +#define WM8750_IFACE 0x07 +#define WM8750_SRATE 0x08 +#define WM8750_LDAC 0x0a +#define WM8750_RDAC 0x0b +#define WM8750_BASS 0x0c +#define WM8750_TREBLE 0x0d +#define WM8750_RESET 0x0f +#define WM8750_3D 0x10 +#define WM8750_ALC1 0x11 +#define WM8750_ALC2 0x12 +#define WM8750_ALC3 0x13 +#define WM8750_NGATE 0x14 +#define WM8750_LADC 0x15 +#define WM8750_RADC 0x16 +#define WM8750_ADCTL1 0x17 +#define WM8750_ADCTL2 0x18 +#define WM8750_PWR1 0x19 +#define WM8750_PWR2 0x1a +#define WM8750_ADCTL3 0x1b +#define WM8750_ADCIN 0x1f +#define WM8750_LADCIN 0x20 +#define WM8750_RADCIN 0x21 +#define WM8750_LOUTM1 0x22 +#define WM8750_LOUTM2 0x23 +#define WM8750_ROUTM1 0x24 +#define WM8750_ROUTM2 0x25 +#define WM8750_MOUTM1 0x26 +#define WM8750_MOUTM2 0x27 +#define WM8750_LOUT2V 0x28 +#define WM8750_ROUT2V 0x29 +#define WM8750_MOUTV 0x2a + +static int wm8750_tx(I2CSlave *i2c, uint8_t data) +{ + WM8750State *s = WM8750(i2c); + uint8_t cmd; + uint16_t value; + + if (s->i2c_len >= 2) { +#ifdef VERBOSE + printf("%s: long message (%i bytes)\n", __func__, s->i2c_len); +#endif + return 1; + } + s->i2c_data[s->i2c_len ++] = data; + if (s->i2c_len != 2) + return 0; + + cmd = s->i2c_data[0] >> 1; + value = ((s->i2c_data[0] << 8) | s->i2c_data[1]) & 0x1ff; + + switch (cmd) { + case WM8750_LADCIN: /* ADC Signal Path Control (Left) */ + s->diff[0] = (((value >> 6) & 3) == 3); /* LINSEL */ + if (s->diff[0]) + s->in[0] = &s->adc_voice[0 + s->ds * 1]; + else + s->in[0] = &s->adc_voice[((value >> 6) & 3) * 1 + 0]; + break; + + case WM8750_RADCIN: /* ADC Signal Path Control (Right) */ + s->diff[1] = (((value >> 6) & 3) == 3); /* RINSEL */ + if (s->diff[1]) + s->in[1] = &s->adc_voice[0 + s->ds * 1]; + else + s->in[1] = &s->adc_voice[((value >> 6) & 3) * 1 + 0]; + break; + + case WM8750_ADCIN: /* ADC Input Mode */ + s->ds = (value >> 8) & 1; /* DS */ + if (s->diff[0]) + s->in[0] = &s->adc_voice[0 + s->ds * 1]; + if (s->diff[1]) + s->in[1] = &s->adc_voice[0 + s->ds * 1]; + s->monomix[0] = (value >> 6) & 3; /* MONOMIX */ + break; + + case WM8750_ADCTL1: /* Additional Control (1) */ + s->monomix[1] = (value >> 1) & 1; /* DMONOMIX */ + break; + + case WM8750_PWR1: /* Power Management (1) */ + s->enable = ((value >> 6) & 7) == 3; /* VMIDSEL, VREF */ + wm8750_set_format(s); + break; + + case WM8750_LINVOL: /* Left Channel PGA */ + s->invol[0] = value & 0x3f; /* LINVOL */ + s->inmute[0] = (value >> 7) & 1; /* LINMUTE */ + wm8750_vol_update(s); + break; + + case WM8750_RINVOL: /* Right Channel PGA */ + s->invol[1] = value & 0x3f; /* RINVOL */ + s->inmute[1] = (value >> 7) & 1; /* RINMUTE */ + wm8750_vol_update(s); + break; + + case WM8750_ADCDAC: /* ADC and DAC Control */ + s->pol = (value >> 5) & 3; /* ADCPOL */ + s->mute = (value >> 3) & 1; /* DACMU */ + wm8750_vol_update(s); + break; + + case WM8750_ADCTL3: /* Additional Control (3) */ + break; + + case WM8750_LADC: /* Left ADC Digital Volume */ + s->invol[2] = value & 0xff; /* LADCVOL */ + wm8750_vol_update(s); + break; + + case WM8750_RADC: /* Right ADC Digital Volume */ + s->invol[3] = value & 0xff; /* RADCVOL */ + wm8750_vol_update(s); + break; + + case WM8750_ALC1: /* ALC Control (1) */ + s->alc = (value >> 7) & 3; /* ALCSEL */ + break; + + case WM8750_NGATE: /* Noise Gate Control */ + case WM8750_3D: /* 3D enhance */ + break; + + case WM8750_LDAC: /* Left Channel Digital Volume */ + s->outvol[0] = value & 0xff; /* LDACVOL */ + wm8750_vol_update(s); + break; + + case WM8750_RDAC: /* Right Channel Digital Volume */ + s->outvol[1] = value & 0xff; /* RDACVOL */ + wm8750_vol_update(s); + break; + + case WM8750_BASS: /* Bass Control */ + break; + + case WM8750_LOUTM1: /* Left Mixer Control (1) */ + s->path[0] = (value >> 8) & 1; /* LD2LO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_LOUTM2: /* Left Mixer Control (2) */ + s->path[1] = (value >> 8) & 1; /* RD2LO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_ROUTM1: /* Right Mixer Control (1) */ + s->path[2] = (value >> 8) & 1; /* LD2RO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_ROUTM2: /* Right Mixer Control (2) */ + s->path[3] = (value >> 8) & 1; /* RD2RO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_MOUTM1: /* Mono Mixer Control (1) */ + s->mpath[0] = (value >> 8) & 1; /* LD2MO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_MOUTM2: /* Mono Mixer Control (2) */ + s->mpath[1] = (value >> 8) & 1; /* RD2MO */ + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_LOUT1V: /* LOUT1 Volume */ + s->outvol[2] = value & 0x7f; /* LOUT1VOL */ + wm8750_vol_update(s); + break; + + case WM8750_LOUT2V: /* LOUT2 Volume */ + s->outvol[4] = value & 0x7f; /* LOUT2VOL */ + wm8750_vol_update(s); + break; + + case WM8750_ROUT1V: /* ROUT1 Volume */ + s->outvol[3] = value & 0x7f; /* ROUT1VOL */ + wm8750_vol_update(s); + break; + + case WM8750_ROUT2V: /* ROUT2 Volume */ + s->outvol[5] = value & 0x7f; /* ROUT2VOL */ + wm8750_vol_update(s); + break; + + case WM8750_MOUTV: /* MONOOUT Volume */ + s->outvol[6] = value & 0x7f; /* MONOOUTVOL */ + wm8750_vol_update(s); + break; + + case WM8750_ADCTL2: /* Additional Control (2) */ + break; + + case WM8750_PWR2: /* Power Management (2) */ + s->power = value & 0x7e; + /* TODO: mute/unmute respective paths */ + wm8750_vol_update(s); + break; + + case WM8750_IFACE: /* Digital Audio Interface Format */ + s->format = value; + s->master = (value >> 6) & 1; /* MS */ + wm8750_clk_update(s, s->master); + break; + + case WM8750_SRATE: /* Clocking and Sample Rate Control */ + s->rate = &wm_rate_table[(value >> 1) & 0x1f]; + wm8750_clk_update(s, 0); + break; + + case WM8750_RESET: /* Reset */ + wm8750_reset(I2C_SLAVE(s)); + break; + +#ifdef VERBOSE + default: + printf("%s: unknown register %02x\n", __FUNCTION__, cmd); +#endif + } + + return 0; +} + +static int wm8750_rx(I2CSlave *i2c) +{ + return 0x00; +} + +static void wm8750_pre_save(void *opaque) +{ + WM8750State *s = opaque; + + s->rate_vmstate = s->rate - wm_rate_table; +} + +static int wm8750_post_load(void *opaque, int version_id) +{ + WM8750State *s = opaque; + + s->rate = &wm_rate_table[s->rate_vmstate & 0x1f]; + return 0; +} + +static const VMStateDescription vmstate_wm8750 = { + .name = CODEC, + .version_id = 0, + .minimum_version_id = 0, + .pre_save = wm8750_pre_save, + .post_load = wm8750_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY(i2c_data, WM8750State, 2), + VMSTATE_INT32(i2c_len, WM8750State), + VMSTATE_INT32(enable, WM8750State), + VMSTATE_INT32(idx_in, WM8750State), + VMSTATE_INT32(req_in, WM8750State), + VMSTATE_INT32(idx_out, WM8750State), + VMSTATE_INT32(req_out, WM8750State), + VMSTATE_UINT8_ARRAY(outvol, WM8750State, 7), + VMSTATE_UINT8_ARRAY(outmute, WM8750State, 2), + VMSTATE_UINT8_ARRAY(invol, WM8750State, 4), + VMSTATE_UINT8_ARRAY(inmute, WM8750State, 2), + VMSTATE_UINT8_ARRAY(diff, WM8750State, 2), + VMSTATE_UINT8(pol, WM8750State), + VMSTATE_UINT8(ds, WM8750State), + VMSTATE_UINT8_ARRAY(monomix, WM8750State, 2), + VMSTATE_UINT8(alc, WM8750State), + VMSTATE_UINT8(mute, WM8750State), + VMSTATE_UINT8_ARRAY(path, WM8750State, 4), + VMSTATE_UINT8_ARRAY(mpath, WM8750State, 2), + VMSTATE_UINT8(format, WM8750State), + VMSTATE_UINT8(power, WM8750State), + VMSTATE_UINT8(rate_vmstate, WM8750State), + VMSTATE_I2C_SLAVE(parent_obj, WM8750State), + VMSTATE_END_OF_LIST() + } +}; + +static int wm8750_init(I2CSlave *i2c) +{ + WM8750State *s = WM8750(i2c); + + AUD_register_card(CODEC, &s->card); + wm8750_reset(I2C_SLAVE(s)); + + return 0; +} + +#if 0 +static void wm8750_fini(I2CSlave *i2c) +{ + WM8750State *s = WM8750(i2c); + + wm8750_reset(I2C_SLAVE(s)); + AUD_remove_card(&s->card); + g_free(s); +} +#endif + +void wm8750_data_req_set(DeviceState *dev, + void (*data_req)(void *, int, int), void *opaque) +{ + WM8750State *s = WM8750(dev); + + s->data_req = data_req; + s->opaque = opaque; +} + +void wm8750_dac_dat(void *opaque, uint32_t sample) +{ + WM8750State *s = (WM8750State *) opaque; + + *(uint32_t *) &s->data_out[s->idx_out] = sample; + s->req_out -= 4; + s->idx_out += 4; + if (s->idx_out >= sizeof(s->data_out) || s->req_out <= 0) + wm8750_out_flush(s); +} + +void *wm8750_dac_buffer(void *opaque, int samples) +{ + WM8750State *s = (WM8750State *) opaque; + /* XXX: Should check if there are samples free samples available */ + void *ret = s->data_out + s->idx_out; + + s->idx_out += samples << 2; + s->req_out -= samples << 2; + return ret; +} + +void wm8750_dac_commit(void *opaque) +{ + WM8750State *s = (WM8750State *) opaque; + + wm8750_out_flush(s); +} + +uint32_t wm8750_adc_dat(void *opaque) +{ + WM8750State *s = (WM8750State *) opaque; + uint32_t *data; + + if (s->idx_in >= sizeof(s->data_in)) + wm8750_in_load(s); + + data = (uint32_t *) &s->data_in[s->idx_in]; + s->req_in -= 4; + s->idx_in += 4; + return *data; +} + +void wm8750_set_bclk_in(void *opaque, int new_hz) +{ + WM8750State *s = (WM8750State *) opaque; + + s->ext_adc_hz = new_hz; + s->ext_dac_hz = new_hz; + wm8750_clk_update(s, 1); +} + +static void wm8750_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + + sc->init = wm8750_init; + sc->event = wm8750_event; + sc->recv = wm8750_rx; + sc->send = wm8750_tx; + dc->vmsd = &vmstate_wm8750; +} + +static const TypeInfo wm8750_info = { + .name = TYPE_WM8750, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(WM8750State), + .class_init = wm8750_class_init, +}; + +static void wm8750_register_types(void) +{ + type_register_static(&wm8750_info); +} + +type_init(wm8750_register_types) -- cgit 1.2.3-korg