qemu/hw/audio/hda-codec.c
Martin Kletzander b7639b7dd0 hw/audio: Simplify hda audio init
No return values are used anywhere, so switch the functions to be void
and add support for error reporting using errp for use in next patches.

Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-ID: <cd1df4ad2a6fae969c4a02a77955c4a8c0d430b6.1650874791.git.mkletzan@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2023-09-22 16:30:07 +02:00

963 lines
27 KiB
C

/*
* Copyright (C) 2010 Red Hat, Inc.
*
* written by Gerd Hoffmann <kraxel@redhat.com>
*
* 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/qdev-properties.h"
#include "intel-hda.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
#include "intel-hda-defs.h"
#include "audio/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;
QEMUSoundCard card;
const desc_codec *desc;
HDAAudioStream st[4];
bool running_compat[16];
bool running_real[2 * 16];
/* properties */
uint32_t debug;
bool mixer;
bool use_timer;
};
static inline int64_t hda_bytes_per_second(HDAAudioStream *st)
{
return 2LL * st->as.nchannels * 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 buft_start = st->buft_start;
int64_t wpos = st->wpos;
int64_t rpos = st->rpos;
int64_t wanted_rpos = hda_bytes_per_second(st) * (now - buft_start)
/ 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 = AUD_read(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 buft_start = st->buft_start;
int64_t wpos = st->wpos;
int64_t rpos = st->rpos;
int64_t wanted_wpos = hda_bytes_per_second(st) * (now - buft_start)
/ 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 = AUD_write(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_compat_input_cb(void *opaque, int avail)
{
HDAAudioStream *st = opaque;
int recv = 0;
int len;
bool rc;
while (avail - recv >= sizeof(st->compat_buf)) {
if (st->compat_bpos != sizeof(st->compat_buf)) {
len = AUD_read(st->voice.in, st->compat_buf + st->compat_bpos,
sizeof(st->compat_buf) - st->compat_bpos);
st->compat_bpos += len;
recv += len;
if (st->compat_bpos != sizeof(st->compat_buf)) {
break;
}
}
rc = hda_codec_xfer(&st->state->hda, st->stream, false,
st->compat_buf, sizeof(st->compat_buf));
if (!rc) {
break;
}
st->compat_bpos = 0;
}
}
static void hda_audio_compat_output_cb(void *opaque, int avail)
{
HDAAudioStream *st = opaque;
int sent = 0;
int len;
bool rc;
while (avail - sent >= sizeof(st->compat_buf)) {
if (st->compat_bpos == sizeof(st->compat_buf)) {
rc = hda_codec_xfer(&st->state->hda, st->stream, true,
st->compat_buf, sizeof(st->compat_buf));
if (!rc) {
break;
}
st->compat_bpos = 0;
}
len = AUD_write(st->voice.out, st->compat_buf + st->compat_bpos,
sizeof(st->compat_buf) - st->compat_bpos);
st->compat_bpos += len;
sent += len;
if (st->compat_bpos != sizeof(st->compat_buf)) {
break;
}
}
}
static void hda_audio_set_running(HDAAudioStream *st, bool running)
{
if (st->node == NULL) {
return;
}
if (st->running == running) {
return;
}
st->running = running;
trace_hda_audio_running(st->node->name, st->stream, st->running);
if (st->state->use_timer) {
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) {
AUD_set_active_out(st->voice.out, st->running);
} else {
AUD_set_active_in(st->voice.in, st->running);
}
}
static void hda_audio_set_amp(HDAAudioStream *st)
{
bool muted;
uint32_t left, right;
if (st->node == NULL) {
return;
}
muted = st->mute_left && st->mute_right;
left = st->mute_left ? 0 : st->gain_left;
right = st->mute_right ? 0 : st->gain_right;
left = left * 255 / QEMU_HDA_AMP_STEPS;
right = right * 255 / QEMU_HDA_AMP_STEPS;
if (!st->state->mixer) {
return;
}
if (st->output) {
AUD_set_volume_out(st->voice.out, muted, left, right);
} else {
AUD_set_volume_in(st->voice.in, muted, left, right);
}
}
static void hda_audio_setup(HDAAudioStream *st)
{
bool use_timer = st->state->use_timer;
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) {
if (use_timer) {
cb = hda_audio_output_cb;
st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
hda_audio_output_timer, st);
} else {
cb = hda_audio_compat_output_cb;
}
st->voice.out = AUD_open_out(&st->state->card, st->voice.out,
st->node->name, st, cb, &st->as);
} else {
if (use_timer) {
cb = hda_audio_input_cb;
st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL,
hda_audio_input_timer, st);
} else {
cb = hda_audio_compat_input_cb;
}
st->voice.in = AUD_open_in(&st->state->card, 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;
a->desc = desc;
a->name = object_get_typename(OBJECT(a));
dprint(a, 1, "%s: cad %d\n", __func__, a->hda.cad);
AUD_register_card("hda", &a->card);
for (i = 0; i < a->desc->nnodes; i++) {
node = a->desc->nodes + i;
param = hda_codec_find_param(node, AC_PAR_AUDIO_WIDGET_CAP);
if (param == NULL) {
continue;
}
type = (param->val & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
switch (type) {
case AC_WID_AUD_OUT:
case AC_WID_AUD_IN:
assert(node->stindex < ARRAY_SIZE(a->st));
st = a->st + node->stindex;
st->state = a;
st->node = node;
if (type == AC_WID_AUD_OUT) {
/* unmute output by default */
st->gain_left = QEMU_HDA_AMP_STEPS;
st->gain_right = QEMU_HDA_AMP_STEPS;
st->compat_bpos = sizeof(st->compat_buf);
st->output = true;
} else {
st->output = false;
}
st->format = AC_FMT_TYPE_PCM | AC_FMT_BITS_16 |
(1 << AC_FMT_CHAN_SHIFT);
hda_codec_parse_fmt(st->format, &st->as);
hda_audio_setup(st);
break;
}
}
}
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;
}
if (a->use_timer) {
timer_del(st->buft);
}
if (st->output) {
AUD_close_out(&a->card, st->voice.out);
} else {
AUD_close_in(&a->card, st->voice.in);
}
}
AUD_remove_card(&a->card);
}
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 && st->state->use_timer;
}
static const VMStateDescription vmstate_hda_audio_stream_buf = {
.name = "hda-audio-stream/buffer",
.version_id = 1,
.needed = vmstate_hda_audio_stream_buf_needed,
.fields = (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 = (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 * []) {
&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 = (VMStateField[]) {
VMSTATE_STRUCT_ARRAY(st, HDAAudioState, 4, 0,
vmstate_hda_audio_stream,
HDAAudioStream),
VMSTATE_BOOL_ARRAY(running_compat, HDAAudioState, 16),
VMSTATE_BOOL_ARRAY_V(running_real, HDAAudioState, 2 * 16, 2),
VMSTATE_END_OF_LIST()
}
};
static Property hda_audio_properties[] = {
DEFINE_AUDIO_PROPERTIES(HDAAudioState, card),
DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0),
DEFINE_PROP_BOOL("mixer", HDAAudioState, mixer, true),
DEFINE_PROP_BOOL("use-timer", HDAAudioState, use_timer, true),
DEFINE_PROP_END_OF_LIST(),
};
static void hda_audio_init_output(HDACodecDevice *hda, Error **errp)
{
HDAAudioState *a = HDA_AUDIO(hda);
const struct desc_codec *desc = &output_nomixemu;
if (!a->mixer) {
desc = &output_mixemu;
}
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_nomixemu;
if (!a->mixer) {
desc = &duplex_mixemu;
}
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 = &micro_nomixemu;
if (!a->mixer) {
desc = &micro_mixemu;
}
hda_audio_init(hda, desc, errp);
}
static void hda_audio_base_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
HDACodecDeviceClass *k = HDA_CODEC_DEVICE_CLASS(klass);
k->exit = hda_audio_exit;
k->command = hda_audio_command;
k->stream = hda_audio_stream;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->reset = hda_audio_reset;
dc->vmsd = &vmstate_hda_audio;
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, 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, 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, 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)