Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
config SB16
|
||||
bool
|
||||
default y
|
||||
depends on ISA_BUS
|
||||
|
||||
config ES1370
|
||||
bool
|
||||
default y if PCI_DEVICES
|
||||
depends on PCI
|
||||
|
||||
config AC97
|
||||
bool
|
||||
default y if PCI_DEVICES
|
||||
depends on PCI
|
||||
|
||||
config ADLIB
|
||||
bool
|
||||
default y
|
||||
depends on ISA_BUS
|
||||
|
||||
config GUS
|
||||
bool
|
||||
default y
|
||||
depends on ISA_BUS
|
||||
|
||||
config CS4231A
|
||||
bool
|
||||
default y
|
||||
depends on ISA_BUS
|
||||
|
||||
config HDA
|
||||
bool
|
||||
default y if PCI_DEVICES
|
||||
depends on PCI
|
||||
|
||||
config PCSPK
|
||||
bool
|
||||
default y
|
||||
depends on I8254
|
||||
|
||||
config WM8750
|
||||
bool
|
||||
depends on I2C
|
||||
|
||||
config PL041
|
||||
bool
|
||||
|
||||
config CS4231
|
||||
bool
|
||||
|
||||
config ASC
|
||||
bool
|
||||
|
||||
config VIRTIO_SND
|
||||
bool
|
||||
default y
|
||||
depends on VIRTIO
|
||||
+1348
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AC97_H
|
||||
#define AC97_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 EACS_VRA 1
|
||||
#define EACS_VRM 8
|
||||
|
||||
#define MUTE_SHIFT 15
|
||||
|
||||
#endif /* AC97_H */
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/isa/isa.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define DEBUG 0
|
||||
|
||||
#define ADLIB_DESC "Yamaha YM3812 (OPL2)"
|
||||
|
||||
#if DEBUG
|
||||
#include "qemu/timer.h"
|
||||
#endif
|
||||
|
||||
#define ldebug(fmt, ...) do { \
|
||||
if (DEBUG) { \
|
||||
error_report("adlib: " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#include "fmopl.h"
|
||||
#define SHIFT 1
|
||||
|
||||
#define TYPE_ADLIB "adlib"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(AdlibState, ADLIB)
|
||||
|
||||
struct AdlibState {
|
||||
ISADevice parent_obj;
|
||||
|
||||
AudioBackend *audio_be;
|
||||
uint32_t freq;
|
||||
uint32_t port;
|
||||
int ticking[2];
|
||||
int enabled;
|
||||
int active;
|
||||
int bufpos;
|
||||
#if DEBUG
|
||||
int64_t exp[2];
|
||||
#endif
|
||||
int16_t *mixbuf;
|
||||
SWVoiceOut *voice;
|
||||
int left, pos, samples;
|
||||
FM_OPL *opl;
|
||||
PortioList port_list;
|
||||
};
|
||||
|
||||
static void adlib_stop_opl_timer (AdlibState *s, size_t n)
|
||||
{
|
||||
OPLTimerOver (s->opl, n);
|
||||
s->ticking[n] = 0;
|
||||
}
|
||||
|
||||
static void adlib_kill_timers (AdlibState *s)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
if (s->ticking[i]) {
|
||||
adlib_stop_opl_timer(s, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void adlib_write(void *opaque, uint32_t nport, uint32_t val)
|
||||
{
|
||||
AdlibState *s = opaque;
|
||||
int a = nport & 3;
|
||||
|
||||
s->active = 1;
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
|
||||
adlib_kill_timers (s);
|
||||
|
||||
OPLWrite (s->opl, a, val);
|
||||
}
|
||||
|
||||
static uint32_t adlib_read(void *opaque, uint32_t nport)
|
||||
{
|
||||
AdlibState *s = opaque;
|
||||
int a = nport & 3;
|
||||
|
||||
adlib_kill_timers (s);
|
||||
return OPLRead (s->opl, a);
|
||||
}
|
||||
|
||||
static void timer_handler (void *opaque, int c, double interval_Sec)
|
||||
{
|
||||
AdlibState *s = opaque;
|
||||
unsigned n = c & 1;
|
||||
#if DEBUG
|
||||
double interval;
|
||||
int64_t exp;
|
||||
#endif
|
||||
|
||||
if (interval_Sec == 0.0) {
|
||||
s->ticking[n] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
s->ticking[n] = 1;
|
||||
#if DEBUG
|
||||
interval = NANOSECONDS_PER_SECOND * interval_Sec;
|
||||
exp = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + interval;
|
||||
s->exp[n] = exp;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
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 = audio_be_write(
|
||||
s->audio_be,
|
||||
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, to_play, written;
|
||||
|
||||
samples = free >> SHIFT;
|
||||
if (!(s->active && s->enabled) || !samples) {
|
||||
return;
|
||||
}
|
||||
|
||||
to_play = 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 = MIN (samples, s->samples - s->pos);
|
||||
if (!samples) {
|
||||
return;
|
||||
}
|
||||
|
||||
YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples);
|
||||
|
||||
while (samples) {
|
||||
written = write_audio (s, samples);
|
||||
|
||||
if (written) {
|
||||
samples -= written;
|
||||
s->pos = (s->pos + written) % s->samples;
|
||||
}
|
||||
else {
|
||||
s->left = samples;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Adlib_fini (AdlibState *s)
|
||||
{
|
||||
if (s->opl) {
|
||||
OPLDestroy (s->opl);
|
||||
s->opl = NULL;
|
||||
}
|
||||
|
||||
g_free(s->mixbuf);
|
||||
|
||||
s->active = 0;
|
||||
s->enabled = 0;
|
||||
}
|
||||
|
||||
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 (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s->opl = OPLCreate (3579545, s->freq);
|
||||
if (!s->opl) {
|
||||
error_setg (errp, "OPLCreate %d failed", s->freq);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
OPLSetTimerHandler(s->opl, timer_handler, s);
|
||||
s->enabled = 1;
|
||||
}
|
||||
|
||||
as.freq = s->freq;
|
||||
as.nchannels = SHIFT;
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
as.big_endian = HOST_BIG_ENDIAN;
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
s->voice,
|
||||
"adlib",
|
||||
s,
|
||||
adlib_callback,
|
||||
&as
|
||||
);
|
||||
if (!s->voice) {
|
||||
Adlib_fini (s);
|
||||
error_setg (errp, "Initializing audio voice failed");
|
||||
return;
|
||||
}
|
||||
|
||||
s->samples = audio_be_get_buffer_size_out(s->audio_be, 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 const Property adlib_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(AdlibState, audio_be),
|
||||
DEFINE_PROP_UINT32 ("iobase", AdlibState, port, 0x220),
|
||||
DEFINE_PROP_UINT32 ("freq", AdlibState, freq, 44100),
|
||||
};
|
||||
|
||||
static void adlib_class_initfn(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS (klass);
|
||||
|
||||
dc->realize = adlib_realizefn;
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
dc->desc = ADLIB_DESC;
|
||||
device_class_set_props(dc, adlib_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo adlib_info = {
|
||||
.name = TYPE_ADLIB,
|
||||
.parent = TYPE_ISA_DEVICE,
|
||||
.instance_size = sizeof (AdlibState),
|
||||
.class_init = adlib_class_initfn,
|
||||
};
|
||||
|
||||
static void adlib_register_types (void)
|
||||
{
|
||||
type_register_static (&adlib_info);
|
||||
audio_register_model("adlib", ADLIB_DESC, TYPE_ADLIB);
|
||||
}
|
||||
|
||||
type_init (adlib_register_types)
|
||||
+729
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
* QEMU Apple Sound Chip emulation
|
||||
*
|
||||
* Apple Sound Chip (ASC) 344S0063
|
||||
* Enhanced Apple Sound Chip (EASC) 343S1063
|
||||
*
|
||||
* Copyright (c) 2012-2018 Laurent Vivier <[email protected]>
|
||||
* Copyright (c) 2022 Mark Cave-Ayland <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/audio/asc.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "trace.h"
|
||||
|
||||
/*
|
||||
* Linux doesn't provide information about ASC, see arch/m68k/mac/macboing.c
|
||||
* and arch/m68k/include/asm/mac_asc.h
|
||||
*
|
||||
* best information is coming from MAME:
|
||||
* https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.h
|
||||
* https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.cpp
|
||||
* Emulation by R. Belmont
|
||||
* or MESS:
|
||||
* http://mess.redump.net/mess/driver_info/easc
|
||||
*
|
||||
* 0x800: VERSION
|
||||
* 0x801: MODE
|
||||
* 1=FIFO mode,
|
||||
* 2=wavetable mode
|
||||
* 0x802: CONTROL
|
||||
* bit 0=analog or PWM output,
|
||||
* 1=stereo/mono,
|
||||
* 7=processing time exceeded
|
||||
* 0x803: FIFO MODE
|
||||
* bit 7=clear FIFO,
|
||||
* bit 1="non-ROM companding",
|
||||
* bit 0="ROM companding")
|
||||
* 0x804: FIFO IRQ STATUS
|
||||
* bit 0=ch A 1/2 full,
|
||||
* 1=ch A full,
|
||||
* 2=ch B 1/2 full,
|
||||
* 3=ch B full)
|
||||
* 0x805: WAVETABLE CONTROL
|
||||
* bits 0-3 wavetables 0-3 start
|
||||
* 0x806: VOLUME
|
||||
* bits 2-4 = 3 bit internal ASC volume,
|
||||
* bits 5-7 = volume control sent to Sony sound chip
|
||||
* 0x807: CLOCK RATE
|
||||
* 0 = Mac 22257 Hz,
|
||||
* 1 = undefined,
|
||||
* 2 = 22050 Hz,
|
||||
* 3 = 44100 Hz
|
||||
* 0x80a: PLAY REC A
|
||||
* 0x80f: TEST
|
||||
* bits 6-7 = digital test,
|
||||
* bits 4-5 = analog test
|
||||
* 0x810: WAVETABLE 0 PHASE
|
||||
* big-endian 9.15 fixed-point, only 24 bits valid
|
||||
* 0x814: WAVETABLE 0 INCREMENT
|
||||
* big-endian 9.15 fixed-point, only 24 bits valid
|
||||
* 0x818: WAVETABLE 1 PHASE
|
||||
* 0x81C: WAVETABLE 1 INCREMENT
|
||||
* 0x820: WAVETABLE 2 PHASE
|
||||
* 0x824: WAVETABLE 2 INCREMENT
|
||||
* 0x828: WAVETABLE 3 PHASE
|
||||
* 0x82C: WAVETABLE 3 INCREMENT
|
||||
* 0x830: UNKNOWN START
|
||||
* NetBSD writes Wavetable data here (are there more
|
||||
* wavetables/channels than we know about?)
|
||||
* 0x857: UNKNOWN END
|
||||
*/
|
||||
|
||||
#define ASC_SIZE 0x2000
|
||||
|
||||
enum {
|
||||
ASC_VERSION = 0x00,
|
||||
ASC_MODE = 0x01,
|
||||
ASC_CONTROL = 0x02,
|
||||
ASC_FIFOMODE = 0x03,
|
||||
ASC_FIFOIRQ = 0x04,
|
||||
ASC_WAVECTRL = 0x05,
|
||||
ASC_VOLUME = 0x06,
|
||||
ASC_CLOCK = 0x07,
|
||||
ASC_PLAYRECA = 0x0a,
|
||||
ASC_TEST = 0x0f,
|
||||
ASC_WAVETABLE = 0x10
|
||||
};
|
||||
|
||||
#define ASC_FIFO_STATUS_HALF_FULL 1
|
||||
#define ASC_FIFO_STATUS_FULL_EMPTY 2
|
||||
|
||||
#define ASC_EXTREGS_FIFOCTRL 0x8
|
||||
#define ASC_EXTREGS_INTCTRL 0x9
|
||||
#define ASC_EXTREGS_CDXA_DECOMP_FILT 0x10
|
||||
|
||||
#define ASC_FIFO_CYCLE_TIME ((NANOSECONDS_PER_SECOND / ASC_FREQ) * \
|
||||
0x400)
|
||||
|
||||
static void asc_raise_irq(ASCState *s)
|
||||
{
|
||||
qemu_set_irq(s->irq, 1);
|
||||
}
|
||||
|
||||
static void asc_lower_irq(ASCState *s)
|
||||
{
|
||||
qemu_set_irq(s->irq, 0);
|
||||
}
|
||||
|
||||
static uint8_t asc_fifo_get(ASCFIFOState *fs)
|
||||
{
|
||||
ASCState *s = container_of(fs, ASCState, fifos[fs->index]);
|
||||
bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1;
|
||||
uint8_t val;
|
||||
|
||||
assert(fs->cnt);
|
||||
|
||||
val = fs->fifo[fs->rptr];
|
||||
trace_asc_fifo_get('A' + fs->index, fs->rptr, fs->cnt, val);
|
||||
|
||||
fs->rptr++;
|
||||
fs->rptr &= 0x3ff;
|
||||
fs->cnt--;
|
||||
|
||||
if (fs->cnt <= 0x1ff) {
|
||||
/* FIFO less than half full */
|
||||
fs->int_status |= ASC_FIFO_STATUS_HALF_FULL;
|
||||
} else {
|
||||
/* FIFO more than half full */
|
||||
fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL;
|
||||
}
|
||||
|
||||
if (fs->cnt == 0x1ff && fifo_half_irq_enabled) {
|
||||
/* Raise FIFO half full IRQ */
|
||||
asc_raise_irq(s);
|
||||
}
|
||||
|
||||
if (fs->cnt == 0) {
|
||||
/* Raise FIFO empty IRQ */
|
||||
fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY;
|
||||
asc_raise_irq(s);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int generate_fifo(ASCState *s, int maxsamples)
|
||||
{
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
uint8_t *buf = s->mixbuf;
|
||||
int i, wcount = 0;
|
||||
|
||||
while (wcount < maxsamples) {
|
||||
uint8_t val;
|
||||
int16_t d, f0, f1;
|
||||
int32_t t;
|
||||
int shift, filter;
|
||||
bool hasdata = false;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
ASCFIFOState *fs = &s->fifos[i];
|
||||
|
||||
switch (fs->extregs[ASC_EXTREGS_FIFOCTRL] & 0x83) {
|
||||
case 0x82:
|
||||
/*
|
||||
* CD-XA BRR mode: decompress 15 bytes into 28 16-bit
|
||||
* samples
|
||||
*/
|
||||
if (!fs->cnt) {
|
||||
val = 0x80;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fs->xa_cnt == -1) {
|
||||
/* Start of packet, get flags */
|
||||
fs->xa_flags = asc_fifo_get(fs);
|
||||
fs->xa_cnt = 0;
|
||||
}
|
||||
|
||||
shift = fs->xa_flags & 0xf;
|
||||
filter = fs->xa_flags >> 4;
|
||||
f0 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT +
|
||||
(filter << 1) + 1];
|
||||
f1 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT +
|
||||
(filter << 1)];
|
||||
|
||||
if ((fs->xa_cnt & 1) == 0) {
|
||||
if (!fs->cnt) {
|
||||
val = 0x80;
|
||||
break;
|
||||
}
|
||||
|
||||
fs->xa_val = asc_fifo_get(fs);
|
||||
d = (fs->xa_val & 0xf) << 12;
|
||||
} else {
|
||||
d = (fs->xa_val & 0xf0) << 8;
|
||||
}
|
||||
t = (d >> shift) + (((fs->xa_last[0] * f0) +
|
||||
(fs->xa_last[1] * f1) + 32) >> 6);
|
||||
if (t < -32768) {
|
||||
t = -32768;
|
||||
} else if (t > 32767) {
|
||||
t = 32767;
|
||||
}
|
||||
|
||||
/*
|
||||
* CD-XA BRR generates 16-bit signed output, so convert to
|
||||
* 8-bit before writing to buffer. Does real hardware do the
|
||||
* same?
|
||||
*/
|
||||
val = (uint8_t)(t / 256) ^ 0x80;
|
||||
hasdata = true;
|
||||
fs->xa_cnt++;
|
||||
|
||||
fs->xa_last[1] = fs->xa_last[0];
|
||||
fs->xa_last[0] = (int16_t)t;
|
||||
|
||||
if (fs->xa_cnt == 28) {
|
||||
/* End of packet */
|
||||
fs->xa_cnt = -1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* fallthrough */
|
||||
case 0x80:
|
||||
/* Raw mode */
|
||||
if (fs->cnt) {
|
||||
val = asc_fifo_get(fs);
|
||||
hasdata = true;
|
||||
} else {
|
||||
val = 0x80;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
buf[wcount * 2 + i] = val;
|
||||
}
|
||||
|
||||
if (!hasdata) {
|
||||
break;
|
||||
}
|
||||
|
||||
wcount++;
|
||||
}
|
||||
|
||||
/*
|
||||
* MacOS (un)helpfully leaves the FIFO engine running even when it has
|
||||
* finished writing out samples, but still expects the FIFO empty
|
||||
* interrupts to be generated for each FIFO cycle (without these interrupts
|
||||
* MacOS will freeze)
|
||||
*/
|
||||
if (s->fifos[0].cnt == 0 && s->fifos[1].cnt == 0) {
|
||||
if (!s->fifo_empty_ns) {
|
||||
/* FIFO has completed first empty cycle */
|
||||
s->fifo_empty_ns = now;
|
||||
} else if (now > (s->fifo_empty_ns + ASC_FIFO_CYCLE_TIME)) {
|
||||
/* FIFO has completed entire cycle with no data */
|
||||
s->fifos[0].int_status |= ASC_FIFO_STATUS_HALF_FULL |
|
||||
ASC_FIFO_STATUS_FULL_EMPTY;
|
||||
s->fifos[1].int_status |= ASC_FIFO_STATUS_HALF_FULL |
|
||||
ASC_FIFO_STATUS_FULL_EMPTY;
|
||||
s->fifo_empty_ns = now;
|
||||
asc_raise_irq(s);
|
||||
}
|
||||
} else {
|
||||
/* FIFO contains data, reset empty time */
|
||||
s->fifo_empty_ns = 0;
|
||||
}
|
||||
|
||||
return wcount;
|
||||
}
|
||||
|
||||
static int generate_wavetable(ASCState *s, int maxsamples)
|
||||
{
|
||||
uint8_t *buf = s->mixbuf;
|
||||
int channel, count = 0;
|
||||
|
||||
while (count < maxsamples) {
|
||||
uint32_t left = 0, right = 0;
|
||||
uint8_t sample;
|
||||
|
||||
for (channel = 0; channel < 4; channel++) {
|
||||
ASCFIFOState *fs = &s->fifos[channel >> 1];
|
||||
int chanreg = ASC_WAVETABLE + (channel << 3);
|
||||
uint32_t phase, incr, offset;
|
||||
|
||||
phase = ldl_be_p(&s->regs[chanreg]);
|
||||
incr = ldl_be_p(&s->regs[chanreg + sizeof(uint32_t)]);
|
||||
|
||||
phase += incr;
|
||||
offset = (phase >> 15) & 0x1ff;
|
||||
sample = fs->fifo[0x200 * (channel >> 1) + offset];
|
||||
|
||||
stl_be_p(&s->regs[chanreg], phase);
|
||||
|
||||
left += sample;
|
||||
right += sample;
|
||||
}
|
||||
|
||||
buf[count * 2] = left >> 2;
|
||||
buf[count * 2 + 1] = right >> 2;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void asc_out_cb(void *opaque, int free_b)
|
||||
{
|
||||
ASCState *s = opaque;
|
||||
int samples, generated;
|
||||
|
||||
if (free_b == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
samples = MIN(s->samples, free_b >> s->shift);
|
||||
|
||||
switch (s->regs[ASC_MODE] & 3) {
|
||||
default:
|
||||
/* Off */
|
||||
generated = 0;
|
||||
break;
|
||||
case 1:
|
||||
/* FIFO mode */
|
||||
generated = generate_fifo(s, samples);
|
||||
break;
|
||||
case 2:
|
||||
/* Wave table mode */
|
||||
generated = generate_wavetable(s, samples);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!generated) {
|
||||
/* Workaround for audio underflow bug on Windows dsound backend */
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
int silent_samples = muldiv64(now - s->fifo_empty_ns,
|
||||
NANOSECONDS_PER_SECOND, ASC_FREQ);
|
||||
|
||||
if (silent_samples > ASC_FIFO_CYCLE_TIME / 2) {
|
||||
/*
|
||||
* No new FIFO data within half a cycle time (~23ms) so fill the
|
||||
* entire available buffer with silence. This prevents an issue
|
||||
* with the Windows dsound backend whereby the sound appears to
|
||||
* loop because the FIFO has run out of data, and the driver
|
||||
* reuses the stale content in its circular audio buffer.
|
||||
*/
|
||||
audio_be_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
audio_be_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift);
|
||||
}
|
||||
|
||||
static uint64_t asc_fifo_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
ASCFIFOState *fs = opaque;
|
||||
|
||||
trace_asc_read_fifo('A' + fs->index, addr, size, fs->fifo[addr]);
|
||||
return fs->fifo[addr];
|
||||
}
|
||||
|
||||
static void asc_fifo_write(void *opaque, hwaddr addr, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
ASCFIFOState *fs = opaque;
|
||||
ASCState *s = container_of(fs, ASCState, fifos[fs->index]);
|
||||
bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1;
|
||||
|
||||
trace_asc_write_fifo('A' + fs->index, addr, size, fs->wptr, fs->cnt, value);
|
||||
|
||||
if (s->regs[ASC_MODE] == 1) {
|
||||
fs->fifo[fs->wptr++] = value;
|
||||
fs->wptr &= 0x3ff;
|
||||
fs->cnt++;
|
||||
|
||||
if (fs->cnt <= 0x1ff) {
|
||||
/* FIFO less than half full */
|
||||
fs->int_status |= ASC_FIFO_STATUS_HALF_FULL;
|
||||
} else {
|
||||
/* FIFO at least half full */
|
||||
fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL;
|
||||
}
|
||||
|
||||
if (fs->cnt == 0x200 && fifo_half_irq_enabled) {
|
||||
/* Raise FIFO half full interrupt */
|
||||
asc_raise_irq(s);
|
||||
}
|
||||
|
||||
if (fs->cnt == 0x3ff) {
|
||||
/* Raise FIFO full interrupt */
|
||||
fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY;
|
||||
asc_raise_irq(s);
|
||||
}
|
||||
} else {
|
||||
fs->fifo[addr] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps asc_fifo_ops = {
|
||||
.read = asc_fifo_read,
|
||||
.write = asc_fifo_write,
|
||||
.impl = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 1,
|
||||
},
|
||||
.endianness = DEVICE_BIG_ENDIAN,
|
||||
};
|
||||
|
||||
static void asc_fifo_reset(ASCFIFOState *fs);
|
||||
|
||||
static uint64_t asc_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
ASCState *s = opaque;
|
||||
uint64_t prev, value;
|
||||
|
||||
switch (addr) {
|
||||
case ASC_VERSION:
|
||||
switch (s->type) {
|
||||
default:
|
||||
case ASC_TYPE_ASC:
|
||||
value = 0;
|
||||
break;
|
||||
case ASC_TYPE_EASC:
|
||||
value = 0xb0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ASC_FIFOIRQ:
|
||||
prev = (s->fifos[0].int_status & 0x3) |
|
||||
(s->fifos[1].int_status & 0x3) << 2;
|
||||
|
||||
s->fifos[0].int_status = 0;
|
||||
s->fifos[1].int_status = 0;
|
||||
asc_lower_irq(s);
|
||||
value = prev;
|
||||
break;
|
||||
default:
|
||||
value = s->regs[addr];
|
||||
break;
|
||||
}
|
||||
|
||||
trace_asc_read_reg(addr, size, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void asc_write(void *opaque, hwaddr addr, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
ASCState *s = opaque;
|
||||
|
||||
switch (addr) {
|
||||
case ASC_MODE:
|
||||
value &= 3;
|
||||
if (value != s->regs[ASC_MODE]) {
|
||||
asc_fifo_reset(&s->fifos[0]);
|
||||
asc_fifo_reset(&s->fifos[1]);
|
||||
asc_lower_irq(s);
|
||||
if (value != 0) {
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
} else {
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ASC_FIFOMODE:
|
||||
if (value & 0x80) {
|
||||
asc_fifo_reset(&s->fifos[0]);
|
||||
asc_fifo_reset(&s->fifos[1]);
|
||||
asc_lower_irq(s);
|
||||
}
|
||||
break;
|
||||
case ASC_WAVECTRL:
|
||||
break;
|
||||
case ASC_VOLUME:
|
||||
{
|
||||
int vol = (value & 0xe0);
|
||||
|
||||
audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
trace_asc_write_reg(addr, size, value);
|
||||
s->regs[addr] = value;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps asc_regs_ops = {
|
||||
.read = asc_read,
|
||||
.write = asc_write,
|
||||
.endianness = DEVICE_BIG_ENDIAN,
|
||||
.impl = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 1,
|
||||
}
|
||||
};
|
||||
|
||||
static uint64_t asc_ext_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
ASCFIFOState *fs = opaque;
|
||||
uint64_t value;
|
||||
|
||||
value = fs->extregs[addr];
|
||||
|
||||
trace_asc_read_extreg('A' + fs->index, addr, size, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void asc_ext_write(void *opaque, hwaddr addr, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
ASCFIFOState *fs = opaque;
|
||||
|
||||
trace_asc_write_extreg('A' + fs->index, addr, size, value);
|
||||
|
||||
fs->extregs[addr] = value;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps asc_extregs_ops = {
|
||||
.read = asc_ext_read,
|
||||
.write = asc_ext_write,
|
||||
.impl = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 1,
|
||||
},
|
||||
.endianness = DEVICE_BIG_ENDIAN,
|
||||
};
|
||||
|
||||
static int asc_post_load(void *opaque, int version)
|
||||
{
|
||||
ASCState *s = ASC(opaque);
|
||||
|
||||
if (s->regs[ASC_MODE] != 0) {
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_asc_fifo = {
|
||||
.name = "apple-sound-chip.fifo",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT8_ARRAY(fifo, ASCFIFOState, ASC_FIFO_SIZE),
|
||||
VMSTATE_UINT8(int_status, ASCFIFOState),
|
||||
VMSTATE_INT32(cnt, ASCFIFOState),
|
||||
VMSTATE_INT32(wptr, ASCFIFOState),
|
||||
VMSTATE_INT32(rptr, ASCFIFOState),
|
||||
VMSTATE_UINT8_ARRAY(extregs, ASCFIFOState, ASC_EXTREG_SIZE),
|
||||
VMSTATE_INT32(xa_cnt, ASCFIFOState),
|
||||
VMSTATE_UINT8(xa_val, ASCFIFOState),
|
||||
VMSTATE_UINT8(xa_flags, ASCFIFOState),
|
||||
VMSTATE_INT16_ARRAY(xa_last, ASCFIFOState, 2),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_asc = {
|
||||
.name = "apple-sound-chip",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.post_load = asc_post_load,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_STRUCT_ARRAY(fifos, ASCState, 2, 0, vmstate_asc_fifo,
|
||||
ASCFIFOState),
|
||||
VMSTATE_UINT8_ARRAY(regs, ASCState, ASC_REG_SIZE),
|
||||
VMSTATE_INT64(fifo_empty_ns, ASCState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void asc_fifo_reset(ASCFIFOState *fs)
|
||||
{
|
||||
fs->wptr = 0;
|
||||
fs->rptr = 0;
|
||||
fs->cnt = 0;
|
||||
fs->xa_cnt = -1;
|
||||
fs->int_status = 0;
|
||||
}
|
||||
|
||||
static void asc_fifo_init(ASCFIFOState *fs, int index)
|
||||
{
|
||||
ASCState *s = container_of(fs, ASCState, fifos[index]);
|
||||
char *name;
|
||||
|
||||
fs->index = index;
|
||||
name = g_strdup_printf("asc.fifo%c", 'A' + index);
|
||||
memory_region_init_io(&fs->mem_fifo, OBJECT(s), &asc_fifo_ops, fs,
|
||||
name, ASC_FIFO_SIZE);
|
||||
g_free(name);
|
||||
|
||||
name = g_strdup_printf("asc.extregs%c", 'A' + index);
|
||||
memory_region_init_io(&fs->mem_extregs, OBJECT(s), &asc_extregs_ops,
|
||||
fs, name, ASC_EXTREG_SIZE);
|
||||
g_free(name);
|
||||
}
|
||||
|
||||
static void asc_reset_hold(Object *obj, ResetType type)
|
||||
{
|
||||
ASCState *s = ASC(obj);
|
||||
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 0);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
asc_fifo_reset(&s->fifos[0]);
|
||||
asc_fifo_reset(&s->fifos[1]);
|
||||
s->fifo_empty_ns = 0;
|
||||
|
||||
if (s->type == ASC_TYPE_ASC) {
|
||||
/* FIFO half full IRQs enabled by default */
|
||||
s->fifos[0].extregs[ASC_EXTREGS_INTCTRL] = 1;
|
||||
s->fifos[1].extregs[ASC_EXTREGS_INTCTRL] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void asc_unrealize(DeviceState *dev)
|
||||
{
|
||||
ASCState *s = ASC(dev);
|
||||
|
||||
g_free(s->mixbuf);
|
||||
g_free(s->silentbuf);
|
||||
}
|
||||
|
||||
static void asc_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
ASCState *s = ASC(dev);
|
||||
struct audsettings as;
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
as.freq = ASC_FREQ;
|
||||
as.nchannels = 2;
|
||||
as.fmt = AUDIO_FORMAT_U8;
|
||||
as.big_endian = HOST_BIG_ENDIAN;
|
||||
|
||||
s->voice = audio_be_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb,
|
||||
&as);
|
||||
if (!s->voice) {
|
||||
error_setg(errp, "Initializing audio stream failed");
|
||||
return;
|
||||
}
|
||||
|
||||
s->shift = 1;
|
||||
s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
|
||||
s->mixbuf = g_malloc0(s->samples << s->shift);
|
||||
|
||||
s->silentbuf = g_malloc(s->samples << s->shift);
|
||||
memset(s->silentbuf, 0x80, s->samples << s->shift);
|
||||
|
||||
/* Add easc registers if required */
|
||||
if (s->type == ASC_TYPE_EASC) {
|
||||
memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET,
|
||||
&s->fifos[0].mem_extregs);
|
||||
memory_region_add_subregion(&s->asc,
|
||||
ASC_EXTREG_OFFSET + ASC_EXTREG_SIZE,
|
||||
&s->fifos[1].mem_extregs);
|
||||
}
|
||||
}
|
||||
|
||||
static void asc_init(Object *obj)
|
||||
{
|
||||
ASCState *s = ASC(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init(&s->asc, OBJECT(obj), "asc", ASC_SIZE);
|
||||
|
||||
asc_fifo_init(&s->fifos[0], 0);
|
||||
asc_fifo_init(&s->fifos[1], 1);
|
||||
|
||||
memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET,
|
||||
&s->fifos[0].mem_fifo);
|
||||
memory_region_add_subregion(&s->asc,
|
||||
ASC_FIFO_OFFSET + ASC_FIFO_SIZE,
|
||||
&s->fifos[1].mem_fifo);
|
||||
|
||||
memory_region_init_io(&s->mem_regs, OBJECT(obj), &asc_regs_ops, s,
|
||||
"asc.regs", ASC_REG_SIZE);
|
||||
memory_region_add_subregion(&s->asc, ASC_REG_OFFSET, &s->mem_regs);
|
||||
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
sysbus_init_mmio(sbd, &s->asc);
|
||||
}
|
||||
|
||||
static const Property asc_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(ASCState, audio_be),
|
||||
DEFINE_PROP_UINT8("asctype", ASCState, type, ASC_TYPE_ASC),
|
||||
};
|
||||
|
||||
static void asc_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
ResettableClass *rc = RESETTABLE_CLASS(oc);
|
||||
|
||||
dc->realize = asc_realize;
|
||||
dc->unrealize = asc_unrealize;
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
dc->vmsd = &vmstate_asc;
|
||||
device_class_set_props(dc, asc_properties);
|
||||
rc->phases.hold = asc_reset_hold;
|
||||
}
|
||||
|
||||
static const TypeInfo asc_info_types[] = {
|
||||
{
|
||||
.name = TYPE_ASC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(ASCState),
|
||||
.instance_init = asc_init,
|
||||
.class_init = asc_class_init,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(asc_info_types)
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "trace.h"
|
||||
#include "qom/object.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 "sun-CS4231"
|
||||
typedef struct CSState CSState;
|
||||
DECLARE_INSTANCE_CHECKER(CSState, CS4231,
|
||||
TYPE_CS4231)
|
||||
|
||||
struct CSState {
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
MemoryRegion iomem;
|
||||
qemu_irq irq;
|
||||
uint32_t regs[CS_REGS];
|
||||
uint8_t dregs[CS_DREGS];
|
||||
};
|
||||
|
||||
#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_BIG_ENDIAN,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_cs4231 = {
|
||||
.name ="cs4231",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, CSState, CS_REGS),
|
||||
VMSTATE_UINT8_ARRAY(dregs, CSState, CS_DREGS),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void cs4231_init(Object *obj)
|
||||
{
|
||||
CSState *s = CS4231(obj);
|
||||
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &cs_mem_ops, s, "cs4321",
|
||||
CS_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
}
|
||||
|
||||
static void cs4231_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, cs_reset);
|
||||
dc->vmsd = &vmstate_cs4231;
|
||||
}
|
||||
|
||||
static const TypeInfo cs4231_info = {
|
||||
.name = TYPE_CS4231,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(CSState),
|
||||
.instance_init = cs4231_init,
|
||||
.class_init = cs4231_class_init,
|
||||
};
|
||||
|
||||
static void cs4231_register_types(void)
|
||||
{
|
||||
type_register_static(&cs4231_info);
|
||||
}
|
||||
|
||||
type_init(cs4231_register_types)
|
||||
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "hw/isa/isa.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
/*
|
||||
Missing features:
|
||||
ADC
|
||||
Loopback
|
||||
Timer
|
||||
ADPCM
|
||||
More...
|
||||
*/
|
||||
|
||||
#define DEBUG 0
|
||||
/* #define DEBUG_XLAW */
|
||||
|
||||
static struct {
|
||||
int aci_counter;
|
||||
} conf = {1};
|
||||
|
||||
#define dolog(fmt, ...) do { \
|
||||
if (DEBUG) { \
|
||||
error_report("cs4231a: " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define lwarn(fmt, ...) warn_report("cs4231a: " fmt, ##__VA_ARGS__)
|
||||
#define lerr(fmt, ...) error_report("cs4231a: " fmt, ##__VA_ARGS__)
|
||||
|
||||
#define CS_REGS 16
|
||||
#define CS_DREGS 32
|
||||
|
||||
#define TYPE_CS4231A "cs4231a"
|
||||
typedef struct CSState CSState;
|
||||
DECLARE_INSTANCE_CHECKER(CSState, CS4231A,
|
||||
TYPE_CS4231A)
|
||||
|
||||
struct CSState {
|
||||
ISADevice dev;
|
||||
AudioBackend *audio_be;
|
||||
MemoryRegion ioports;
|
||||
qemu_irq pic;
|
||||
uint32_t regs[CS_REGS];
|
||||
uint8_t dregs[CS_DREGS];
|
||||
uint32_t irq;
|
||||
uint32_t dma;
|
||||
uint32_t port;
|
||||
IsaDma *isa_dma;
|
||||
int shift;
|
||||
int dma_running;
|
||||
int audio_free;
|
||||
int transferred;
|
||||
int aci_counter;
|
||||
SWVoiceOut *voice;
|
||||
const int16_t *tab;
|
||||
};
|
||||
|
||||
#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 const 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 const 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 const 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;
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
|
||||
#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=0x%x)", val);
|
||||
goto error;
|
||||
}
|
||||
|
||||
as.nchannels = (val & (1 << 4)) ? 2 : 1;
|
||||
as.big_endian = false;
|
||||
s->tab = NULL;
|
||||
|
||||
switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) {
|
||||
case 0:
|
||||
as.fmt = AUDIO_FORMAT_U8;
|
||||
s->shift = as.nchannels == 2;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
s->tab = MuLawDecompressTable;
|
||||
goto x_law;
|
||||
case 3:
|
||||
s->tab = ALawDecompressTable;
|
||||
x_law:
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
as.big_endian = HOST_BIG_ENDIAN;
|
||||
s->shift = as.nchannels == 2;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
as.big_endian = true;
|
||||
/* fall through */
|
||||
case 2:
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
s->shift = as.nchannels;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
case 4:
|
||||
lerr("attempt to use reserved format value (0x%x)", val);
|
||||
goto error;
|
||||
|
||||
case 5:
|
||||
lerr("ADPCM 4 bit IMA compatible format is not supported");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
s->voice,
|
||||
"cs4231a",
|
||||
s,
|
||||
cs_audio_callback,
|
||||
&as
|
||||
);
|
||||
|
||||
if (s->dregs[Interface_Configuration] & PEN) {
|
||||
if (!s->dma_running) {
|
||||
k->hold_DREQ(s->isa_dma, s->dma);
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
s->transferred = 0;
|
||||
}
|
||||
s->dma_running = 1;
|
||||
}
|
||||
else {
|
||||
if (s->dma_running) {
|
||||
k->release_DREQ(s->isa_dma, s->dma);
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 0);
|
||||
}
|
||||
s->dma_running = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
if (s->dma_running) {
|
||||
k->release_DREQ(s->isa_dma, s->dma);
|
||||
audio_be_set_active_out(s->audio_be, 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", 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 0x%x to reserved indirect register %d",
|
||||
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(0x%x, 0x%x) is not set, val=0x%x",
|
||||
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 (0x%x)", 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) {
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
k->release_DREQ(s->isa_dma, s->dma);
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 0);
|
||||
s->dma_running = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Error_Status_And_Initialization:
|
||||
lwarn("attempt to write to read only register %d", iaddr);
|
||||
break;
|
||||
|
||||
case MODE_And_ID:
|
||||
dolog("val=0x%x", 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");
|
||||
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 0x%x", val);
|
||||
s->dregs[iaddr] = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
s->dregs[iaddr] = val;
|
||||
break;
|
||||
}
|
||||
dolog("written value 0x%x to indirect register %d", 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 0x%x to PIO register", val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int cs_write_audio (CSState *s, int nchan, int dma_pos,
|
||||
int dma_len, int len)
|
||||
{
|
||||
int temp, net;
|
||||
QEMU_UNINITIALIZED uint8_t tmpbuf[4096];
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
|
||||
temp = len;
|
||||
net = 0;
|
||||
|
||||
while (temp) {
|
||||
int left = dma_len - dma_pos;
|
||||
int copied;
|
||||
size_t to_copy;
|
||||
|
||||
to_copy = MIN (temp, left);
|
||||
if (to_copy > sizeof (tmpbuf)) {
|
||||
to_copy = sizeof (tmpbuf);
|
||||
}
|
||||
|
||||
copied = k->read_memory(s->isa_dma, nchan, tmpbuf, dma_pos, to_copy);
|
||||
if (s->tab) {
|
||||
int i;
|
||||
QEMU_UNINITIALIZED int16_t linbuf[4096];
|
||||
|
||||
for (i = 0; i < copied; ++i)
|
||||
linbuf[i] = s->tab[tmpbuf[i]];
|
||||
copied = audio_be_write(s->audio_be, s->voice, linbuf, copied << 1);
|
||||
copied >>= 1;
|
||||
}
|
||||
else {
|
||||
copied = audio_be_write(s->audio_be, 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 = 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) {
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
k->release_DREQ(s->isa_dma, s->dma);
|
||||
audio_be_set_active_out(s->audio_be, 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 = (const 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);
|
||||
ISABus *bus = isa_bus_from_device(d);
|
||||
CSState *s = CS4231A (dev);
|
||||
IsaDmaClass *k;
|
||||
|
||||
s->isa_dma = isa_bus_get_dma(bus, s->dma);
|
||||
if (!s->isa_dma) {
|
||||
error_setg(errp, "ISA controller does not support DMA");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->irq >= ISA_NUM_IRQS) {
|
||||
error_setg(errp, "Invalid IRQ %d (max %d)", s->irq, ISA_NUM_IRQS - 1);
|
||||
return;
|
||||
}
|
||||
s->pic = isa_bus_get_irq(bus, s->irq);
|
||||
k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
k->register_channel(s->isa_dma, s->dma, cs_dma_read, s);
|
||||
|
||||
isa_register_ioport (d, &s->ioports, s->port);
|
||||
}
|
||||
|
||||
static const Property cs4231a_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(CSState, audio_be),
|
||||
DEFINE_PROP_UINT32 ("iobase", CSState, port, 0x534),
|
||||
DEFINE_PROP_UINT32 ("irq", CSState, irq, 9),
|
||||
DEFINE_PROP_UINT32 ("dma", CSState, dma, 3),
|
||||
};
|
||||
|
||||
static void cs4231a_class_initfn(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS (klass);
|
||||
|
||||
dc->realize = cs4231a_realizefn;
|
||||
device_class_set_legacy_reset(dc, cs4231a_reset);
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
dc->desc = "Crystal Semiconductor CS4231A";
|
||||
dc->vmsd = &vmstate_cs4231a;
|
||||
device_class_set_props(dc, 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);
|
||||
audio_register_model("cs4231a", "CS4231A", TYPE_CS4231A);
|
||||
}
|
||||
|
||||
type_init (cs4231a_register_types)
|
||||
@@ -0,0 +1,910 @@
|
||||
/*
|
||||
* 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 0
|
||||
#define VERBOSE_ES1370 0
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qom/object.h"
|
||||
#include "trace.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 ([email protected])
|
||||
*/
|
||||
|
||||
/* 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
|
||||
|
||||
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);
|
||||
|
||||
static void print_ctl(uint32_t val)
|
||||
{
|
||||
if (DEBUG_ES1370) {
|
||||
char buf[1024];
|
||||
|
||||
buf[0] = '\0';
|
||||
#define a(n) if (val & CTRL_##n) pstrcat(buf, sizeof(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
|
||||
error_report("es1370: ctl - PCLKDIV %d(DAC2 freq %d), freq %d,%s",
|
||||
(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)
|
||||
{
|
||||
if (DEBUG_ES1370) {
|
||||
static const char *fmt_names[] = {"8M", "8S", "16M", "16S"};
|
||||
char buf[1024];
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
#define a(n) if (val & SCTRL_##n) pstrcat(buf, sizeof(buf), " "#n)
|
||||
#define b(n) if (!(val & SCTRL_##n)) pstrcat(buf, sizeof(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]) {
|
||||
pstrcat(buf, sizeof(buf), "\n ");
|
||||
} else {
|
||||
buf[0] = ' ';
|
||||
buf[1] = '\0';
|
||||
}
|
||||
#undef b
|
||||
#undef a
|
||||
error_report("es1370: "
|
||||
"%s p2_end_inc %d, p2_st_inc %d,"
|
||||
" r1_fmt %s, p2_fmt %s, p1_fmt %s",
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
#define lwarn(fmt, ...) \
|
||||
do { \
|
||||
if (VERBOSE_ES1370) { \
|
||||
error_report("es1370: " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPE_ES1370 "ES1370"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(ES1370State, ES1370)
|
||||
|
||||
struct chan {
|
||||
uint32_t shift;
|
||||
uint32_t leftover;
|
||||
uint32_t scount;
|
||||
uint32_t frame_addr;
|
||||
uint32_t frame_cnt;
|
||||
};
|
||||
|
||||
struct ES1370State {
|
||||
PCIDevice dev;
|
||||
AudioBackend *audio_be;
|
||||
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;
|
||||
};
|
||||
|
||||
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) {
|
||||
audio_be_close_in(s->audio_be, s->adc_voice);
|
||||
s->adc_voice = NULL;
|
||||
} else {
|
||||
audio_be_close_out(s->audio_be, 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);
|
||||
trace_es1370_stream_format(i, new_freq,
|
||||
new_fmt & 2 ? "s16" : "u8", new_fmt & 1 ? "stereo" : "mono",
|
||||
d->shift);
|
||||
if (new_freq) {
|
||||
struct audsettings as;
|
||||
|
||||
as.freq = new_freq;
|
||||
as.nchannels = 1 << (new_fmt & 1);
|
||||
as.fmt = (new_fmt & 2) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_U8;
|
||||
as.big_endian = false;
|
||||
|
||||
if (i == ADC_CHANNEL) {
|
||||
s->adc_voice =
|
||||
audio_be_open_in(
|
||||
s->audio_be,
|
||||
s->adc_voice,
|
||||
"es1370.adc",
|
||||
s,
|
||||
es1370_adc_callback,
|
||||
&as
|
||||
);
|
||||
} else {
|
||||
s->dac_voice[i] =
|
||||
audio_be_open_out(
|
||||
s->audio_be,
|
||||
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) {
|
||||
audio_be_set_active_in(s->audio_be, s->adc_voice, on);
|
||||
} else {
|
||||
audio_be_set_active_out(s->audio_be, 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;
|
||||
}
|
||||
|
||||
static void es1370_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||||
{
|
||||
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_DAC1_SCOUNT:
|
||||
case ES1370_REG_DAC2_SCOUNT:
|
||||
case ES1370_REG_ADC_SCOUNT:
|
||||
d += (addr - ES1370_REG_DAC1_SCOUNT) >> 2;
|
||||
d->scount = (val & 0xffff) << 16 | (val & 0xffff);
|
||||
trace_es1370_sample_count_wr(d - &s->chan[0],
|
||||
d->scount >> 16, d->scount & 0xffff);
|
||||
break;
|
||||
|
||||
case ES1370_REG_ADC_FRAMEADR:
|
||||
d += 2;
|
||||
goto frameadr;
|
||||
case ES1370_REG_DAC1_FRAMEADR:
|
||||
case ES1370_REG_DAC2_FRAMEADR:
|
||||
d += (addr - ES1370_REG_DAC1_FRAMEADR) >> 3;
|
||||
frameadr:
|
||||
d->frame_addr = val;
|
||||
trace_es1370_frame_address_wr(d - &s->chan[0], d->frame_addr);
|
||||
break;
|
||||
|
||||
case ES1370_REG_PHANTOM_FRAMECNT:
|
||||
lwarn("writing to phantom frame count 0x%" PRIx64, val);
|
||||
break;
|
||||
case ES1370_REG_PHANTOM_FRAMEADR:
|
||||
lwarn("writing to phantom frame address 0x%" PRIx64, val);
|
||||
break;
|
||||
|
||||
case ES1370_REG_ADC_FRAMECNT:
|
||||
d += 2;
|
||||
goto framecnt;
|
||||
case ES1370_REG_DAC1_FRAMECNT:
|
||||
case ES1370_REG_DAC2_FRAMECNT:
|
||||
d += (addr - ES1370_REG_DAC1_FRAMECNT) >> 3;
|
||||
framecnt:
|
||||
d->frame_cnt = val;
|
||||
d->leftover = 0;
|
||||
trace_es1370_frame_count_wr(d - &s->chan[0],
|
||||
d->frame_cnt >> 16, d->frame_cnt & 0xffff);
|
||||
break;
|
||||
|
||||
default:
|
||||
lwarn("writel 0x%" PRIx64 " <- 0x%" PRIx64, addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t es1370_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
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_DAC1_SCOUNT:
|
||||
case ES1370_REG_DAC2_SCOUNT:
|
||||
case ES1370_REG_ADC_SCOUNT:
|
||||
d += (addr - ES1370_REG_DAC1_SCOUNT) >> 2;
|
||||
trace_es1370_sample_count_rd(d - &s->chan[0],
|
||||
d->scount >> 16, d->scount & 0xffff);
|
||||
val = d->scount;
|
||||
break;
|
||||
|
||||
case ES1370_REG_ADC_FRAMECNT:
|
||||
d += 2;
|
||||
goto framecnt;
|
||||
case ES1370_REG_DAC1_FRAMECNT:
|
||||
case ES1370_REG_DAC2_FRAMECNT:
|
||||
d += (addr - ES1370_REG_DAC1_FRAMECNT) >> 3;
|
||||
framecnt:
|
||||
trace_es1370_frame_count_rd(d - &s->chan[0],
|
||||
d->frame_cnt >> 16, d->frame_cnt & 0xffff);
|
||||
val = d->frame_cnt;
|
||||
break;
|
||||
|
||||
case ES1370_REG_ADC_FRAMEADR:
|
||||
d += 2;
|
||||
goto frameadr;
|
||||
case ES1370_REG_DAC1_FRAMEADR:
|
||||
case ES1370_REG_DAC2_FRAMEADR:
|
||||
d += (addr - ES1370_REG_DAC1_FRAMEADR) >> 3;
|
||||
frameadr:
|
||||
trace_es1370_frame_address_rd(d - &s->chan[0], d->frame_addr);
|
||||
val = d->frame_addr;
|
||||
break;
|
||||
|
||||
case ES1370_REG_PHANTOM_FRAMECNT:
|
||||
val = ~0U;
|
||||
lwarn("reading from phantom frame count");
|
||||
break;
|
||||
case ES1370_REG_PHANTOM_FRAMEADR:
|
||||
val = ~0U;
|
||||
lwarn("reading from phantom frame address");
|
||||
break;
|
||||
|
||||
default:
|
||||
val = ~0U;
|
||||
lwarn("readl 0x%" PRIx64 " -> 0x%x", addr, val);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
|
||||
int max, bool *irq)
|
||||
{
|
||||
QEMU_UNINITIALIZED uint8_t tmpbuf[4096];
|
||||
size_t to_transfer;
|
||||
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;
|
||||
if (size < cnt) {
|
||||
return;
|
||||
}
|
||||
int left = ((size - cnt + 1) << 2) + d->leftover;
|
||||
int transferred = 0;
|
||||
int index = d - &s->chan[0];
|
||||
|
||||
to_transfer = MIN(max, MIN(left, csc_bytes));
|
||||
addr += (cnt << 2) + d->leftover;
|
||||
|
||||
if (index == ADC_CHANNEL) {
|
||||
while (to_transfer > 0) {
|
||||
int acquired, to_copy;
|
||||
|
||||
to_copy = MIN(to_transfer, sizeof(tmpbuf));
|
||||
acquired = audio_be_read(s->audio_be, s->adc_voice, tmpbuf, to_copy);
|
||||
if (!acquired) {
|
||||
break;
|
||||
}
|
||||
|
||||
pci_dma_write (&s->dev, addr, tmpbuf, acquired);
|
||||
|
||||
to_transfer -= acquired;
|
||||
addr += acquired;
|
||||
transferred += acquired;
|
||||
}
|
||||
} else {
|
||||
SWVoiceOut *voice = s->dac_voice[index];
|
||||
|
||||
while (to_transfer > 0) {
|
||||
int copied, to_copy;
|
||||
|
||||
to_copy = MIN(to_transfer, sizeof(tmpbuf));
|
||||
pci_dma_read (&s->dev, addr, tmpbuf, to_copy);
|
||||
copied = audio_be_write(s->audio_be, voice, tmpbuf, to_copy);
|
||||
if (!copied) {
|
||||
break;
|
||||
}
|
||||
to_transfer -= copied;
|
||||
addr += copied;
|
||||
transferred += copied;
|
||||
}
|
||||
}
|
||||
|
||||
if (csc_bytes == transferred) {
|
||||
if (*irq) {
|
||||
trace_es1370_lost_interrupt(index);
|
||||
}
|
||||
*irq = true;
|
||||
d->scount = sc | (sc << 16);
|
||||
} else {
|
||||
*irq = false;
|
||||
d->scount = sc | (((csc_bytes - transferred - 1) >> d->shift) << 16);
|
||||
}
|
||||
|
||||
cnt += (transferred + d->leftover) >> 2;
|
||||
|
||||
if (s->sctl & loop_sel) {
|
||||
/*
|
||||
* loop_sel tells us which bit in the SCTL register to look at
|
||||
* (either P1_LOOP_SEL, P2_LOOP_SEL or R1_LOOP_SEL). The sense
|
||||
* of these bits is 0 for loop mode (set interrupt and keep recording
|
||||
* when the sample count reaches zero) or 1 for stop mode (set
|
||||
* interrupt and stop recording).
|
||||
*/
|
||||
warn_report("es1370: non looping mode");
|
||||
} else {
|
||||
d->frame_cnt = size;
|
||||
|
||||
if ((uint32_t) cnt <= d->frame_cnt) {
|
||||
d->frame_cnt |= cnt << 16;
|
||||
}
|
||||
}
|
||||
|
||||
d->leftover = (transferred + d->leftover) & 3;
|
||||
trace_es1370_transfer_audio(index,
|
||||
d->frame_cnt >> 16, d->frame_cnt & 0xffff,
|
||||
d->scount >> 16, d->scount & 0xffff,
|
||||
d->leftover, *irq);
|
||||
}
|
||||
|
||||
static void es1370_run_channel (ES1370State *s, size_t chan, int free_or_avail)
|
||||
{
|
||||
uint32_t new_status = s->status;
|
||||
int max_bytes;
|
||||
bool 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;
|
||||
}
|
||||
|
||||
irq = s->sctl & b->sctl_inten && s->status & b->stat_int;
|
||||
|
||||
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 const MemoryRegionOps es1370_io_ops = {
|
||||
.read = es1370_read,
|
||||
.write = es1370_write,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.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 = (const 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) {
|
||||
audio_be_close_in(s->audio_be, s->adc_voice);
|
||||
s->adc_voice = NULL;
|
||||
}
|
||||
} else {
|
||||
if (s->dac_voice[i]) {
|
||||
audio_be_close_out(s->audio_be, 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 = (const 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(DeviceState *dev)
|
||||
{
|
||||
ES1370State *s = ES1370(dev);
|
||||
|
||||
es1370_reset (s);
|
||||
}
|
||||
|
||||
static void es1370_realize(PCIDevice *dev, Error **errp)
|
||||
{
|
||||
ES1370State *s = ES1370(dev);
|
||||
uint8_t *c = s->dev.config;
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
es1370_reset (s);
|
||||
}
|
||||
|
||||
static void es1370_exit(PCIDevice *dev)
|
||||
{
|
||||
ES1370State *s = ES1370(dev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
audio_be_close_out(s->audio_be, s->dac_voice[i]);
|
||||
}
|
||||
|
||||
audio_be_close_in(s->audio_be, s->adc_voice);
|
||||
}
|
||||
|
||||
static const Property es1370_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(ES1370State, audio_be),
|
||||
};
|
||||
|
||||
static void es1370_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS (klass);
|
||||
PCIDeviceClass *k = PCI_DEVICE_CLASS (klass);
|
||||
|
||||
k->realize = es1370_realize;
|
||||
k->exit = es1370_exit;
|
||||
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;
|
||||
device_class_set_legacy_reset(dc, es1370_on_reset);
|
||||
device_class_set_props(dc, es1370_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo es1370_info = {
|
||||
.name = TYPE_ES1370,
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.instance_size = sizeof (ES1370State),
|
||||
.class_init = es1370_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||||
{ },
|
||||
},
|
||||
};
|
||||
|
||||
static void es1370_register_types (void)
|
||||
{
|
||||
type_register_static (&es1370_info);
|
||||
audio_register_model("es1370", "ENSONIQ AudioPCI ES1370", TYPE_ES1370);
|
||||
}
|
||||
|
||||
type_init (es1370_register_types)
|
||||
+1211
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
#ifndef FMOPL_H
|
||||
#define FMOPL_H
|
||||
|
||||
|
||||
typedef void (*OPL_TIMERHANDLER)(void *param, int channel, double interval_Sec);
|
||||
|
||||
/* !!!!! here is private section , do not access there member direct !!!!! */
|
||||
|
||||
/* Saving is necessary for member of the 'R' mark for suspend/resume */
|
||||
/* ---------- OPL one of slot ---------- */
|
||||
typedef struct fm_opl_slot {
|
||||
int32_t TL; /* total level :TL << 8 */
|
||||
int32_t TLL; /* adjusted now TL */
|
||||
uint8_t KSR; /* key scale rate :(shift down bit) */
|
||||
int32_t *AR; /* attack rate :&AR_TABLE[AR<<2] */
|
||||
int32_t *DR; /* decay rate :&DR_TALBE[DR<<2] */
|
||||
int32_t SL; /* sustin level :SL_TALBE[SL] */
|
||||
int32_t *RR; /* release rate :&DR_TABLE[RR<<2] */
|
||||
uint8_t ksl; /* keyscale level :(shift down bits) */
|
||||
uint8_t ksr; /* key scale rate :kcode>>KSR */
|
||||
uint32_t mul; /* multiple :ML_TABLE[ML] */
|
||||
uint32_t Cnt; /* frequency count : */
|
||||
uint32_t Incr; /* frequency step : */
|
||||
/* envelope generator state */
|
||||
uint8_t eg_typ; /* envelope type flag */
|
||||
uint8_t evm; /* envelope phase */
|
||||
int32_t evc; /* envelope counter */
|
||||
int32_t eve; /* envelope counter end point */
|
||||
int32_t evs; /* envelope counter step */
|
||||
int32_t evsa; /* envelope step for AR :AR[ksr] */
|
||||
int32_t evsd; /* envelope step for DR :DR[ksr] */
|
||||
int32_t evsr; /* envelope step for RR :RR[ksr] */
|
||||
/* LFO */
|
||||
uint8_t ams; /* ams flag */
|
||||
uint8_t vib; /* vibrate flag */
|
||||
/* wave selector */
|
||||
int32_t **wavetable;
|
||||
}OPL_SLOT;
|
||||
|
||||
/* ---------- OPL one of channel ---------- */
|
||||
typedef struct fm_opl_channel {
|
||||
OPL_SLOT SLOT[2];
|
||||
uint8_t CON; /* connection type */
|
||||
uint8_t FB; /* feed back :(shift down bit) */
|
||||
int32_t *connect1; /* slot1 output pointer */
|
||||
int32_t *connect2; /* slot2 output pointer */
|
||||
int32_t op1_out[2]; /* slot1 output for selfeedback */
|
||||
/* phase generator state */
|
||||
uint32_t block_fnum; /* block+fnum : */
|
||||
uint8_t kcode; /* key code : KeyScaleCode */
|
||||
uint32_t fc; /* Freq. Increment base */
|
||||
uint32_t ksl_base; /* KeyScaleLevel Base step */
|
||||
uint8_t keyon; /* key on/off flag */
|
||||
} OPL_CH;
|
||||
|
||||
/* OPL state */
|
||||
typedef struct fm_opl_f {
|
||||
int clock; /* master clock (Hz) */
|
||||
int rate; /* sampling rate (Hz) */
|
||||
double freqbase; /* frequency base */
|
||||
double TimerBase; /* Timer base time (==sampling time) */
|
||||
uint8_t address; /* address register */
|
||||
uint8_t status; /* status flag */
|
||||
uint8_t statusmask; /* status mask */
|
||||
uint32_t mode; /* Reg.08 : CSM , notesel,etc. */
|
||||
/* Timer */
|
||||
int T[2]; /* timer counter */
|
||||
uint8_t st[2]; /* timer enable */
|
||||
/* FM channel slots */
|
||||
OPL_CH *P_CH; /* pointer of CH */
|
||||
int max_ch; /* maximum channel */
|
||||
/* Rhythm section */
|
||||
uint8_t rhythm; /* Rhythm mode , key flag */
|
||||
/* time tables */
|
||||
int32_t AR_TABLE[76]; /* attack rate tables */
|
||||
int32_t DR_TABLE[76]; /* decay rate tables */
|
||||
uint32_t FN_TABLE[1024]; /* fnumber -> increment counter */
|
||||
/* LFO */
|
||||
int32_t *ams_table;
|
||||
int32_t *vib_table;
|
||||
int32_t amsCnt;
|
||||
int32_t amsIncr;
|
||||
int32_t vibCnt;
|
||||
int32_t vibIncr;
|
||||
/* wave selector enable flag */
|
||||
uint8_t wavesel;
|
||||
/* external event callback handler */
|
||||
OPL_TIMERHANDLER TimerHandler; /* TIMER handler */
|
||||
void *TimerParam; /* TIMER parameter */
|
||||
} FM_OPL;
|
||||
|
||||
/* ---------- Generic interface section ---------- */
|
||||
FM_OPL *OPLCreate(int clock, int rate);
|
||||
void OPLDestroy(FM_OPL *OPL);
|
||||
void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,
|
||||
void *param);
|
||||
|
||||
int OPLWrite(FM_OPL *OPL,int a,int v);
|
||||
unsigned char OPLRead(FM_OPL *OPL,int a);
|
||||
int OPLTimerOver(FM_OPL *OPL,int c);
|
||||
|
||||
void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length);
|
||||
#endif
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "hw/isa/isa.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "gusemu.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define DEBUG 0
|
||||
|
||||
#define ldebug(fmt, ...) do { \
|
||||
if (DEBUG) { \
|
||||
error_report("gus: " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPE_GUS "gus"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(GUSState, GUS)
|
||||
|
||||
struct GUSState {
|
||||
ISADevice dev;
|
||||
GUSEmuState emu;
|
||||
AudioBackend *audio_be;
|
||||
uint32_t freq;
|
||||
uint32_t port;
|
||||
int pos, left, shift, irqs;
|
||||
int16_t *mixbuf;
|
||||
uint8_t himem[1024 * 1024 + 32 + 4096];
|
||||
int samples;
|
||||
SWVoiceOut *voice;
|
||||
int64_t last_ticks;
|
||||
qemu_irq pic;
|
||||
IsaDma *isa_dma;
|
||||
PortioList portio_list1;
|
||||
PortioList portio_list2;
|
||||
};
|
||||
|
||||
static uint32_t gus_readb(void *opaque, uint32_t nport)
|
||||
{
|
||||
GUSState *s = opaque;
|
||||
|
||||
return gus_read (&s->emu, nport, 1);
|
||||
}
|
||||
|
||||
static void gus_writeb(void *opaque, uint32_t nport, uint32_t val)
|
||||
{
|
||||
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 = audio_be_write(
|
||||
s->audio_be,
|
||||
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 = 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 = 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, (uint64_t)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", hwirq, n, s->irqs);
|
||||
return n;
|
||||
}
|
||||
|
||||
void GUS_irqclear (GUSEmuState *emu, int hwirq)
|
||||
{
|
||||
GUSState *s = emu->opaque;
|
||||
ldebug("irqclear %d %d", 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 *emu)
|
||||
{
|
||||
GUSState *s = emu->opaque;
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
ldebug("dma request %d", s->emu.gusdma);
|
||||
k->hold_DREQ(s->isa_dma, s->emu.gusdma);
|
||||
}
|
||||
|
||||
static int GUS_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len)
|
||||
{
|
||||
GUSState *s = opaque;
|
||||
IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
QEMU_UNINITIALIZED char tmpbuf[4096];
|
||||
int pos = dma_pos, mode, left = dma_len - dma_pos;
|
||||
|
||||
ldebug("read DMA 0x%x %d", dma_pos, dma_len);
|
||||
mode = k->has_autoinitialization(s->isa_dma, s->emu.gusdma);
|
||||
while (left) {
|
||||
int to_copy = MIN ((size_t) left, sizeof (tmpbuf));
|
||||
int copied;
|
||||
|
||||
ldebug("left=%d to_copy=%d pos=%d", left, to_copy, pos);
|
||||
copied = k->read_memory(s->isa_dma, nchan, tmpbuf, pos, to_copy);
|
||||
gus_dma_transferdata (&s->emu, tmpbuf, copied, left == copied);
|
||||
left -= copied;
|
||||
pos += copied;
|
||||
}
|
||||
|
||||
if (((mode >> 4) & 1) == 0) {
|
||||
k->release_DREQ(s->isa_dma, s->emu.gusdma);
|
||||
}
|
||||
return dma_len;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_gus = {
|
||||
.name = "gus",
|
||||
.version_id = 2,
|
||||
.minimum_version_id = 2,
|
||||
.fields = (const 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);
|
||||
ISABus *bus = isa_bus_from_device(d);
|
||||
GUSState *s = GUS (dev);
|
||||
IsaDmaClass *k;
|
||||
struct audsettings as;
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s->isa_dma = isa_bus_get_dma(bus, s->emu.gusdma);
|
||||
if (!s->isa_dma) {
|
||||
error_setg(errp, "ISA controller does not support DMA");
|
||||
return;
|
||||
}
|
||||
|
||||
as.freq = s->freq;
|
||||
as.nchannels = 2;
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
as.big_endian = HOST_BIG_ENDIAN;
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
NULL,
|
||||
"gus",
|
||||
s,
|
||||
GUS_callback,
|
||||
&as
|
||||
);
|
||||
|
||||
if (!s->voice) {
|
||||
error_setg(errp, "No voice");
|
||||
return;
|
||||
}
|
||||
|
||||
s->shift = 2;
|
||||
s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
|
||||
s->mixbuf = g_malloc0 (s->samples << s->shift);
|
||||
|
||||
isa_register_portio_list(d, &s->portio_list1, s->port,
|
||||
gus_portio_list1, s, "gus");
|
||||
isa_register_portio_list(d, &s->portio_list2, (s->port + 0x100) & 0xf00,
|
||||
gus_portio_list2, s, "gus");
|
||||
|
||||
k = ISADMA_GET_CLASS(s->isa_dma);
|
||||
k->register_channel(s->isa_dma, 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;
|
||||
s->pic = isa_bus_get_irq(bus, s->emu.gusirq);
|
||||
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
}
|
||||
|
||||
static const Property gus_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(GUSState, audio_be),
|
||||
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),
|
||||
};
|
||||
|
||||
static void gus_class_initfn(ObjectClass *klass, const 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;
|
||||
device_class_set_props(dc, 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);
|
||||
audio_register_model("gus", "Gravis Ultrasound GF1", TYPE_GUS);
|
||||
}
|
||||
|
||||
type_init (gus_register_types)
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
typedef struct _GUSEmuState
|
||||
{
|
||||
uint8_t *himemaddr; /* 1024*1024 bytes used for storing uploaded samples (+32 additional bytes for read padding) */
|
||||
uint8_t *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, int16_t *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 */
|
||||
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "gustate.h"
|
||||
#include "gusemu.h"
|
||||
|
||||
#define GUSregb(position) (* (gusptr+(position)))
|
||||
#define GUSregw(position) (*(uint16_t *) (gusptr+(position)))
|
||||
#define GUSregd(position) (*(uint32_t *)(gusptr + (position)))
|
||||
|
||||
/* size given in bytes */
|
||||
unsigned int gus_read(GUSEmuState * state, int port, int size)
|
||||
{
|
||||
int value_read = 0;
|
||||
|
||||
uint8_t *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 (uint8_t) 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 + Function*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 (uint16_t) value_read;
|
||||
/* case 0x306: */ /* Mixer/Version info */
|
||||
/* return 0xff; */ /* Pre 3.6 boards, ICS mixer NOT present */
|
||||
case 0x307: /* DRAMaccess */
|
||||
{
|
||||
uint8_t *adr;
|
||||
adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff);
|
||||
return *adr;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
void gus_write(GUSEmuState * state, int port, int size, unsigned int data)
|
||||
{
|
||||
uint8_t *gusptr;
|
||||
gusptr = state->gusdatapos;
|
||||
GUSregd(portaccesses)++;
|
||||
|
||||
switch (port & 0xff0f)
|
||||
{
|
||||
case 0x200: /* MixerCtrlReg */
|
||||
GUSregb(MixerCtrlReg2x0) = (uint8_t) 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) = (uint8_t) 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) = (uint8_t) data;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUSregb(AdLibData2x9) = (uint8_t) data;
|
||||
if (GUSregb(GUS45TimerCtrl) & 0x02)
|
||||
{
|
||||
GUSregb(TimerStatus2x8) |= 0x01;
|
||||
GUSregb(IRQStatReg2x6) = 0x10;
|
||||
GUS_irqrequest(state, state->gusirq, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x20A:
|
||||
GUSregb(AdLibStatus2x8) = (uint8_t) data;
|
||||
break; /* AdLibStatus2x8 */
|
||||
case 0x20B: /* GUS hidden registers */
|
||||
switch (GUSregb(RegCtrl_2xF) & 0x7)
|
||||
{
|
||||
case 0:
|
||||
if (GUSregb(MixerCtrlReg2x0) & 0x40)
|
||||
GUSregb(IRQ_2xB) = (uint8_t) data; /* control register select bit */
|
||||
else
|
||||
GUSregb(DMA_2xB) = (uint8_t) 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) = (uint8_t) 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);
|
||||
}
|
||||
/* fall through */
|
||||
case 0x20D: /* SB2xCd no IRQ */
|
||||
GUSregb(SB2xCd) = (uint8_t) data;
|
||||
break;
|
||||
case 0x20E: /* SB2xE */
|
||||
GUSregb(SB2xE) = (uint8_t) data;
|
||||
break;
|
||||
case 0x20F:
|
||||
GUSregb(RegCtrl_2xF) = (uint8_t) data;
|
||||
break; /* CtrlReg2xF */
|
||||
case 0x302: /* VoiceSelReg */
|
||||
GUSregb(VoiceSelReg3x2) = (uint8_t) data;
|
||||
break;
|
||||
case 0x303: /* FunkSelReg */
|
||||
GUSregb(FunkSelReg3x3) = (uint8_t) data;
|
||||
if ((uint8_t) 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:
|
||||
{
|
||||
uint16_t writedata = (uint16_t) data;
|
||||
uint16_t readmask = 0x0000;
|
||||
if (size == 1)
|
||||
{
|
||||
readmask = 0xff00;
|
||||
writedata &= 0xff;
|
||||
if ((port & 0xff0f) == 0x305)
|
||||
{
|
||||
writedata = (uint16_t) (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 + Function*2 */
|
||||
GUSregw(offset) = (uint16_t) ((GUSregw(offset) & readmask) | writedata);
|
||||
}
|
||||
break;
|
||||
/* voice unspecific functions */
|
||||
case 0x0e: /* NumVoices */
|
||||
GUSregb(NumVoices) = (uint8_t) data;
|
||||
break;
|
||||
/* case 0x0f: */ /* read only */
|
||||
/* common functions */
|
||||
case 0x41: /* DramDMAContrReg */
|
||||
GUSregb(GUS41DMACtrl) = (uint8_t) 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) = (uint8_t) 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) = (uint8_t) data;
|
||||
break;
|
||||
case 0x47: /* Counter2 */
|
||||
GUSregb(GUS47Counter2) = (uint8_t) data;
|
||||
break;
|
||||
/* case 0x48: */ /* sampling freq reg not emulated (same as interwave) */
|
||||
case 0x49: /* SampCtrlReg */
|
||||
GUSregb(GUS49SampCtrl) = (uint8_t) data;
|
||||
break;
|
||||
/* case 0x4b: */ /* joystick trim not emulated */
|
||||
case 0x4c: /* GUSreset */
|
||||
GUSregb(GUS4cReset) = (uint8_t) 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 */
|
||||
{
|
||||
uint8_t *adr;
|
||||
adr = state->himemaddr + (GUSregd(GUSDRAMPOS24bit) & 0xfffff);
|
||||
*adr = (uint8_t) 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;
|
||||
uint8_t *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) += (uint16_t) (count >> 4); /* ToDo: add 16bit GUS page limit? */
|
||||
GUSregb(GUS50DMAHigh) = (uint8_t) ((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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "gusemu.h"
|
||||
#include "gustate.h"
|
||||
|
||||
#define GUSregb(position) (* (gusptr+(position)))
|
||||
#define GUSregw(position) (*(uint16_t *) (gusptr+(position)))
|
||||
#define GUSregd(position) (*(uint32_t *)(gusptr + (position)))
|
||||
|
||||
#define GUSvoice(position) (*(uint16_t *)(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,
|
||||
int16_t *bufferpos)
|
||||
{
|
||||
/* note that byte registers are stored in the upper half of each voice register! */
|
||||
uint8_t *gusptr;
|
||||
int Voice;
|
||||
uint16_t *voiceptr;
|
||||
|
||||
unsigned int count;
|
||||
for (count = 0; count < numsamples * 2; count++)
|
||||
*(bufferpos + count) = 0; /* clear */
|
||||
|
||||
gusptr = state->gusdatapos;
|
||||
voiceptr = (uint16_t *) 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);
|
||||
int8_t *adr;
|
||||
adr = (int8_t *) state->himemaddr + offset;
|
||||
sample1 = (*adr & 0xff) + (*(adr + 1) * 256);
|
||||
sample2 = (*(adr + 2) & 0xff) + (*(adr + 2 + 1) * 256);
|
||||
}
|
||||
else /* 8bit */
|
||||
{
|
||||
int offset = (CurrPos >> 9) & 0xfffff;
|
||||
int8_t *adr;
|
||||
adr = (int8_t *) 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) += (int16_t) ((sample1 * PanningPos) >> 4); /* right */
|
||||
*(bufferpos + 2 * sample + 1) += (int16_t) ((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;
|
||||
uint8_t *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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Common code to disable/enable mixer emulation at run time
|
||||
*
|
||||
* Copyright (C) 2013 Red Hat, Inc.
|
||||
*
|
||||
* Written by Bandan Das <[email protected]>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -0,0 +1,916 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Red Hat, Inc.
|
||||
*
|
||||
* written by Gerd Hoffmann <[email protected]>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "intel-hda.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/host-utils.h"
|
||||
#include "qemu/module.h"
|
||||
#include "intel-hda-defs.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "trace.h"
|
||||
#include "qom/object.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 = AUDIO_FORMAT_S8; break;
|
||||
case AC_FMT_BITS_16: as->fmt = AUDIO_FORMAT_S16; break;
|
||||
case AC_FMT_BITS_32: as->fmt = AUDIO_FORMAT_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"
|
||||
|
||||
#define HDA_TIMER_TICKS (SCALE_MS)
|
||||
#define B_SIZE sizeof(st->buf)
|
||||
#define B_MASK (sizeof(st->buf) - 1)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static const char *fmt2name[] = {
|
||||
[ AUDIO_FORMAT_U8 ] = "PCM-U8",
|
||||
[ AUDIO_FORMAT_S8 ] = "PCM-S8",
|
||||
[ AUDIO_FORMAT_U16 ] = "PCM-U16",
|
||||
[ AUDIO_FORMAT_S16 ] = "PCM-S16",
|
||||
[ AUDIO_FORMAT_U32 ] = "PCM-U32",
|
||||
[ AUDIO_FORMAT_S32 ] = "PCM-S32",
|
||||
};
|
||||
|
||||
#define TYPE_HDA_AUDIO "hda-audio"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(HDAAudioState, HDA_AUDIO)
|
||||
|
||||
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 compat_buf[HDA_BUFFER_SIZE];
|
||||
uint32_t compat_bpos;
|
||||
uint8_t buf[8192]; /* size must be power of two */
|
||||
int64_t rpos;
|
||||
int64_t wpos;
|
||||
QEMUTimer *buft;
|
||||
int64_t buft_start;
|
||||
};
|
||||
|
||||
struct HDAAudioState {
|
||||
HDACodecDevice hda;
|
||||
const char *name;
|
||||
|
||||
AudioBackend *audio_be;
|
||||
const desc_codec *desc;
|
||||
HDAAudioStream st[4];
|
||||
bool running_compat[16];
|
||||
bool running_real[2 * 16];
|
||||
|
||||
/* properties */
|
||||
uint32_t debug;
|
||||
bool mixer;
|
||||
};
|
||||
|
||||
static inline uint32_t hda_bytes_per_second(HDAAudioStream *st)
|
||||
{
|
||||
return 2 * (uint32_t)st->as.nchannels * (uint32_t)st->as.freq;
|
||||
}
|
||||
|
||||
static inline void hda_timer_sync_adjust(HDAAudioStream *st, int64_t target_pos)
|
||||
{
|
||||
int64_t limit = B_SIZE / 8;
|
||||
int64_t corr = 0;
|
||||
|
||||
if (target_pos > limit) {
|
||||
corr = HDA_TIMER_TICKS;
|
||||
}
|
||||
if (target_pos < -limit) {
|
||||
corr = -HDA_TIMER_TICKS;
|
||||
}
|
||||
if (target_pos < -(2 * limit)) {
|
||||
corr = -(4 * HDA_TIMER_TICKS);
|
||||
}
|
||||
if (corr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_hda_audio_adjust(st->node->name, target_pos);
|
||||
st->buft_start += corr;
|
||||
}
|
||||
|
||||
static void hda_audio_input_timer(void *opaque)
|
||||
{
|
||||
HDAAudioStream *st = opaque;
|
||||
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
|
||||
int64_t uptime = now - st->buft_start;
|
||||
int64_t wpos = st->wpos;
|
||||
int64_t rpos = st->rpos;
|
||||
int64_t wanted_rpos;
|
||||
|
||||
if (uptime <= 0) {
|
||||
/* wanted_rpos <= 0 */
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
wanted_rpos = muldiv64(uptime, hda_bytes_per_second(st),
|
||||
NANOSECONDS_PER_SECOND);
|
||||
wanted_rpos &= -4; /* IMPORTANT! clip to frames */
|
||||
|
||||
if (wanted_rpos <= rpos) {
|
||||
/* we already transmitted the data */
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
int64_t to_transfer = MIN(wpos - rpos, wanted_rpos - rpos);
|
||||
while (to_transfer) {
|
||||
uint32_t start = (rpos & B_MASK);
|
||||
uint32_t chunk = MIN(B_SIZE - start, to_transfer);
|
||||
int rc = hda_codec_xfer(
|
||||
&st->state->hda, st->stream, false, st->buf + start, chunk);
|
||||
if (!rc) {
|
||||
break;
|
||||
}
|
||||
rpos += chunk;
|
||||
to_transfer -= chunk;
|
||||
st->rpos += chunk;
|
||||
}
|
||||
|
||||
out_timer:
|
||||
|
||||
if (st->running) {
|
||||
timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
|
||||
}
|
||||
}
|
||||
|
||||
static void hda_audio_input_cb(void *opaque, int avail)
|
||||
{
|
||||
HDAAudioStream *st = opaque;
|
||||
|
||||
int64_t wpos = st->wpos;
|
||||
int64_t rpos = st->rpos;
|
||||
|
||||
int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), avail);
|
||||
|
||||
while (to_transfer) {
|
||||
uint32_t start = (uint32_t) (wpos & B_MASK);
|
||||
uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
|
||||
uint32_t read = audio_be_read(st->state->audio_be, st->voice.in,
|
||||
st->buf + start, chunk);
|
||||
wpos += read;
|
||||
to_transfer -= read;
|
||||
st->wpos += read;
|
||||
if (chunk != read) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hda_timer_sync_adjust(st, -((wpos - rpos) - (B_SIZE >> 1)));
|
||||
}
|
||||
|
||||
static void hda_audio_output_timer(void *opaque)
|
||||
{
|
||||
HDAAudioStream *st = opaque;
|
||||
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
|
||||
int64_t uptime = now - st->buft_start;
|
||||
int64_t wpos = st->wpos;
|
||||
int64_t rpos = st->rpos;
|
||||
int64_t wanted_wpos;
|
||||
|
||||
if (uptime <= 0) {
|
||||
/* wanted_wpos <= 0 */
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
wanted_wpos = muldiv64(uptime, hda_bytes_per_second(st),
|
||||
NANOSECONDS_PER_SECOND);
|
||||
wanted_wpos &= -4; /* IMPORTANT! clip to frames */
|
||||
|
||||
if (wanted_wpos <= wpos) {
|
||||
/* we already received the data */
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), wanted_wpos - wpos);
|
||||
while (to_transfer) {
|
||||
uint32_t start = (wpos & B_MASK);
|
||||
uint32_t chunk = MIN(B_SIZE - start, to_transfer);
|
||||
int rc = hda_codec_xfer(
|
||||
&st->state->hda, st->stream, true, st->buf + start, chunk);
|
||||
if (!rc) {
|
||||
break;
|
||||
}
|
||||
wpos += chunk;
|
||||
to_transfer -= chunk;
|
||||
st->wpos += chunk;
|
||||
}
|
||||
|
||||
out_timer:
|
||||
|
||||
if (st->running) {
|
||||
timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
|
||||
}
|
||||
}
|
||||
|
||||
static void hda_audio_output_cb(void *opaque, int avail)
|
||||
{
|
||||
HDAAudioStream *st = opaque;
|
||||
|
||||
int64_t wpos = st->wpos;
|
||||
int64_t rpos = st->rpos;
|
||||
|
||||
int64_t to_transfer = MIN(wpos - rpos, avail);
|
||||
|
||||
if (wpos - rpos == B_SIZE) {
|
||||
/* drop buffer, reset timer adjust */
|
||||
st->rpos = 0;
|
||||
st->wpos = 0;
|
||||
st->buft_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
trace_hda_audio_overrun(st->node->name);
|
||||
return;
|
||||
}
|
||||
|
||||
while (to_transfer) {
|
||||
uint32_t start = (uint32_t) (rpos & B_MASK);
|
||||
uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
|
||||
uint32_t written = audio_be_write(st->state->audio_be, st->voice.out,
|
||||
st->buf + start, chunk);
|
||||
rpos += written;
|
||||
to_transfer -= written;
|
||||
st->rpos += written;
|
||||
if (chunk != written) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1));
|
||||
}
|
||||
|
||||
static void hda_audio_set_running(HDAAudioStream *st, bool running)
|
||||
{
|
||||
if (st->node == NULL) {
|
||||
return;
|
||||
}
|
||||
if (st->running == running) {
|
||||
return;
|
||||
}
|
||||
st->running = running;
|
||||
trace_hda_audio_running(st->node->name, st->stream, st->running);
|
||||
if (running) {
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
st->rpos = 0;
|
||||
st->wpos = 0;
|
||||
st->buft_start = now;
|
||||
timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS);
|
||||
} else {
|
||||
timer_del(st->buft);
|
||||
}
|
||||
if (st->output) {
|
||||
audio_be_set_active_out(st->state->audio_be, st->voice.out, st->running);
|
||||
} else {
|
||||
audio_be_set_active_in(st->state->audio_be, 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) {
|
||||
audio_be_set_volume_out_lr(st->state->audio_be, st->voice.out,
|
||||
muted, left, right);
|
||||
} else {
|
||||
audio_be_set_volume_in_lr(st->state->audio_be, st->voice.in,
|
||||
muted, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
static void hda_audio_setup(HDAAudioStream *st)
|
||||
{
|
||||
audio_callback_fn cb;
|
||||
|
||||
if (st->node == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_hda_audio_format(st->node->name, st->as.nchannels,
|
||||
fmt2name[st->as.fmt], st->as.freq);
|
||||
|
||||
if (st->output) {
|
||||
cb = hda_audio_output_cb;
|
||||
timer_del(st->buft);
|
||||
st->voice.out = audio_be_open_out(st->state->audio_be, st->voice.out,
|
||||
st->node->name, st, cb, &st->as);
|
||||
} else {
|
||||
cb = hda_audio_input_cb;
|
||||
timer_del(st->buft);
|
||||
st->voice.in = audio_be_open_in(st->state->audio_be, st->voice.in,
|
||||
st->node->name, st, 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",
|
||||
__func__, 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",
|
||||
__func__, 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 void hda_audio_init(HDACodecDevice *hda,
|
||||
const struct desc_codec *desc,
|
||||
Error **errp)
|
||||
{
|
||||
HDAAudioState *a = HDA_AUDIO(hda);
|
||||
HDAAudioStream *st;
|
||||
const desc_node *node;
|
||||
const desc_param *param;
|
||||
uint32_t i, type;
|
||||
|
||||
if (!audio_be_check(&a->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
a->desc = desc;
|
||||
a->name = object_get_typename(OBJECT(a));
|
||||
dprint(a, 1, "%s: cad %d\n", __func__, a->hda.cad);
|
||||
|
||||
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->compat_bpos = sizeof(st->compat_buf);
|
||||
st->output = true;
|
||||
st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
||||
hda_audio_output_timer, st);
|
||||
} else {
|
||||
st->output = false;
|
||||
st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
||||
hda_audio_input_timer, st);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hda_audio_exit(HDACodecDevice *hda)
|
||||
{
|
||||
HDAAudioState *a = HDA_AUDIO(hda);
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
timer_free(st->buft);
|
||||
if (st->output) {
|
||||
audio_be_close_out(a->audio_be, st->voice.out);
|
||||
} else {
|
||||
audio_be_close_in(a->audio_be, st->voice.in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int hda_audio_post_load(void *opaque, int version)
|
||||
{
|
||||
HDAAudioState *a = opaque;
|
||||
HDAAudioStream *st;
|
||||
int i;
|
||||
|
||||
dprint(a, 1, "%s\n", __func__);
|
||||
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 bool vmstate_hda_audio_stream_buf_needed(void *opaque)
|
||||
{
|
||||
HDAAudioStream *st = opaque;
|
||||
return st->state;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_hda_audio_stream_buf = {
|
||||
.name = "hda-audio-stream/buffer",
|
||||
.version_id = 1,
|
||||
.needed = vmstate_hda_audio_stream_buf_needed,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_BUFFER(buf, HDAAudioStream),
|
||||
VMSTATE_INT64(rpos, HDAAudioStream),
|
||||
VMSTATE_INT64(wpos, HDAAudioStream),
|
||||
VMSTATE_TIMER_PTR(buft, HDAAudioStream),
|
||||
VMSTATE_INT64(buft_start, HDAAudioStream),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_hda_audio_stream = {
|
||||
.name = "hda-audio-stream",
|
||||
.version_id = 1,
|
||||
.fields = (const 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(compat_bpos, HDAAudioStream),
|
||||
VMSTATE_BUFFER(compat_buf, HDAAudioStream),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
.subsections = (const VMStateDescription * const []) {
|
||||
&vmstate_hda_audio_stream_buf,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_hda_audio = {
|
||||
.name = "hda-audio",
|
||||
.version_id = 2,
|
||||
.post_load = hda_audio_post_load,
|
||||
.fields = (const 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 const Property hda_audio_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(HDAAudioState, audio_be),
|
||||
DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0),
|
||||
DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true),
|
||||
};
|
||||
|
||||
static void hda_audio_init_output(HDACodecDevice *hda, Error **errp)
|
||||
{
|
||||
HDAAudioState *a = HDA_AUDIO(hda);
|
||||
const struct desc_codec *desc = &output_mixemu;
|
||||
|
||||
if (!a->mixer) {
|
||||
desc = &output_nomixemu;
|
||||
}
|
||||
|
||||
hda_audio_init(hda, desc, errp);
|
||||
}
|
||||
|
||||
static void hda_audio_init_duplex(HDACodecDevice *hda, Error **errp)
|
||||
{
|
||||
HDAAudioState *a = HDA_AUDIO(hda);
|
||||
const struct desc_codec *desc = &duplex_mixemu;
|
||||
|
||||
if (!a->mixer) {
|
||||
desc = &duplex_nomixemu;
|
||||
}
|
||||
|
||||
hda_audio_init(hda, desc, errp);
|
||||
}
|
||||
|
||||
static void hda_audio_init_micro(HDACodecDevice *hda, Error **errp)
|
||||
{
|
||||
HDAAudioState *a = HDA_AUDIO(hda);
|
||||
const struct desc_codec *desc = µ_mixemu;
|
||||
|
||||
if (!a->mixer) {
|
||||
desc = µ_nomixemu;
|
||||
}
|
||||
|
||||
hda_audio_init(hda, desc, errp);
|
||||
}
|
||||
|
||||
static void hda_audio_base_class_init(ObjectClass *klass, const 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);
|
||||
device_class_set_legacy_reset(dc, hda_audio_reset);
|
||||
dc->vmsd = &vmstate_hda_audio;
|
||||
device_class_set_props(dc, hda_audio_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo hda_audio_info = {
|
||||
.name = TYPE_HDA_AUDIO,
|
||||
.parent = TYPE_HDA_CODEC_DEVICE,
|
||||
.instance_size = sizeof(HDAAudioState),
|
||||
.class_init = hda_audio_base_class_init,
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void hda_audio_output_class_init(ObjectClass *klass, const 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,
|
||||
.class_init = hda_audio_output_class_init,
|
||||
};
|
||||
|
||||
static void hda_audio_duplex_class_init(ObjectClass *klass, const 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,
|
||||
.class_init = hda_audio_duplex_class_init,
|
||||
};
|
||||
|
||||
static void hda_audio_micro_class_init(ObjectClass *klass, const 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,
|
||||
.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)
|
||||
@@ -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 capabilities */
|
||||
#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 capabilities */
|
||||
#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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
#ifndef HW_INTEL_HDA_H
|
||||
#define HW_INTEL_HDA_H
|
||||
|
||||
#include "hw/core/qdev.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* hda bus */
|
||||
|
||||
#define TYPE_HDA_CODEC_DEVICE "hda-codec"
|
||||
OBJECT_DECLARE_TYPE(HDACodecDevice, HDACodecDeviceClass,
|
||||
HDA_CODEC_DEVICE)
|
||||
|
||||
#define TYPE_HDA_BUS "HDA"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(HDACodecBus, HDA_BUS)
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct HDACodecDeviceClass {
|
||||
DeviceClass parent_class;
|
||||
|
||||
void (*init)(HDACodecDevice *dev, Error **errp);
|
||||
void (*exit)(HDACodecDevice *dev);
|
||||
void (*command)(HDACodecDevice *dev, uint32_t nid, uint32_t data);
|
||||
void (*stream)(HDACodecDevice *dev, uint32_t stnr, bool running, bool output);
|
||||
};
|
||||
|
||||
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
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/core/hw-error.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "lm4549.h"
|
||||
#include "migration/vmstate.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)
|
||||
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 */
|
||||
audio_be_set_active_out(s->audio_be, s->voice, 1);
|
||||
s->voice_is_active = 1;
|
||||
|
||||
/* Try to write the buffer content */
|
||||
written_bytes = audio_be_write(s->audio_be, 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 = audio_be_get_buffer_size_out(s->audio_be, 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) {
|
||||
audio_be_set_active_out(s->audio_be, 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:
|
||||
DPRINTF("DAC rate change = %i\n", value);
|
||||
|
||||
/*
|
||||
* Valid sample rates are 4kHz to 48kHz.
|
||||
* The datasheet doesn't say what happens if you try to
|
||||
* set the frequency to zero. AUD_open_out() will print
|
||||
* a bug message if we pass it a zero frequency, so just
|
||||
* ignore attempts to set the DAC frequency to zero.
|
||||
*/
|
||||
if (value < 4000 || value > 48000) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: DAC sample rate %d Hz is invalid, ignoring it\n",
|
||||
__func__, value);
|
||||
break;
|
||||
}
|
||||
regfile[LM4549_PCM_Front_DAC_Rate] = value;
|
||||
|
||||
/* Re-open a voice with the new sample rate */
|
||||
struct audsettings as;
|
||||
as.freq = value;
|
||||
as.nchannels = 2;
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
as.big_endian = false;
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
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 = AUDIO_FORMAT_S16;
|
||||
as.big_endian = false;
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
s->voice,
|
||||
"lm4549.out",
|
||||
s,
|
||||
lm4549_audio_out_callback,
|
||||
&as
|
||||
);
|
||||
|
||||
/* Request data */
|
||||
if (s->voice_is_active == 1) {
|
||||
lm4549_audio_out_callback(s, audio_be_get_buffer_size_out(s->audio_be, s->voice));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque,
|
||||
Error **errp)
|
||||
{
|
||||
struct audsettings as;
|
||||
|
||||
/* Register an audio card */
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store the callback and opaque pointer */
|
||||
s->data_req_cb = data_req_cb;
|
||||
s->opaque = opaque;
|
||||
|
||||
/* Init the registers */
|
||||
lm4549_reset(s);
|
||||
|
||||
/* Open a default voice */
|
||||
as.freq = 48000;
|
||||
as.nchannels = 2;
|
||||
as.fmt = AUDIO_FORMAT_S16;
|
||||
as.big_endian = false;
|
||||
|
||||
s->voice = audio_be_open_out(
|
||||
s->audio_be,
|
||||
s->voice,
|
||||
"lm4549.out",
|
||||
s,
|
||||
lm4549_audio_out_callback,
|
||||
&as
|
||||
);
|
||||
|
||||
audio_be_set_volume_out_lr(s->audio_be, 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 = (const 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()
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 "qemu/audio.h"
|
||||
#include "exec/hwaddr.h"
|
||||
|
||||
typedef void (*lm4549_callback)(void *opaque);
|
||||
|
||||
#define LM4549_BUFFER_SIZE (512 * 2) /* 512 16-bit stereo samples */
|
||||
|
||||
|
||||
typedef struct {
|
||||
AudioBackend *audio_be;
|
||||
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,
|
||||
Error **errp);
|
||||
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 /* HW_LM4549_H */
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/audio/wm8750.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qom/object.h"
|
||||
#include "system/physmem.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)
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(mv88w8618_audio_state, MV88W8618_AUDIO)
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
|
||||
{
|
||||
mv88w8618_audio_state *s = opaque;
|
||||
int16_t *codec_buffer;
|
||||
QEMU_UNINITIALIZED 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;
|
||||
}
|
||||
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 void mv88w8618_audio_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
|
||||
mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
|
||||
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &mv88w8618_audio_ops, s,
|
||||
"audio", MP_AUDIO_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
|
||||
object_property_add_link(OBJECT(dev), "wm8750", TYPE_WM8750,
|
||||
(Object **) &s->wm,
|
||||
qdev_prop_allow_set_link_before_realize,
|
||||
0);
|
||||
}
|
||||
|
||||
static void mv88w8618_audio_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
|
||||
|
||||
wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
|
||||
}
|
||||
|
||||
static const VMStateDescription mv88w8618_audio_vmsd = {
|
||||
.name = "mv88w8618_audio",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const 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 void mv88w8618_audio_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = mv88w8618_audio_realize;
|
||||
device_class_set_legacy_reset(dc, mv88w8618_audio_reset);
|
||||
dc->vmsd = &mv88w8618_audio_vmsd;
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
static const TypeInfo mv88w8618_audio_info = {
|
||||
.name = TYPE_MV88W8618_AUDIO,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(mv88w8618_audio_state),
|
||||
.instance_init = mv88w8618_audio_init,
|
||||
.class_init = mv88w8618_audio_class_init,
|
||||
};
|
||||
|
||||
static void mv88w8618_register_types(void)
|
||||
{
|
||||
type_register_static(&mv88w8618_audio_info);
|
||||
}
|
||||
|
||||
type_init(mv88w8618_register_types)
|
||||
@@ -0,0 +1,17 @@
|
||||
system_ss.add(files('model.c'))
|
||||
system_ss.add(when: 'CONFIG_AC97', if_true: files('ac97.c'))
|
||||
system_ss.add(when: 'CONFIG_ADLIB', if_true: files('fmopl.c', 'adlib.c'))
|
||||
system_ss.add(when: 'CONFIG_ASC', if_true: files('asc.c'))
|
||||
system_ss.add(when: 'CONFIG_CS4231', if_true: files('cs4231.c'))
|
||||
system_ss.add(when: 'CONFIG_CS4231A', if_true: files('cs4231a.c'))
|
||||
system_ss.add(when: 'CONFIG_ES1370', if_true: files('es1370.c'))
|
||||
system_ss.add(when: 'CONFIG_GUS', if_true: files('gus.c', 'gusemu_hal.c', 'gusemu_mixer.c'))
|
||||
system_ss.add(when: 'CONFIG_HDA', if_true: files('intel-hda.c', 'hda-codec.c'))
|
||||
system_ss.add(when: 'CONFIG_MARVELL_88W8618', if_true: files('marvell_88w8618.c'))
|
||||
system_ss.add(when: 'CONFIG_PCSPK', if_true: files('pcspk.c'))
|
||||
system_ss.add(when: 'CONFIG_PL041', if_true: files('pl041.c', 'lm4549.c'))
|
||||
system_ss.add(when: 'CONFIG_SB16', if_true: files('sb16.c'))
|
||||
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('via-ac97.c'))
|
||||
system_ss.add(when: 'CONFIG_WM8750', if_true: files('wm8750.c'))
|
||||
system_ss.add(when: ['CONFIG_VIRTIO_SND', 'CONFIG_VIRTIO'], if_true: files('virtio-snd.c'))
|
||||
system_ss.add(when: ['CONFIG_VIRTIO_SND', 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI'], if_true: files('virtio-snd-pci.c'))
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 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 "qemu/osdep.h"
|
||||
#include "hw/core/qdev.h"
|
||||
#include "monitor/qdev.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/audio/model.h"
|
||||
|
||||
struct audio_model {
|
||||
const char *name;
|
||||
const char *descr;
|
||||
const char *typename;
|
||||
void (*init)(const char *audiodev);
|
||||
};
|
||||
|
||||
static struct audio_model audio_models[9];
|
||||
static int audio_models_count;
|
||||
|
||||
void audio_register_model_with_cb(const char *name, const char *descr,
|
||||
void (*init_audio_model)(const char *audiodev))
|
||||
{
|
||||
assert(audio_models_count < ARRAY_SIZE(audio_models) - 1);
|
||||
audio_models[audio_models_count].name = name;
|
||||
audio_models[audio_models_count].descr = descr;
|
||||
audio_models[audio_models_count].init = init_audio_model;
|
||||
audio_models_count++;
|
||||
}
|
||||
|
||||
void audio_register_model(const char *name, const char *descr,
|
||||
const char *typename)
|
||||
{
|
||||
assert(audio_models_count < ARRAY_SIZE(audio_models) - 1);
|
||||
audio_models[audio_models_count].name = name;
|
||||
audio_models[audio_models_count].descr = descr;
|
||||
audio_models[audio_models_count].typename = typename;
|
||||
audio_models_count++;
|
||||
}
|
||||
|
||||
void audio_print_available_models(void)
|
||||
{
|
||||
struct audio_model *c;
|
||||
|
||||
if (audio_models_count) {
|
||||
printf("Valid audio device model names:\n");
|
||||
for (c = audio_models; c->name; ++c) {
|
||||
printf("%-11s %s\n", c->name, c->descr);
|
||||
}
|
||||
} else {
|
||||
printf("Machine has no user-selectable audio hardware "
|
||||
"(it may or may not have always-present audio hardware).\n");
|
||||
}
|
||||
}
|
||||
|
||||
static struct audio_model *selected;
|
||||
static const char *audiodev_id;
|
||||
|
||||
void audio_set_model(const char *name, const char *audiodev)
|
||||
{
|
||||
struct audio_model *c;
|
||||
|
||||
if (selected) {
|
||||
error_report("only one -audio option is allowed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (c = audio_models; c->name; ++c) {
|
||||
if (g_str_equal(c->name, name)) {
|
||||
selected = c;
|
||||
audiodev_id = audiodev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!c->name) {
|
||||
error_report("Unknown audio device model `%s'", name);
|
||||
audio_print_available_models();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_model_init(void)
|
||||
{
|
||||
struct audio_model *c = selected;
|
||||
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->typename) {
|
||||
DeviceState *dev = qdev_new(c->typename);
|
||||
BusState *bus = qdev_find_default_bus(DEVICE_GET_CLASS(dev), &error_fatal);
|
||||
qdev_prop_set_string(dev, "audiodev", audiodev_id);
|
||||
qdev_realize_and_unref(dev, bus, &error_fatal);
|
||||
} else {
|
||||
c->init(audiodev_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/isa/isa.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/timer/i8254.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/audio/pcspk.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qom/object.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define PCSPK_BUF_LEN 1792
|
||||
#define PCSPK_SAMPLE_RATE 32000
|
||||
#define PCSPK_MAX_FREQ (PCSPK_SAMPLE_RATE >> 1)
|
||||
#define PCSPK_MIN_COUNT DIV_ROUND_UP(PIT_FREQ, PCSPK_MAX_FREQ)
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(PCSpkState, PC_SPEAKER)
|
||||
|
||||
struct PCSpkState {
|
||||
ISADevice parent_obj;
|
||||
|
||||
MemoryRegion ioport;
|
||||
uint32_t iobase;
|
||||
uint8_t sample_buf[PCSPK_BUF_LEN];
|
||||
AudioBackend *audio_be;
|
||||
SWVoiceOut *voice;
|
||||
PITCommonState *pit;
|
||||
unsigned int pit_count;
|
||||
unsigned int samples;
|
||||
unsigned int play_pos;
|
||||
uint8_t data_on;
|
||||
uint8_t dummy_refresh_clock;
|
||||
};
|
||||
|
||||
static const char *s_spk = "pcspk";
|
||||
|
||||
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 = (QEMU_ALIGN_DOWN(PCSPK_BUF_LEN * PIT_FREQ, 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 = MIN(s->samples - s->play_pos, (unsigned int)free);
|
||||
n = audio_be_write(s->audio_be, 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(PCSpkState *s)
|
||||
{
|
||||
struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUDIO_FORMAT_U8, 0};
|
||||
|
||||
if (s->voice) {
|
||||
/* already initialized */
|
||||
return 0;
|
||||
}
|
||||
|
||||
s->voice = audio_be_open_out(s->audio_be, s->voice, s_spk, s, pcspk_callback, &as);
|
||||
if (!s->voice) {
|
||||
error_report("pcspk: Could not open voice");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t pcspk_io_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
PCSpkState *s = opaque;
|
||||
PITChannelInfo ch;
|
||||
uint8_t val;
|
||||
|
||||
pit_get_channel_info(s->pit, 2, &ch);
|
||||
|
||||
s->dummy_refresh_clock ^= (1 << 4);
|
||||
|
||||
val = ch.gate | (s->data_on << 1) | s->dummy_refresh_clock |
|
||||
(ch.out << 5);
|
||||
|
||||
trace_pcspk_io_read(s->iobase, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val,
|
||||
unsigned size)
|
||||
{
|
||||
PCSpkState *s = opaque;
|
||||
const int gate = val & 1;
|
||||
|
||||
trace_pcspk_io_write(s->iobase, val);
|
||||
|
||||
s->data_on = (val >> 1) & 1;
|
||||
pit_set_gate(s->pit, 2, gate);
|
||||
if (s->voice) {
|
||||
if (gate) /* restart */
|
||||
s->play_pos = 0;
|
||||
audio_be_set_active_out(s->audio_be, 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);
|
||||
|
||||
if (!s->pit) {
|
||||
error_setg(errp, "pcspk: No \"pit\" set or available");
|
||||
return;
|
||||
}
|
||||
|
||||
isa_register_ioport(isadev, &s->ioport, s->iobase);
|
||||
|
||||
if (s->audio_be && audio_be_check(&s->audio_be, errp)) {
|
||||
pcspk_audio_init(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_spk = {
|
||||
.name = "pcspk",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT8(data_on, PCSpkState),
|
||||
VMSTATE_UINT8(dummy_refresh_clock, PCSpkState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const Property pcspk_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(PCSpkState, audio_be),
|
||||
DEFINE_PROP_UINT32("iobase", PCSpkState, iobase, 0x61),
|
||||
DEFINE_PROP_LINK("pit", PCSpkState, pit, TYPE_PIT_COMMON, PITCommonState *),
|
||||
};
|
||||
|
||||
static void pcspk_class_initfn(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = pcspk_realizefn;
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
dc->vmsd = &vmstate_spk;
|
||||
device_class_set_props(dc, pcspk_properties);
|
||||
/* Reason: pit object link */
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
type_init(pcspk_register)
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
* 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 "qemu/osdep.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
|
||||
#include "pl041.h"
|
||||
#include "lm4549.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qom/object.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"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(PL041State, PL041)
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
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 void pl041_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
|
||||
PL041State *s = PL041(dev);
|
||||
|
||||
DBG_L1("pl041_init 0x%08x\n", (uint32_t)s);
|
||||
|
||||
/* Connect the device to the sysbus */
|
||||
memory_region_init_io(&s->iomem, obj, &pl041_ops, s, "pl041", 0x1000);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
}
|
||||
|
||||
static void pl041_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
PL041State *s = PL041(dev);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Init the codec */
|
||||
lm4549_init(&s->codec, &pl041_request_data, (void *)s, errp);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_pl041_regfile = {
|
||||
.name = "pl041_regfile",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const 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 = (const 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 = (const 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 = (const 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 const Property pl041_device_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(PL041State, codec.audio_be),
|
||||
/* Non-compact FIFO depth property */
|
||||
DEFINE_PROP_UINT32("nc_fifo_depth", PL041State, fifo_depth,
|
||||
DEFAULT_FIFO_DEPTH),
|
||||
};
|
||||
|
||||
static void pl041_device_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = pl041_realize;
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
device_class_set_legacy_reset(dc, pl041_device_reset);
|
||||
dc->vmsd = &vmstate_pl041;
|
||||
device_class_set_props(dc, pl041_device_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo pl041_device_info = {
|
||||
.name = TYPE_PL041,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(PL041State),
|
||||
.instance_init = pl041_init,
|
||||
.class_init = pl041_device_class_init,
|
||||
};
|
||||
|
||||
static void pl041_register_types(void)
|
||||
{
|
||||
type_register_static(&pl041_device_info);
|
||||
}
|
||||
|
||||
type_init(pl041_register_types)
|
||||
@@ -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 /* HW_PL041_H */
|
||||
@@ -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 )
|
||||
+1483
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
# See docs/devel/tracing.rst for syntax documentation.
|
||||
|
||||
# cs4231.c
|
||||
cs4231_mem_readl_dreg(uint32_t reg, uint32_t ret) "read dreg %d: 0x%02x"
|
||||
cs4231_mem_readl_reg(uint32_t reg, uint32_t ret) "read reg %d: 0x%08x"
|
||||
cs4231_mem_writel_reg(uint32_t reg, uint32_t old, uint32_t val) "write reg %d: 0x%08x -> 0x%08x"
|
||||
cs4231_mem_writel_dreg(uint32_t reg, uint32_t old, uint32_t val) "write dreg %d: 0x%02x -> 0x%02x"
|
||||
|
||||
# es1370.c
|
||||
es1370_frame_address_rd(int ch, uint32_t addr) "ch=%d addr=0x%08x"
|
||||
es1370_frame_address_wr(int ch, uint32_t addr) "ch=%d addr=0x%08x"
|
||||
es1370_frame_count_rd(int ch, uint32_t curr, uint32_t size) "ch=%d CURR_CT=%u BUF_SIZE=%u"
|
||||
es1370_frame_count_wr(int ch, uint32_t curr, uint32_t size) "ch=%d CURR_CT=%u BUF_SIZE=%u"
|
||||
es1370_lost_interrupt(int ch) "ch=%d lost interrupt"
|
||||
es1370_sample_count_rd(int ch, uint32_t curr, uint32_t num) "ch=%d CURR_SAMP_CT=%u SAMP_CT=%u"
|
||||
es1370_sample_count_wr(int ch, uint32_t curr, uint32_t num) "ch=%d CURR_SAMP_CT=%u SAMP_CT=%u"
|
||||
es1370_stream_format(int ch, uint32_t freq, const char *fmt, const char *mode, uint32_t shift) "ch=%d fmt=%u:%s:%s shift=%u"
|
||||
es1370_transfer_audio(int ch, uint32_t f_curr, uint32_t f_size, uint32_t s_curr, uint32_t s_num, uint32_t leftover, bool irq) "ch=%d CURR_CT=%u BUF_SIZE=%u CURR_SAMP_CT=%u SAMP_CT=%u leftover=%u irq=%d"
|
||||
|
||||
# hda-codec.c
|
||||
hda_audio_running(const char *stream, int nr, bool running) "st %s, nr %d, run %d"
|
||||
hda_audio_format(const char *stream, int chan, const char *fmt, int freq) "st %s, %d x %s @ %d Hz"
|
||||
hda_audio_adjust(const char *stream, int pos) "st %s, pos %d"
|
||||
hda_audio_overrun(const char *stream) "st %s"
|
||||
|
||||
# pcspk.c
|
||||
pcspk_io_read(uint16_t addr, uint8_t val) "[0x%"PRIx16"] -> 0x%"PRIx8
|
||||
pcspk_io_write(uint16_t addr, uint8_t val) "[0x%"PRIx16"] <- 0x%"PRIx8
|
||||
|
||||
#via-ac97.c
|
||||
via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x"
|
||||
via_ac97_sgd_fetch(uint32_t curr, uint32_t addr, char stop, char eol, char flag, uint32_t len) "curr=0x%x addr=0x%x %c%c%c len=%d"
|
||||
via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64
|
||||
via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64
|
||||
|
||||
# asc.c
|
||||
asc_read_fifo(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_read_reg(int reg, unsigned size, uint64_t value) "reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_read_extreg(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_fifo_get(const char fifo, int rptr, int cnt, uint64_t value) "fifo %c rptr=0x%x cnt=0x%x value=0x%"PRIx64
|
||||
asc_write_fifo(const char fifo, int reg, unsigned size, int wrptr, int cnt, uint64_t value) "fifo %c reg=0x%03x size=%u wptr=0x%x cnt=0x%x value=0x%"PRIx64
|
||||
asc_write_reg(int reg, unsigned size, uint64_t value) "reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_write_extreg(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_update_irq(int irq, int a, int b) "set IRQ to %d (A: 0x%x B: 0x%x)"
|
||||
|
||||
#virtio-snd.c
|
||||
virtio_snd_get_config(void *vdev, uint32_t jacks, uint32_t streams, uint32_t chmaps) "snd %p: get_config jacks=%"PRIu32" streams=%"PRIu32" chmaps=%"PRIu32""
|
||||
virtio_snd_get_features(void *vdev, uint64_t features) "snd %p: get_features 0x%"PRIx64
|
||||
virtio_snd_vm_state_running(void) "vm state running"
|
||||
virtio_snd_vm_state_stopped(void) "vm state stopped"
|
||||
virtio_snd_realize(void *snd) "snd %p: realize"
|
||||
virtio_snd_unrealize(void *snd) "snd %p: unrealize"
|
||||
virtio_snd_handle_pcm_set_params(uint32_t stream) "VIRTIO_SND_PCM_SET_PARAMS called for stream %"PRIu32
|
||||
virtio_snd_handle_ctrl(void *vdev, void *vq) "snd %p: handle ctrl event for queue %p"
|
||||
virtio_snd_handle_pcm_info(uint32_t stream) "VIRTIO_SND_R_PCM_INFO called for stream %"PRIu32
|
||||
virtio_snd_handle_pcm_start_stop(const char *code, uint32_t stream) "%s called for stream %"PRIu32
|
||||
virtio_snd_handle_pcm_release(uint32_t stream) "VIRTIO_SND_PCM_RELEASE called for stream %"PRIu32
|
||||
virtio_snd_handle_code(uint32_t val, const char *code) "ctrl code msg val = %"PRIu32" == %s"
|
||||
virtio_snd_handle_chmap_info(void) "VIRTIO_SND_CHMAP_INFO called"
|
||||
virtio_snd_handle_event(void) "event queue callback called"
|
||||
virtio_snd_pcm_stream_flush(uint32_t stream) "flushing stream %"PRIu32
|
||||
virtio_snd_handle_tx_xfer(void) "tx queue callback called"
|
||||
virtio_snd_handle_rx_xfer(void) "rx queue callback called"
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-hw_audio.h"
|
||||
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
* VIA south bridges sound support
|
||||
*
|
||||
* Copyright (c) 2022-2023 BALATON Zoltan
|
||||
*
|
||||
* This work is licensed under the GNU GPL license version 2 or later.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO: This is only a basic implementation of one audio playback channel
|
||||
* more functionality should be added here.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "hw/isa/vt82c686.h"
|
||||
#include "ac97.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define CLEN_IS_EOL(x) ((x)->clen & BIT(31))
|
||||
#define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
|
||||
#define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
|
||||
#define CLEN_LEN(x) ((x)->clen & 0xffffff)
|
||||
|
||||
#define STAT_ACTIVE BIT(7)
|
||||
#define STAT_PAUSED BIT(6)
|
||||
#define STAT_TRIG BIT(3)
|
||||
#define STAT_STOP BIT(2)
|
||||
#define STAT_EOL BIT(1)
|
||||
#define STAT_FLAG BIT(0)
|
||||
|
||||
#define CNTL_START BIT(7)
|
||||
#define CNTL_TERM BIT(6)
|
||||
#define CNTL_PAUSE BIT(3)
|
||||
|
||||
static void open_voice_out(ViaAC97State *s);
|
||||
|
||||
static uint16_t codec_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100,
|
||||
48000 };
|
||||
|
||||
#define CODEC_REG(s, o) ((s)->codec_regs[(o) / 2])
|
||||
#define CODEC_VOL(vol, mask) ((255 * ((vol) & mask)) / mask)
|
||||
|
||||
static void codec_volume_set_out(ViaAC97State *s)
|
||||
{
|
||||
int lvol, rvol, mute;
|
||||
|
||||
lvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute) >> 8, 0x1f);
|
||||
lvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> 8, 0x1f);
|
||||
lvol /= 255;
|
||||
rvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute), 0x1f);
|
||||
rvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute), 0x1f);
|
||||
rvol /= 255;
|
||||
mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
|
||||
mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
|
||||
audio_be_set_volume_out_lr(s->audio_be, s->vo, mute, lvol, rvol);
|
||||
}
|
||||
|
||||
static void codec_reset(ViaAC97State *s)
|
||||
{
|
||||
memset(s->codec_regs, 0, sizeof(s->codec_regs));
|
||||
CODEC_REG(s, AC97_Reset) = 0x6a90;
|
||||
CODEC_REG(s, AC97_Master_Volume_Mute) = 0x8000;
|
||||
CODEC_REG(s, AC97_Headphone_Volume_Mute) = 0x8000;
|
||||
CODEC_REG(s, AC97_Master_Volume_Mono_Mute) = 0x8000;
|
||||
CODEC_REG(s, AC97_Phone_Volume_Mute) = 0x8008;
|
||||
CODEC_REG(s, AC97_Mic_Volume_Mute) = 0x8008;
|
||||
CODEC_REG(s, AC97_Line_In_Volume_Mute) = 0x8808;
|
||||
CODEC_REG(s, AC97_CD_Volume_Mute) = 0x8808;
|
||||
CODEC_REG(s, AC97_Video_Volume_Mute) = 0x8808;
|
||||
CODEC_REG(s, AC97_Aux_Volume_Mute) = 0x8808;
|
||||
CODEC_REG(s, AC97_PCM_Out_Volume_Mute) = 0x8808;
|
||||
CODEC_REG(s, AC97_Record_Gain_Mute) = 0x8000;
|
||||
CODEC_REG(s, AC97_Powerdown_Ctrl_Stat) = 0x000f;
|
||||
CODEC_REG(s, AC97_Extended_Audio_ID) = 0x0a05;
|
||||
CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) = 0x0400;
|
||||
CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
|
||||
CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
|
||||
/* Sigmatel 9766 (STAC9766) */
|
||||
CODEC_REG(s, AC97_Vendor_ID1) = 0x8384;
|
||||
CODEC_REG(s, AC97_Vendor_ID2) = 0x7666;
|
||||
}
|
||||
|
||||
static uint16_t codec_read(ViaAC97State *s, uint8_t addr)
|
||||
{
|
||||
return CODEC_REG(s, addr);
|
||||
}
|
||||
|
||||
static void codec_write(ViaAC97State *s, uint8_t addr, uint16_t val)
|
||||
{
|
||||
trace_via_ac97_codec_write(addr, val);
|
||||
switch (addr) {
|
||||
case AC97_Reset:
|
||||
codec_reset(s);
|
||||
return;
|
||||
case AC97_Master_Volume_Mute:
|
||||
case AC97_PCM_Out_Volume_Mute:
|
||||
if (addr == AC97_Master_Volume_Mute) {
|
||||
if (val & BIT(13)) {
|
||||
val |= 0x1f00;
|
||||
}
|
||||
if (val & BIT(5)) {
|
||||
val |= 0x1f;
|
||||
}
|
||||
}
|
||||
CODEC_REG(s, addr) = val & 0x9f1f;
|
||||
codec_volume_set_out(s);
|
||||
return;
|
||||
case AC97_Extended_Audio_Ctrl_Stat:
|
||||
CODEC_REG(s, addr) &= ~EACS_VRA;
|
||||
CODEC_REG(s, addr) |= val & EACS_VRA;
|
||||
if (!(val & EACS_VRA)) {
|
||||
CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
|
||||
CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
|
||||
open_voice_out(s);
|
||||
}
|
||||
return;
|
||||
case AC97_PCM_Front_DAC_Rate:
|
||||
case AC97_PCM_LR_ADC_Rate:
|
||||
if (CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
|
||||
int i;
|
||||
uint16_t rate = val;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(codec_rates) - 1; i++) {
|
||||
if (rate < codec_rates[i] +
|
||||
(codec_rates[i + 1] - codec_rates[i]) / 2) {
|
||||
rate = codec_rates[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rate > 48000) {
|
||||
rate = 48000;
|
||||
}
|
||||
CODEC_REG(s, addr) = rate;
|
||||
open_voice_out(s);
|
||||
}
|
||||
return;
|
||||
case AC97_Powerdown_Ctrl_Stat:
|
||||
CODEC_REG(s, addr) = (val & 0xff00) | (CODEC_REG(s, addr) & 0xff);
|
||||
return;
|
||||
case AC97_Extended_Audio_ID:
|
||||
case AC97_Vendor_ID1:
|
||||
case AC97_Vendor_ID2:
|
||||
/* Read only registers */
|
||||
return;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"via-ac97: Unimplemented codec register 0x%x\n", addr);
|
||||
CODEC_REG(s, addr) = val;
|
||||
}
|
||||
}
|
||||
|
||||
static void fetch_sgd(ViaAC97SGDChannel *c, PCIDevice *d)
|
||||
{
|
||||
uint32_t b[2];
|
||||
|
||||
if (c->curr < c->base) {
|
||||
c->curr = c->base;
|
||||
}
|
||||
if (unlikely(pci_dma_read(d, c->curr, b, sizeof(b)) != MEMTX_OK)) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"via-ac97: DMA error reading SGD table\n");
|
||||
return;
|
||||
}
|
||||
c->addr = le32_to_cpu(b[0]);
|
||||
c->clen = le32_to_cpu(b[1]);
|
||||
trace_via_ac97_sgd_fetch(c->curr, c->addr, CLEN_IS_STOP(c) ? 'S' : '-',
|
||||
CLEN_IS_EOL(c) ? 'E' : '-',
|
||||
CLEN_IS_FLAG(c) ? 'F' : '-', CLEN_LEN(c));
|
||||
}
|
||||
|
||||
static void out_cb(void *opaque, int avail)
|
||||
{
|
||||
ViaAC97State *s = opaque;
|
||||
ViaAC97SGDChannel *c = &s->aur;
|
||||
int temp, to_copy, copied;
|
||||
bool stop = false;
|
||||
QEMU_UNINITIALIZED uint8_t tmpbuf[4096];
|
||||
|
||||
if (c->stat & STAT_PAUSED) {
|
||||
return;
|
||||
}
|
||||
c->stat |= STAT_ACTIVE;
|
||||
while (avail && !stop) {
|
||||
if (!c->clen) {
|
||||
fetch_sgd(c, &s->dev);
|
||||
}
|
||||
temp = MIN(CLEN_LEN(c), avail);
|
||||
while (temp) {
|
||||
to_copy = MIN(temp, sizeof(tmpbuf));
|
||||
pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
|
||||
copied = audio_be_write(s->audio_be, s->vo, tmpbuf, to_copy);
|
||||
if (!copied) {
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
temp -= copied;
|
||||
avail -= copied;
|
||||
c->addr += copied;
|
||||
c->clen -= copied;
|
||||
}
|
||||
if (CLEN_LEN(c) == 0) {
|
||||
c->curr += 8;
|
||||
if (CLEN_IS_EOL(c)) {
|
||||
c->stat |= STAT_EOL;
|
||||
if (c->type & CNTL_START) {
|
||||
c->curr = c->base;
|
||||
c->stat |= STAT_PAUSED;
|
||||
} else {
|
||||
c->stat &= ~STAT_ACTIVE;
|
||||
audio_be_set_active_out(s->audio_be, s->vo, 0);
|
||||
}
|
||||
if (c->type & STAT_EOL) {
|
||||
via_isa_set_irq(&s->dev, 0, 1);
|
||||
}
|
||||
}
|
||||
if (CLEN_IS_FLAG(c)) {
|
||||
c->stat |= STAT_FLAG;
|
||||
c->stat |= STAT_PAUSED;
|
||||
if (c->type & STAT_FLAG) {
|
||||
via_isa_set_irq(&s->dev, 0, 1);
|
||||
}
|
||||
}
|
||||
if (CLEN_IS_STOP(c)) {
|
||||
c->stat |= STAT_STOP;
|
||||
c->stat |= STAT_PAUSED;
|
||||
}
|
||||
c->clen = 0;
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void open_voice_out(ViaAC97State *s)
|
||||
{
|
||||
struct audsettings as = {
|
||||
.freq = CODEC_REG(s, AC97_PCM_Front_DAC_Rate),
|
||||
.nchannels = s->aur.type & BIT(4) ? 2 : 1,
|
||||
.fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8,
|
||||
.big_endian = false,
|
||||
};
|
||||
s->vo = audio_be_open_out(s->audio_be, s->vo, "via-ac97.out", s, out_cb, &as);
|
||||
}
|
||||
|
||||
static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
ViaAC97State *s = opaque;
|
||||
uint64_t val = 0;
|
||||
|
||||
switch (addr) {
|
||||
case 0:
|
||||
val = s->aur.stat;
|
||||
if (s->aur.type & CNTL_START) {
|
||||
val |= STAT_TRIG;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
val = s->aur.stat & STAT_PAUSED ? BIT(3) : 0;
|
||||
break;
|
||||
case 2:
|
||||
val = s->aur.type;
|
||||
break;
|
||||
case 4:
|
||||
val = s->aur.curr;
|
||||
break;
|
||||
case 0xc:
|
||||
val = CLEN_LEN(&s->aur);
|
||||
break;
|
||||
case 0x10:
|
||||
/* silence unimplemented log message that happens at every IRQ */
|
||||
break;
|
||||
case 0x80:
|
||||
val = s->ac97_cmd;
|
||||
break;
|
||||
case 0x84:
|
||||
val = s->aur.stat & STAT_FLAG;
|
||||
if (s->aur.stat & STAT_EOL) {
|
||||
val |= BIT(4);
|
||||
}
|
||||
if (s->aur.stat & STAT_STOP) {
|
||||
val |= BIT(8);
|
||||
}
|
||||
if (s->aur.stat & STAT_ACTIVE) {
|
||||
val |= BIT(12);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register read 0x%"
|
||||
HWADDR_PRIx"\n", addr);
|
||||
}
|
||||
trace_via_ac97_sgd_read(addr, size, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||||
{
|
||||
ViaAC97State *s = opaque;
|
||||
|
||||
trace_via_ac97_sgd_write(addr, size, val);
|
||||
switch (addr) {
|
||||
case 0:
|
||||
if (val & STAT_STOP) {
|
||||
s->aur.stat &= ~STAT_PAUSED;
|
||||
}
|
||||
if (val & STAT_EOL) {
|
||||
s->aur.stat &= ~(STAT_EOL | STAT_PAUSED);
|
||||
if (s->aur.type & STAT_EOL) {
|
||||
via_isa_set_irq(&s->dev, 0, 0);
|
||||
}
|
||||
}
|
||||
if (val & STAT_FLAG) {
|
||||
s->aur.stat &= ~(STAT_FLAG | STAT_PAUSED);
|
||||
if (s->aur.type & STAT_FLAG) {
|
||||
via_isa_set_irq(&s->dev, 0, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (val & CNTL_START) {
|
||||
audio_be_set_active_out(s->audio_be, s->vo, 1);
|
||||
s->aur.stat = STAT_ACTIVE;
|
||||
}
|
||||
if (val & CNTL_TERM) {
|
||||
audio_be_set_active_out(s->audio_be, s->vo, 0);
|
||||
s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
|
||||
s->aur.clen = 0;
|
||||
}
|
||||
if (val & CNTL_PAUSE) {
|
||||
audio_be_set_active_out(s->audio_be, s->vo, 0);
|
||||
s->aur.stat &= ~STAT_ACTIVE;
|
||||
s->aur.stat |= STAT_PAUSED;
|
||||
} else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
|
||||
audio_be_set_active_out(s->audio_be, s->vo, 1);
|
||||
s->aur.stat |= STAT_ACTIVE;
|
||||
s->aur.stat &= ~STAT_PAUSED;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
uint32_t oldval = s->aur.type;
|
||||
s->aur.type = val;
|
||||
if ((oldval & 0x30) != (val & 0x30)) {
|
||||
open_voice_out(s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
s->aur.base = val & ~1ULL;
|
||||
s->aur.curr = s->aur.base;
|
||||
break;
|
||||
case 0x80:
|
||||
if (val >> 30) {
|
||||
/* we only have primary codec */
|
||||
break;
|
||||
}
|
||||
if (val & BIT(23)) { /* read reg */
|
||||
s->ac97_cmd = val & 0xc0ff0000ULL;
|
||||
s->ac97_cmd |= codec_read(s, (val >> 16) & 0x7f);
|
||||
s->ac97_cmd |= BIT(25); /* data valid */
|
||||
} else {
|
||||
s->ac97_cmd = val & 0xc0ffffffULL;
|
||||
codec_write(s, (val >> 16) & 0x7f, val);
|
||||
}
|
||||
break;
|
||||
case 0xc:
|
||||
case 0x84:
|
||||
/* Read only */
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register write 0x%"
|
||||
HWADDR_PRIx"\n", addr);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sgd_ops = {
|
||||
.read = sgd_read,
|
||||
.write = sgd_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static uint64_t fm_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
|
||||
__func__, addr, size, val);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps fm_ops = {
|
||||
.read = fm_read,
|
||||
.write = fm_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static uint64_t midi_read(void *opaque, hwaddr addr, unsigned size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
|
||||
__func__, addr, size, val);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps midi_ops = {
|
||||
.read = midi_read,
|
||||
.write = midi_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static void via_ac97_reset(DeviceState *dev)
|
||||
{
|
||||
ViaAC97State *s = VIA_AC97(dev);
|
||||
|
||||
codec_reset(s);
|
||||
}
|
||||
|
||||
static void via_ac97_realize(PCIDevice *pci_dev, Error **errp)
|
||||
{
|
||||
ViaAC97State *s = VIA_AC97(pci_dev);
|
||||
Object *o = OBJECT(s);
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Command register Bus Master bit is documented to be fixed at 0 but it's
|
||||
* needed for PCI DMA to work in QEMU. The pegasos2 firmware writes 0 here
|
||||
* and the AmigaOS driver writes 1 only enabling IO bit which works on
|
||||
* real hardware. So set it here and fix it to 1 to allow DMA.
|
||||
*/
|
||||
pci_set_word(pci_dev->config + PCI_COMMAND, PCI_COMMAND_MASTER);
|
||||
pci_set_word(pci_dev->wmask + PCI_COMMAND, PCI_COMMAND_IO);
|
||||
pci_set_word(pci_dev->config + PCI_STATUS,
|
||||
PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_MEDIUM);
|
||||
pci_set_long(pci_dev->config + PCI_INTERRUPT_PIN, 0x03);
|
||||
pci_set_byte(pci_dev->config + 0x40, 1); /* codec ready */
|
||||
|
||||
memory_region_init_io(&s->sgd, o, &sgd_ops, s, "via-ac97.sgd", 256);
|
||||
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->sgd);
|
||||
memory_region_init_io(&s->fm, o, &fm_ops, s, "via-ac97.fm", 4);
|
||||
pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->fm);
|
||||
memory_region_init_io(&s->midi, o, &midi_ops, s, "via-ac97.midi", 4);
|
||||
pci_register_bar(pci_dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &s->midi);
|
||||
}
|
||||
|
||||
static void via_ac97_exit(PCIDevice *dev)
|
||||
{
|
||||
ViaAC97State *s = VIA_AC97(dev);
|
||||
|
||||
audio_be_close_out(s->audio_be, s->vo);
|
||||
}
|
||||
|
||||
static const Property via_ac97_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(ViaAC97State, audio_be),
|
||||
};
|
||||
|
||||
static void via_ac97_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
||||
|
||||
k->realize = via_ac97_realize;
|
||||
k->exit = via_ac97_exit;
|
||||
k->vendor_id = PCI_VENDOR_ID_VIA;
|
||||
k->device_id = PCI_DEVICE_ID_VIA_AC97;
|
||||
k->revision = 0x50;
|
||||
k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
|
||||
device_class_set_props(dc, via_ac97_properties);
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
dc->desc = "VIA AC97";
|
||||
device_class_set_legacy_reset(dc, via_ac97_reset);
|
||||
/* Reason: Part of a south bridge chip */
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
static const TypeInfo via_ac97_info = {
|
||||
.name = TYPE_VIA_AC97,
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.instance_size = sizeof(ViaAC97State),
|
||||
.class_init = via_ac97_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||||
{ },
|
||||
},
|
||||
};
|
||||
|
||||
static void via_mc97_realize(PCIDevice *pci_dev, Error **errp)
|
||||
{
|
||||
pci_set_word(pci_dev->config + PCI_COMMAND,
|
||||
PCI_COMMAND_INVALIDATE | PCI_COMMAND_VGA_PALETTE);
|
||||
pci_set_word(pci_dev->config + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM);
|
||||
pci_set_long(pci_dev->config + PCI_INTERRUPT_PIN, 0x03);
|
||||
}
|
||||
|
||||
static void via_mc97_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
||||
|
||||
k->realize = via_mc97_realize;
|
||||
k->vendor_id = PCI_VENDOR_ID_VIA;
|
||||
k->device_id = PCI_DEVICE_ID_VIA_MC97;
|
||||
k->class_id = PCI_CLASS_COMMUNICATION_OTHER;
|
||||
k->revision = 0x30;
|
||||
set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
|
||||
dc->desc = "VIA MC97";
|
||||
/* Reason: Part of a south bridge chip */
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
static const TypeInfo via_mc97_info = {
|
||||
.name = TYPE_VIA_MC97,
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.instance_size = sizeof(PCIDevice),
|
||||
.class_init = via_mc97_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||||
{ },
|
||||
},
|
||||
};
|
||||
|
||||
static void via_ac97_register_types(void)
|
||||
{
|
||||
type_register_static(&via_ac97_info);
|
||||
type_register_static(&via_mc97_info);
|
||||
}
|
||||
|
||||
type_init(via_ac97_register_types)
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* VIRTIO Sound Device PCI Bindings
|
||||
*
|
||||
* Copyright (c) 2023 Emmanouil Pitsidianakis <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or
|
||||
* (at your option) any later version. See the COPYING file in the
|
||||
* top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qom/object.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "hw/virtio/virtio-pci.h"
|
||||
#include "hw/audio/virtio-snd.h"
|
||||
|
||||
/*
|
||||
* virtio-snd-pci: This extends VirtioPCIProxy.
|
||||
*/
|
||||
#define TYPE_VIRTIO_SND_PCI "virtio-sound-pci"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VirtIOSoundPCI, VIRTIO_SND_PCI)
|
||||
|
||||
struct VirtIOSoundPCI {
|
||||
VirtIOPCIProxy parent_obj;
|
||||
|
||||
VirtIOSound vdev;
|
||||
};
|
||||
|
||||
static const Property virtio_snd_pci_properties[] = {
|
||||
DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
|
||||
VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
|
||||
DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
|
||||
};
|
||||
|
||||
static void virtio_snd_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
|
||||
{
|
||||
VirtIOSoundPCI *dev = VIRTIO_SND_PCI(vpci_dev);
|
||||
DeviceState *vdev = DEVICE(&dev->vdev);
|
||||
|
||||
virtio_pci_force_virtio_1(vpci_dev);
|
||||
qdev_realize(vdev, BUS(&vpci_dev->bus), errp);
|
||||
}
|
||||
|
||||
static void virtio_snd_pci_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
VirtioPCIClass *vpciklass = VIRTIO_PCI_CLASS(klass);
|
||||
PCIDeviceClass *pcidevklass = PCI_DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, virtio_snd_pci_properties);
|
||||
dc->desc = "Virtio Sound";
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
|
||||
vpciklass->realize = virtio_snd_pci_realize;
|
||||
pcidevklass->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
|
||||
}
|
||||
|
||||
static void virtio_snd_pci_instance_init(Object *obj)
|
||||
{
|
||||
VirtIOSoundPCI *dev = VIRTIO_SND_PCI(obj);
|
||||
|
||||
virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
|
||||
TYPE_VIRTIO_SND);
|
||||
}
|
||||
|
||||
static const VirtioPCIDeviceTypeInfo virtio_snd_pci_info = {
|
||||
.generic_name = TYPE_VIRTIO_SND_PCI,
|
||||
.instance_size = sizeof(VirtIOSoundPCI),
|
||||
.instance_init = virtio_snd_pci_instance_init,
|
||||
.class_init = virtio_snd_pci_class_init,
|
||||
};
|
||||
|
||||
static void virtio_snd_pci_register(void)
|
||||
{
|
||||
virtio_pci_types_register(&virtio_snd_pci_info);
|
||||
audio_register_model("virtio", "Virtio Sound", TYPE_VIRTIO_SND_PCI);
|
||||
}
|
||||
|
||||
type_init(virtio_snd_pci_register);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,738 @@
|
||||
/*
|
||||
* WM8750 audio CODEC.
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Written by Andrzej Zaborowski <[email protected]>
|
||||
*
|
||||
* This file is licensed under GNU GPL.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/i2c/i2c.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/audio/wm8750.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "qom/object.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;
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(WM8750State, WM8750)
|
||||
|
||||
struct WM8750State {
|
||||
I2CSlave parent_obj;
|
||||
|
||||
uint8_t i2c_data[2];
|
||||
int i2c_len;
|
||||
AudioBackend *audio_be;
|
||||
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;
|
||||
};
|
||||
|
||||
/* 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 = MAX(0, (int) sizeof(s->data_in) - s->req_in);
|
||||
audio_be_read(s->audio_be, *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 += audio_be_write(s->audio_be, *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] */
|
||||
|
||||
audio_be_set_volume_in_lr(s->audio_be, 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]));
|
||||
audio_be_set_volume_in_lr(s->audio_be, 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]));
|
||||
audio_be_set_volume_in_lr(s->audio_be, 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 */
|
||||
audio_be_set_volume_out_lr(s->audio_be, 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 */
|
||||
audio_be_set_volume_out_lr(s->audio_be, 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 */
|
||||
audio_be_set_volume_out_lr(s->audio_be, 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])
|
||||
audio_be_set_active_in(s->audio_be, *s->in[0], 0);
|
||||
if (s->out[0] && *s->out[0])
|
||||
audio_be_set_active_out(s->audio_be, *s->out[0], 0);
|
||||
|
||||
for (i = 0; i < IN_PORT_N; i ++)
|
||||
if (s->adc_voice[i]) {
|
||||
audio_be_close_in(s->audio_be, s->adc_voice[i]);
|
||||
s->adc_voice[i] = NULL;
|
||||
}
|
||||
for (i = 0; i < OUT_PORT_N; i ++)
|
||||
if (s->dac_voice[i]) {
|
||||
audio_be_close_out(s->audio_be, s->dac_voice[i]);
|
||||
s->dac_voice[i] = NULL;
|
||||
}
|
||||
|
||||
if (!s->enable)
|
||||
return;
|
||||
|
||||
/* Setup input */
|
||||
in_fmt.big_endian = false;
|
||||
in_fmt.nchannels = 2;
|
||||
in_fmt.freq = s->adc_hz;
|
||||
in_fmt.fmt = AUDIO_FORMAT_S16;
|
||||
|
||||
s->adc_voice[0] = audio_be_open_in(s->audio_be, s->adc_voice[0],
|
||||
CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt);
|
||||
s->adc_voice[1] = audio_be_open_in(s->audio_be, s->adc_voice[1],
|
||||
CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt);
|
||||
s->adc_voice[2] = audio_be_open_in(s->audio_be, s->adc_voice[2],
|
||||
CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt);
|
||||
|
||||
/* Setup output */
|
||||
out_fmt.big_endian = false;
|
||||
out_fmt.nchannels = 2;
|
||||
out_fmt.freq = s->dac_hz;
|
||||
out_fmt.fmt = AUDIO_FORMAT_S16;
|
||||
|
||||
s->dac_voice[0] = audio_be_open_out(s->audio_be, s->dac_voice[0],
|
||||
CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt);
|
||||
s->dac_voice[1] = audio_be_open_out(s->audio_be, s->dac_voice[1],
|
||||
CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt);
|
||||
/* MONOMIX is also in stereo for simplicity */
|
||||
s->dac_voice[2] = audio_be_open_out(s->audio_be, 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])
|
||||
audio_be_set_active_in(s->audio_be, *s->in[0], 1);
|
||||
if (s->out[0] && *s->out[0])
|
||||
audio_be_set_active_out(s->audio_be, *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 int 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",
|
||||
__func__, s->i2c_len);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#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", __func__, cmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t wm8750_rx(I2CSlave *i2c)
|
||||
{
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
static int wm8750_pre_save(void *opaque)
|
||||
{
|
||||
WM8750State *s = opaque;
|
||||
|
||||
s->rate_vmstate = s->rate - wm_rate_table;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 = (const 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 void wm8750_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
WM8750State *s = WM8750(dev);
|
||||
|
||||
if (!audio_be_check(&s->audio_be, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wm8750_reset(I2C_SLAVE(s));
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void wm8750_fini(I2CSlave *i2c)
|
||||
{
|
||||
WM8750State *s = WM8750(i2c);
|
||||
|
||||
wm8750_reset(I2C_SLAVE(s));
|
||||
g_free(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
void wm8750_data_req_set(DeviceState *dev, data_req_cb *data_req, 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 <i>samples</i> 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);
|
||||
if (s->idx_in >= sizeof(s->data_in)) {
|
||||
return 0x80008000; /* silence in AUDIO_FORMAT_S16 sample format */
|
||||
}
|
||||
}
|
||||
|
||||
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 const Property wm8750_properties[] = {
|
||||
DEFINE_AUDIO_PROPERTIES(WM8750State, audio_be),
|
||||
};
|
||||
|
||||
static void wm8750_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
|
||||
|
||||
dc->realize = wm8750_realize;
|
||||
sc->event = wm8750_event;
|
||||
sc->recv = wm8750_rx;
|
||||
sc->send = wm8750_tx;
|
||||
dc->vmsd = &vmstate_wm8750;
|
||||
device_class_set_props(dc, wm8750_properties);
|
||||
}
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user