In qdev, each bus in practice identified an abstract superclass, but this was mostly hidden. In QOM, instead, these abstract classes are explicit so we can move bus properties there. All bus property walks are removed, and all device property walks are changed to look along the class hierarchy instead. We would have duplicates if class A defines some properties and its subclass B does not define any, because class_b->props will be left equal to class_a->props. The solution here is to reintroduce the class_base_init TypeInfo callback, that was present in one of the early QOM versions but removed (on my request...) before committing. This breaks global bus properties, an obscure feature when used with the command-line which is actually useful and used when used by backwards-compatible machine types. So this patch also adjusts the global bus properties in hw/pc_piix.c to refer to the abstract class. Globals and other properties must be modified in the same patch to avoid complications related to initialization ordering. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Andreas Färber <afaerber@suse.de>
1317 lines
39 KiB
C
1317 lines
39 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 "hw.h"
|
|
#include "pci.h"
|
|
#include "msi.h"
|
|
#include "qemu-timer.h"
|
|
#include "audiodev.h"
|
|
#include "intel-hda.h"
|
|
#include "intel-hda-defs.h"
|
|
#include "dma.h"
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
/* hda bus */
|
|
|
|
static Property hda_props[] = {
|
|
DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1),
|
|
DEFINE_PROP_END_OF_LIST()
|
|
};
|
|
|
|
static struct BusInfo hda_codec_bus_info = {
|
|
.name = "HDA",
|
|
.size = sizeof(HDACodecBus),
|
|
};
|
|
|
|
void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus,
|
|
hda_codec_response_func response,
|
|
hda_codec_xfer_func xfer)
|
|
{
|
|
qbus_create_inplace(&bus->qbus, &hda_codec_bus_info, dev, NULL);
|
|
bus->response = response;
|
|
bus->xfer = xfer;
|
|
}
|
|
|
|
static int hda_codec_dev_init(DeviceState *qdev)
|
|
{
|
|
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus);
|
|
HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
|
|
HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
|
|
|
|
if (dev->cad == -1) {
|
|
dev->cad = bus->next_cad;
|
|
}
|
|
if (dev->cad >= 15) {
|
|
return -1;
|
|
}
|
|
bus->next_cad = dev->cad + 1;
|
|
return cdc->init(dev);
|
|
}
|
|
|
|
static int hda_codec_dev_exit(DeviceState *qdev)
|
|
{
|
|
HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
|
|
HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);
|
|
|
|
if (cdc->exit) {
|
|
cdc->exit(dev);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad)
|
|
{
|
|
DeviceState *qdev;
|
|
HDACodecDevice *cdev;
|
|
|
|
QTAILQ_FOREACH(qdev, &bus->qbus.children, sibling) {
|
|
cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
|
|
if (cdev->cad == cad) {
|
|
return cdev;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response)
|
|
{
|
|
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
|
|
bus->response(dev, solicited, response);
|
|
}
|
|
|
|
bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
|
|
uint8_t *buf, uint32_t len)
|
|
{
|
|
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
|
|
return bus->xfer(dev, stnr, output, buf, len);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
/* intel hda emulation */
|
|
|
|
typedef struct IntelHDAStream IntelHDAStream;
|
|
typedef struct IntelHDAState IntelHDAState;
|
|
typedef struct IntelHDAReg IntelHDAReg;
|
|
|
|
typedef struct bpl {
|
|
uint64_t addr;
|
|
uint32_t len;
|
|
uint32_t flags;
|
|
} bpl;
|
|
|
|
struct IntelHDAStream {
|
|
/* registers */
|
|
uint32_t ctl;
|
|
uint32_t lpib;
|
|
uint32_t cbl;
|
|
uint32_t lvi;
|
|
uint32_t fmt;
|
|
uint32_t bdlp_lbase;
|
|
uint32_t bdlp_ubase;
|
|
|
|
/* state */
|
|
bpl *bpl;
|
|
uint32_t bentries;
|
|
uint32_t bsize, be, bp;
|
|
};
|
|
|
|
struct IntelHDAState {
|
|
PCIDevice pci;
|
|
const char *name;
|
|
HDACodecBus codecs;
|
|
|
|
/* registers */
|
|
uint32_t g_ctl;
|
|
uint32_t wake_en;
|
|
uint32_t state_sts;
|
|
uint32_t int_ctl;
|
|
uint32_t int_sts;
|
|
uint32_t wall_clk;
|
|
|
|
uint32_t corb_lbase;
|
|
uint32_t corb_ubase;
|
|
uint32_t corb_rp;
|
|
uint32_t corb_wp;
|
|
uint32_t corb_ctl;
|
|
uint32_t corb_sts;
|
|
uint32_t corb_size;
|
|
|
|
uint32_t rirb_lbase;
|
|
uint32_t rirb_ubase;
|
|
uint32_t rirb_wp;
|
|
uint32_t rirb_cnt;
|
|
uint32_t rirb_ctl;
|
|
uint32_t rirb_sts;
|
|
uint32_t rirb_size;
|
|
|
|
uint32_t dp_lbase;
|
|
uint32_t dp_ubase;
|
|
|
|
uint32_t icw;
|
|
uint32_t irr;
|
|
uint32_t ics;
|
|
|
|
/* streams */
|
|
IntelHDAStream st[8];
|
|
|
|
/* state */
|
|
MemoryRegion mmio;
|
|
uint32_t rirb_count;
|
|
int64_t wall_base_ns;
|
|
|
|
/* debug logging */
|
|
const IntelHDAReg *last_reg;
|
|
uint32_t last_val;
|
|
uint32_t last_write;
|
|
uint32_t last_sec;
|
|
uint32_t repeat_count;
|
|
|
|
/* properties */
|
|
uint32_t debug;
|
|
uint32_t msi;
|
|
};
|
|
|
|
struct IntelHDAReg {
|
|
const char *name; /* register name */
|
|
uint32_t size; /* size in bytes */
|
|
uint32_t reset; /* reset value */
|
|
uint32_t wmask; /* write mask */
|
|
uint32_t wclear; /* write 1 to clear bits */
|
|
uint32_t offset; /* location in IntelHDAState */
|
|
uint32_t shift; /* byte access entries for dwords */
|
|
uint32_t stream;
|
|
void (*whandler)(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old);
|
|
void (*rhandler)(IntelHDAState *d, const IntelHDAReg *reg);
|
|
};
|
|
|
|
static void intel_hda_reset(DeviceState *dev);
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
static target_phys_addr_t intel_hda_addr(uint32_t lbase, uint32_t ubase)
|
|
{
|
|
target_phys_addr_t addr;
|
|
|
|
#if TARGET_PHYS_ADDR_BITS == 32
|
|
addr = lbase;
|
|
#else
|
|
addr = ubase;
|
|
addr <<= 32;
|
|
addr |= lbase;
|
|
#endif
|
|
return addr;
|
|
}
|
|
|
|
static void intel_hda_update_int_sts(IntelHDAState *d)
|
|
{
|
|
uint32_t sts = 0;
|
|
uint32_t i;
|
|
|
|
/* update controller status */
|
|
if (d->rirb_sts & ICH6_RBSTS_IRQ) {
|
|
sts |= (1 << 30);
|
|
}
|
|
if (d->rirb_sts & ICH6_RBSTS_OVERRUN) {
|
|
sts |= (1 << 30);
|
|
}
|
|
if (d->state_sts & d->wake_en) {
|
|
sts |= (1 << 30);
|
|
}
|
|
|
|
/* update stream status */
|
|
for (i = 0; i < 8; i++) {
|
|
/* buffer completion interrupt */
|
|
if (d->st[i].ctl & (1 << 26)) {
|
|
sts |= (1 << i);
|
|
}
|
|
}
|
|
|
|
/* update global status */
|
|
if (sts & d->int_ctl) {
|
|
sts |= (1 << 31);
|
|
}
|
|
|
|
d->int_sts = sts;
|
|
}
|
|
|
|
static void intel_hda_update_irq(IntelHDAState *d)
|
|
{
|
|
int msi = d->msi && msi_enabled(&d->pci);
|
|
int level;
|
|
|
|
intel_hda_update_int_sts(d);
|
|
if (d->int_sts & (1 << 31) && d->int_ctl & (1 << 31)) {
|
|
level = 1;
|
|
} else {
|
|
level = 0;
|
|
}
|
|
dprint(d, 2, "%s: level %d [%s]\n", __FUNCTION__,
|
|
level, msi ? "msi" : "intx");
|
|
if (msi) {
|
|
if (level) {
|
|
msi_notify(&d->pci, 0);
|
|
}
|
|
} else {
|
|
qemu_set_irq(d->pci.irq[0], level);
|
|
}
|
|
}
|
|
|
|
static int intel_hda_send_command(IntelHDAState *d, uint32_t verb)
|
|
{
|
|
uint32_t cad, nid, data;
|
|
HDACodecDevice *codec;
|
|
HDACodecDeviceClass *cdc;
|
|
|
|
cad = (verb >> 28) & 0x0f;
|
|
if (verb & (1 << 27)) {
|
|
/* indirect node addressing, not specified in HDA 1.0 */
|
|
dprint(d, 1, "%s: indirect node addressing (guest bug?)\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
nid = (verb >> 20) & 0x7f;
|
|
data = verb & 0xfffff;
|
|
|
|
codec = hda_codec_find(&d->codecs, cad);
|
|
if (codec == NULL) {
|
|
dprint(d, 1, "%s: addressed non-existing codec\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
cdc = HDA_CODEC_DEVICE_GET_CLASS(codec);
|
|
cdc->command(codec, nid, data);
|
|
return 0;
|
|
}
|
|
|
|
static void intel_hda_corb_run(IntelHDAState *d)
|
|
{
|
|
target_phys_addr_t addr;
|
|
uint32_t rp, verb;
|
|
|
|
if (d->ics & ICH6_IRS_BUSY) {
|
|
dprint(d, 2, "%s: [icw] verb 0x%08x\n", __FUNCTION__, d->icw);
|
|
intel_hda_send_command(d, d->icw);
|
|
return;
|
|
}
|
|
|
|
for (;;) {
|
|
if (!(d->corb_ctl & ICH6_CORBCTL_RUN)) {
|
|
dprint(d, 2, "%s: !run\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
if ((d->corb_rp & 0xff) == d->corb_wp) {
|
|
dprint(d, 2, "%s: corb ring empty\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
if (d->rirb_count == d->rirb_cnt) {
|
|
dprint(d, 2, "%s: rirb count reached\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
rp = (d->corb_rp + 1) & 0xff;
|
|
addr = intel_hda_addr(d->corb_lbase, d->corb_ubase);
|
|
verb = ldl_le_pci_dma(&d->pci, addr + 4*rp);
|
|
d->corb_rp = rp;
|
|
|
|
dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __FUNCTION__, rp, verb);
|
|
intel_hda_send_command(d, verb);
|
|
}
|
|
}
|
|
|
|
static void intel_hda_response(HDACodecDevice *dev, bool solicited, uint32_t response)
|
|
{
|
|
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
|
|
IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
|
|
target_phys_addr_t addr;
|
|
uint32_t wp, ex;
|
|
|
|
if (d->ics & ICH6_IRS_BUSY) {
|
|
dprint(d, 2, "%s: [irr] response 0x%x, cad 0x%x\n",
|
|
__FUNCTION__, response, dev->cad);
|
|
d->irr = response;
|
|
d->ics &= ~(ICH6_IRS_BUSY | 0xf0);
|
|
d->ics |= (ICH6_IRS_VALID | (dev->cad << 4));
|
|
return;
|
|
}
|
|
|
|
if (!(d->rirb_ctl & ICH6_RBCTL_DMA_EN)) {
|
|
dprint(d, 1, "%s: rirb dma disabled, drop codec response\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
ex = (solicited ? 0 : (1 << 4)) | dev->cad;
|
|
wp = (d->rirb_wp + 1) & 0xff;
|
|
addr = intel_hda_addr(d->rirb_lbase, d->rirb_ubase);
|
|
stl_le_pci_dma(&d->pci, addr + 8*wp, response);
|
|
stl_le_pci_dma(&d->pci, addr + 8*wp + 4, ex);
|
|
d->rirb_wp = wp;
|
|
|
|
dprint(d, 2, "%s: [wp 0x%x] response 0x%x, extra 0x%x\n",
|
|
__FUNCTION__, wp, response, ex);
|
|
|
|
d->rirb_count++;
|
|
if (d->rirb_count == d->rirb_cnt) {
|
|
dprint(d, 2, "%s: rirb count reached (%d)\n", __FUNCTION__, d->rirb_count);
|
|
if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
|
|
d->rirb_sts |= ICH6_RBSTS_IRQ;
|
|
intel_hda_update_irq(d);
|
|
}
|
|
} else if ((d->corb_rp & 0xff) == d->corb_wp) {
|
|
dprint(d, 2, "%s: corb ring empty (%d/%d)\n", __FUNCTION__,
|
|
d->rirb_count, d->rirb_cnt);
|
|
if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
|
|
d->rirb_sts |= ICH6_RBSTS_IRQ;
|
|
intel_hda_update_irq(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
|
|
uint8_t *buf, uint32_t len)
|
|
{
|
|
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
|
|
IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
|
|
target_phys_addr_t addr;
|
|
uint32_t s, copy, left;
|
|
IntelHDAStream *st;
|
|
bool irq = false;
|
|
|
|
st = output ? d->st + 4 : d->st;
|
|
for (s = 0; s < 4; s++) {
|
|
if (stnr == ((st[s].ctl >> 20) & 0x0f)) {
|
|
st = st + s;
|
|
break;
|
|
}
|
|
}
|
|
if (s == 4) {
|
|
return false;
|
|
}
|
|
if (st->bpl == NULL) {
|
|
return false;
|
|
}
|
|
if (st->ctl & (1 << 26)) {
|
|
/*
|
|
* Wait with the next DMA xfer until the guest
|
|
* has acked the buffer completion interrupt
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
left = len;
|
|
while (left > 0) {
|
|
copy = left;
|
|
if (copy > st->bsize - st->lpib)
|
|
copy = st->bsize - st->lpib;
|
|
if (copy > st->bpl[st->be].len - st->bp)
|
|
copy = st->bpl[st->be].len - st->bp;
|
|
|
|
dprint(d, 3, "dma: entry %d, pos %d/%d, copy %d\n",
|
|
st->be, st->bp, st->bpl[st->be].len, copy);
|
|
|
|
pci_dma_rw(&d->pci, st->bpl[st->be].addr + st->bp, buf, copy, !output);
|
|
st->lpib += copy;
|
|
st->bp += copy;
|
|
buf += copy;
|
|
left -= copy;
|
|
|
|
if (st->bpl[st->be].len == st->bp) {
|
|
/* bpl entry filled */
|
|
if (st->bpl[st->be].flags & 0x01) {
|
|
irq = true;
|
|
}
|
|
st->bp = 0;
|
|
st->be++;
|
|
if (st->be == st->bentries) {
|
|
/* bpl wrap around */
|
|
st->be = 0;
|
|
st->lpib = 0;
|
|
}
|
|
}
|
|
}
|
|
if (d->dp_lbase & 0x01) {
|
|
addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase);
|
|
stl_le_pci_dma(&d->pci, addr + 8*s, st->lpib);
|
|
}
|
|
dprint(d, 3, "dma: --\n");
|
|
|
|
if (irq) {
|
|
st->ctl |= (1 << 26); /* buffer completion interrupt */
|
|
intel_hda_update_irq(d);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st)
|
|
{
|
|
target_phys_addr_t addr;
|
|
uint8_t buf[16];
|
|
uint32_t i;
|
|
|
|
addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase);
|
|
st->bentries = st->lvi +1;
|
|
g_free(st->bpl);
|
|
st->bpl = g_malloc(sizeof(bpl) * st->bentries);
|
|
for (i = 0; i < st->bentries; i++, addr += 16) {
|
|
pci_dma_read(&d->pci, addr, buf, 16);
|
|
st->bpl[i].addr = le64_to_cpu(*(uint64_t *)buf);
|
|
st->bpl[i].len = le32_to_cpu(*(uint32_t *)(buf + 8));
|
|
st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12));
|
|
dprint(d, 1, "bdl/%d: 0x%" PRIx64 " +0x%x, 0x%x\n",
|
|
i, st->bpl[i].addr, st->bpl[i].len, st->bpl[i].flags);
|
|
}
|
|
|
|
st->bsize = st->cbl;
|
|
st->lpib = 0;
|
|
st->be = 0;
|
|
st->bp = 0;
|
|
}
|
|
|
|
static void intel_hda_notify_codecs(IntelHDAState *d, uint32_t stream, bool running, bool output)
|
|
{
|
|
DeviceState *qdev;
|
|
HDACodecDevice *cdev;
|
|
|
|
QTAILQ_FOREACH(qdev, &d->codecs.qbus.children, sibling) {
|
|
HDACodecDeviceClass *cdc;
|
|
|
|
cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
|
|
cdc = HDA_CODEC_DEVICE_GET_CLASS(cdev);
|
|
if (cdc->stream) {
|
|
cdc->stream(cdev, stream, running, output);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
static void intel_hda_set_g_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
if ((d->g_ctl & ICH6_GCTL_RESET) == 0) {
|
|
intel_hda_reset(&d->pci.qdev);
|
|
}
|
|
}
|
|
|
|
static void intel_hda_set_wake_en(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_update_irq(d);
|
|
}
|
|
|
|
static void intel_hda_set_state_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_update_irq(d);
|
|
}
|
|
|
|
static void intel_hda_set_int_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_update_irq(d);
|
|
}
|
|
|
|
static void intel_hda_get_wall_clk(IntelHDAState *d, const IntelHDAReg *reg)
|
|
{
|
|
int64_t ns;
|
|
|
|
ns = qemu_get_clock_ns(vm_clock) - d->wall_base_ns;
|
|
d->wall_clk = (uint32_t)(ns * 24 / 1000); /* 24 MHz */
|
|
}
|
|
|
|
static void intel_hda_set_corb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_corb_run(d);
|
|
}
|
|
|
|
static void intel_hda_set_corb_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_corb_run(d);
|
|
}
|
|
|
|
static void intel_hda_set_rirb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
if (d->rirb_wp & ICH6_RIRBWP_RST) {
|
|
d->rirb_wp = 0;
|
|
}
|
|
}
|
|
|
|
static void intel_hda_set_rirb_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
intel_hda_update_irq(d);
|
|
|
|
if ((old & ICH6_RBSTS_IRQ) && !(d->rirb_sts & ICH6_RBSTS_IRQ)) {
|
|
/* cleared ICH6_RBSTS_IRQ */
|
|
d->rirb_count = 0;
|
|
intel_hda_corb_run(d);
|
|
}
|
|
}
|
|
|
|
static void intel_hda_set_ics(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
if (d->ics & ICH6_IRS_BUSY) {
|
|
intel_hda_corb_run(d);
|
|
}
|
|
}
|
|
|
|
static void intel_hda_set_st_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
|
|
{
|
|
bool output = reg->stream >= 4;
|
|
IntelHDAStream *st = d->st + reg->stream;
|
|
|
|
if (st->ctl & 0x01) {
|
|
/* reset */
|
|
dprint(d, 1, "st #%d: reset\n", reg->stream);
|
|
st->ctl = 0;
|
|
}
|
|
if ((st->ctl & 0x02) != (old & 0x02)) {
|
|
uint32_t stnr = (st->ctl >> 20) & 0x0f;
|
|
/* run bit flipped */
|
|
if (st->ctl & 0x02) {
|
|
/* start */
|
|
dprint(d, 1, "st #%d: start %d (ring buf %d bytes)\n",
|
|
reg->stream, stnr, st->cbl);
|
|
intel_hda_parse_bdl(d, st);
|
|
intel_hda_notify_codecs(d, stnr, true, output);
|
|
} else {
|
|
/* stop */
|
|
dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr);
|
|
intel_hda_notify_codecs(d, stnr, false, output);
|
|
}
|
|
}
|
|
intel_hda_update_irq(d);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
#define ST_REG(_n, _o) (0x80 + (_n) * 0x20 + (_o))
|
|
|
|
static const struct IntelHDAReg regtab[] = {
|
|
/* global */
|
|
[ ICH6_REG_GCAP ] = {
|
|
.name = "GCAP",
|
|
.size = 2,
|
|
.reset = 0x4401,
|
|
},
|
|
[ ICH6_REG_VMIN ] = {
|
|
.name = "VMIN",
|
|
.size = 1,
|
|
},
|
|
[ ICH6_REG_VMAJ ] = {
|
|
.name = "VMAJ",
|
|
.size = 1,
|
|
.reset = 1,
|
|
},
|
|
[ ICH6_REG_OUTPAY ] = {
|
|
.name = "OUTPAY",
|
|
.size = 2,
|
|
.reset = 0x3c,
|
|
},
|
|
[ ICH6_REG_INPAY ] = {
|
|
.name = "INPAY",
|
|
.size = 2,
|
|
.reset = 0x1d,
|
|
},
|
|
[ ICH6_REG_GCTL ] = {
|
|
.name = "GCTL",
|
|
.size = 4,
|
|
.wmask = 0x0103,
|
|
.offset = offsetof(IntelHDAState, g_ctl),
|
|
.whandler = intel_hda_set_g_ctl,
|
|
},
|
|
[ ICH6_REG_WAKEEN ] = {
|
|
.name = "WAKEEN",
|
|
.size = 2,
|
|
.wmask = 0x7fff,
|
|
.offset = offsetof(IntelHDAState, wake_en),
|
|
.whandler = intel_hda_set_wake_en,
|
|
},
|
|
[ ICH6_REG_STATESTS ] = {
|
|
.name = "STATESTS",
|
|
.size = 2,
|
|
.wmask = 0x7fff,
|
|
.wclear = 0x7fff,
|
|
.offset = offsetof(IntelHDAState, state_sts),
|
|
.whandler = intel_hda_set_state_sts,
|
|
},
|
|
|
|
/* interrupts */
|
|
[ ICH6_REG_INTCTL ] = {
|
|
.name = "INTCTL",
|
|
.size = 4,
|
|
.wmask = 0xc00000ff,
|
|
.offset = offsetof(IntelHDAState, int_ctl),
|
|
.whandler = intel_hda_set_int_ctl,
|
|
},
|
|
[ ICH6_REG_INTSTS ] = {
|
|
.name = "INTSTS",
|
|
.size = 4,
|
|
.wmask = 0xc00000ff,
|
|
.wclear = 0xc00000ff,
|
|
.offset = offsetof(IntelHDAState, int_sts),
|
|
},
|
|
|
|
/* misc */
|
|
[ ICH6_REG_WALLCLK ] = {
|
|
.name = "WALLCLK",
|
|
.size = 4,
|
|
.offset = offsetof(IntelHDAState, wall_clk),
|
|
.rhandler = intel_hda_get_wall_clk,
|
|
},
|
|
[ ICH6_REG_WALLCLK + 0x2000 ] = {
|
|
.name = "WALLCLK(alias)",
|
|
.size = 4,
|
|
.offset = offsetof(IntelHDAState, wall_clk),
|
|
.rhandler = intel_hda_get_wall_clk,
|
|
},
|
|
|
|
/* dma engine */
|
|
[ ICH6_REG_CORBLBASE ] = {
|
|
.name = "CORBLBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffff80,
|
|
.offset = offsetof(IntelHDAState, corb_lbase),
|
|
},
|
|
[ ICH6_REG_CORBUBASE ] = {
|
|
.name = "CORBUBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffffff,
|
|
.offset = offsetof(IntelHDAState, corb_ubase),
|
|
},
|
|
[ ICH6_REG_CORBWP ] = {
|
|
.name = "CORBWP",
|
|
.size = 2,
|
|
.wmask = 0xff,
|
|
.offset = offsetof(IntelHDAState, corb_wp),
|
|
.whandler = intel_hda_set_corb_wp,
|
|
},
|
|
[ ICH6_REG_CORBRP ] = {
|
|
.name = "CORBRP",
|
|
.size = 2,
|
|
.wmask = 0x80ff,
|
|
.offset = offsetof(IntelHDAState, corb_rp),
|
|
},
|
|
[ ICH6_REG_CORBCTL ] = {
|
|
.name = "CORBCTL",
|
|
.size = 1,
|
|
.wmask = 0x03,
|
|
.offset = offsetof(IntelHDAState, corb_ctl),
|
|
.whandler = intel_hda_set_corb_ctl,
|
|
},
|
|
[ ICH6_REG_CORBSTS ] = {
|
|
.name = "CORBSTS",
|
|
.size = 1,
|
|
.wmask = 0x01,
|
|
.wclear = 0x01,
|
|
.offset = offsetof(IntelHDAState, corb_sts),
|
|
},
|
|
[ ICH6_REG_CORBSIZE ] = {
|
|
.name = "CORBSIZE",
|
|
.size = 1,
|
|
.reset = 0x42,
|
|
.offset = offsetof(IntelHDAState, corb_size),
|
|
},
|
|
[ ICH6_REG_RIRBLBASE ] = {
|
|
.name = "RIRBLBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffff80,
|
|
.offset = offsetof(IntelHDAState, rirb_lbase),
|
|
},
|
|
[ ICH6_REG_RIRBUBASE ] = {
|
|
.name = "RIRBUBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffffff,
|
|
.offset = offsetof(IntelHDAState, rirb_ubase),
|
|
},
|
|
[ ICH6_REG_RIRBWP ] = {
|
|
.name = "RIRBWP",
|
|
.size = 2,
|
|
.wmask = 0x8000,
|
|
.offset = offsetof(IntelHDAState, rirb_wp),
|
|
.whandler = intel_hda_set_rirb_wp,
|
|
},
|
|
[ ICH6_REG_RINTCNT ] = {
|
|
.name = "RINTCNT",
|
|
.size = 2,
|
|
.wmask = 0xff,
|
|
.offset = offsetof(IntelHDAState, rirb_cnt),
|
|
},
|
|
[ ICH6_REG_RIRBCTL ] = {
|
|
.name = "RIRBCTL",
|
|
.size = 1,
|
|
.wmask = 0x07,
|
|
.offset = offsetof(IntelHDAState, rirb_ctl),
|
|
},
|
|
[ ICH6_REG_RIRBSTS ] = {
|
|
.name = "RIRBSTS",
|
|
.size = 1,
|
|
.wmask = 0x05,
|
|
.wclear = 0x05,
|
|
.offset = offsetof(IntelHDAState, rirb_sts),
|
|
.whandler = intel_hda_set_rirb_sts,
|
|
},
|
|
[ ICH6_REG_RIRBSIZE ] = {
|
|
.name = "RIRBSIZE",
|
|
.size = 1,
|
|
.reset = 0x42,
|
|
.offset = offsetof(IntelHDAState, rirb_size),
|
|
},
|
|
|
|
[ ICH6_REG_DPLBASE ] = {
|
|
.name = "DPLBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffff81,
|
|
.offset = offsetof(IntelHDAState, dp_lbase),
|
|
},
|
|
[ ICH6_REG_DPUBASE ] = {
|
|
.name = "DPUBASE",
|
|
.size = 4,
|
|
.wmask = 0xffffffff,
|
|
.offset = offsetof(IntelHDAState, dp_ubase),
|
|
},
|
|
|
|
[ ICH6_REG_IC ] = {
|
|
.name = "ICW",
|
|
.size = 4,
|
|
.wmask = 0xffffffff,
|
|
.offset = offsetof(IntelHDAState, icw),
|
|
},
|
|
[ ICH6_REG_IR ] = {
|
|
.name = "IRR",
|
|
.size = 4,
|
|
.offset = offsetof(IntelHDAState, irr),
|
|
},
|
|
[ ICH6_REG_IRS ] = {
|
|
.name = "ICS",
|
|
.size = 2,
|
|
.wmask = 0x0003,
|
|
.wclear = 0x0002,
|
|
.offset = offsetof(IntelHDAState, ics),
|
|
.whandler = intel_hda_set_ics,
|
|
},
|
|
|
|
#define HDA_STREAM(_t, _i) \
|
|
[ ST_REG(_i, ICH6_REG_SD_CTL) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " CTL", \
|
|
.size = 4, \
|
|
.wmask = 0x1cff001f, \
|
|
.offset = offsetof(IntelHDAState, st[_i].ctl), \
|
|
.whandler = intel_hda_set_st_ctl, \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_CTL) + 2] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " CTL(stnr)", \
|
|
.size = 1, \
|
|
.shift = 16, \
|
|
.wmask = 0x00ff0000, \
|
|
.offset = offsetof(IntelHDAState, st[_i].ctl), \
|
|
.whandler = intel_hda_set_st_ctl, \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_STS)] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " CTL(sts)", \
|
|
.size = 1, \
|
|
.shift = 24, \
|
|
.wmask = 0x1c000000, \
|
|
.wclear = 0x1c000000, \
|
|
.offset = offsetof(IntelHDAState, st[_i].ctl), \
|
|
.whandler = intel_hda_set_st_ctl, \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_LPIB) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " LPIB", \
|
|
.size = 4, \
|
|
.offset = offsetof(IntelHDAState, st[_i].lpib), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_LPIB) + 0x2000 ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " LPIB(alias)", \
|
|
.size = 4, \
|
|
.offset = offsetof(IntelHDAState, st[_i].lpib), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_CBL) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " CBL", \
|
|
.size = 4, \
|
|
.wmask = 0xffffffff, \
|
|
.offset = offsetof(IntelHDAState, st[_i].cbl), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_LVI) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " LVI", \
|
|
.size = 2, \
|
|
.wmask = 0x00ff, \
|
|
.offset = offsetof(IntelHDAState, st[_i].lvi), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_FIFOSIZE) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " FIFOS", \
|
|
.size = 2, \
|
|
.reset = HDA_BUFFER_SIZE, \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_FORMAT) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " FMT", \
|
|
.size = 2, \
|
|
.wmask = 0x7f7f, \
|
|
.offset = offsetof(IntelHDAState, st[_i].fmt), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_BDLPL) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " BDLPL", \
|
|
.size = 4, \
|
|
.wmask = 0xffffff80, \
|
|
.offset = offsetof(IntelHDAState, st[_i].bdlp_lbase), \
|
|
}, \
|
|
[ ST_REG(_i, ICH6_REG_SD_BDLPU) ] = { \
|
|
.stream = _i, \
|
|
.name = _t stringify(_i) " BDLPU", \
|
|
.size = 4, \
|
|
.wmask = 0xffffffff, \
|
|
.offset = offsetof(IntelHDAState, st[_i].bdlp_ubase), \
|
|
}, \
|
|
|
|
HDA_STREAM("IN", 0)
|
|
HDA_STREAM("IN", 1)
|
|
HDA_STREAM("IN", 2)
|
|
HDA_STREAM("IN", 3)
|
|
|
|
HDA_STREAM("OUT", 4)
|
|
HDA_STREAM("OUT", 5)
|
|
HDA_STREAM("OUT", 6)
|
|
HDA_STREAM("OUT", 7)
|
|
|
|
};
|
|
|
|
static const IntelHDAReg *intel_hda_reg_find(IntelHDAState *d, target_phys_addr_t addr)
|
|
{
|
|
const IntelHDAReg *reg;
|
|
|
|
if (addr >= sizeof(regtab)/sizeof(regtab[0])) {
|
|
goto noreg;
|
|
}
|
|
reg = regtab+addr;
|
|
if (reg->name == NULL) {
|
|
goto noreg;
|
|
}
|
|
return reg;
|
|
|
|
noreg:
|
|
dprint(d, 1, "unknown register, addr 0x%x\n", (int) addr);
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t *intel_hda_reg_addr(IntelHDAState *d, const IntelHDAReg *reg)
|
|
{
|
|
uint8_t *addr = (void*)d;
|
|
|
|
addr += reg->offset;
|
|
return (uint32_t*)addr;
|
|
}
|
|
|
|
static void intel_hda_reg_write(IntelHDAState *d, const IntelHDAReg *reg, uint32_t val,
|
|
uint32_t wmask)
|
|
{
|
|
uint32_t *addr;
|
|
uint32_t old;
|
|
|
|
if (!reg) {
|
|
return;
|
|
}
|
|
|
|
if (d->debug) {
|
|
time_t now = time(NULL);
|
|
if (d->last_write && d->last_reg == reg && d->last_val == val) {
|
|
d->repeat_count++;
|
|
if (d->last_sec != now) {
|
|
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
|
|
d->last_sec = now;
|
|
d->repeat_count = 0;
|
|
}
|
|
} else {
|
|
if (d->repeat_count) {
|
|
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
|
|
}
|
|
dprint(d, 2, "write %-16s: 0x%x (%x)\n", reg->name, val, wmask);
|
|
d->last_write = 1;
|
|
d->last_reg = reg;
|
|
d->last_val = val;
|
|
d->last_sec = now;
|
|
d->repeat_count = 0;
|
|
}
|
|
}
|
|
assert(reg->offset != 0);
|
|
|
|
addr = intel_hda_reg_addr(d, reg);
|
|
old = *addr;
|
|
|
|
if (reg->shift) {
|
|
val <<= reg->shift;
|
|
wmask <<= reg->shift;
|
|
}
|
|
wmask &= reg->wmask;
|
|
*addr &= ~wmask;
|
|
*addr |= wmask & val;
|
|
*addr &= ~(val & reg->wclear);
|
|
|
|
if (reg->whandler) {
|
|
reg->whandler(d, reg, old);
|
|
}
|
|
}
|
|
|
|
static uint32_t intel_hda_reg_read(IntelHDAState *d, const IntelHDAReg *reg,
|
|
uint32_t rmask)
|
|
{
|
|
uint32_t *addr, ret;
|
|
|
|
if (!reg) {
|
|
return 0;
|
|
}
|
|
|
|
if (reg->rhandler) {
|
|
reg->rhandler(d, reg);
|
|
}
|
|
|
|
if (reg->offset == 0) {
|
|
/* constant read-only register */
|
|
ret = reg->reset;
|
|
} else {
|
|
addr = intel_hda_reg_addr(d, reg);
|
|
ret = *addr;
|
|
if (reg->shift) {
|
|
ret >>= reg->shift;
|
|
}
|
|
ret &= rmask;
|
|
}
|
|
if (d->debug) {
|
|
time_t now = time(NULL);
|
|
if (!d->last_write && d->last_reg == reg && d->last_val == ret) {
|
|
d->repeat_count++;
|
|
if (d->last_sec != now) {
|
|
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
|
|
d->last_sec = now;
|
|
d->repeat_count = 0;
|
|
}
|
|
} else {
|
|
if (d->repeat_count) {
|
|
dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
|
|
}
|
|
dprint(d, 2, "read %-16s: 0x%x (%x)\n", reg->name, ret, rmask);
|
|
d->last_write = 0;
|
|
d->last_reg = reg;
|
|
d->last_val = ret;
|
|
d->last_sec = now;
|
|
d->repeat_count = 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void intel_hda_regs_reset(IntelHDAState *d)
|
|
{
|
|
uint32_t *addr;
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(regtab)/sizeof(regtab[0]); i++) {
|
|
if (regtab[i].name == NULL) {
|
|
continue;
|
|
}
|
|
if (regtab[i].offset == 0) {
|
|
continue;
|
|
}
|
|
addr = intel_hda_reg_addr(d, regtab + i);
|
|
*addr = regtab[i].reset;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
static void intel_hda_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
intel_hda_reg_write(d, reg, val, 0xff);
|
|
}
|
|
|
|
static void intel_hda_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
intel_hda_reg_write(d, reg, val, 0xffff);
|
|
}
|
|
|
|
static void intel_hda_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
intel_hda_reg_write(d, reg, val, 0xffffffff);
|
|
}
|
|
|
|
static uint32_t intel_hda_mmio_readb(void *opaque, target_phys_addr_t addr)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
return intel_hda_reg_read(d, reg, 0xff);
|
|
}
|
|
|
|
static uint32_t intel_hda_mmio_readw(void *opaque, target_phys_addr_t addr)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
return intel_hda_reg_read(d, reg, 0xffff);
|
|
}
|
|
|
|
static uint32_t intel_hda_mmio_readl(void *opaque, target_phys_addr_t addr)
|
|
{
|
|
IntelHDAState *d = opaque;
|
|
const IntelHDAReg *reg = intel_hda_reg_find(d, addr);
|
|
|
|
return intel_hda_reg_read(d, reg, 0xffffffff);
|
|
}
|
|
|
|
static const MemoryRegionOps intel_hda_mmio_ops = {
|
|
.old_mmio = {
|
|
.read = {
|
|
intel_hda_mmio_readb,
|
|
intel_hda_mmio_readw,
|
|
intel_hda_mmio_readl,
|
|
},
|
|
.write = {
|
|
intel_hda_mmio_writeb,
|
|
intel_hda_mmio_writew,
|
|
intel_hda_mmio_writel,
|
|
},
|
|
},
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
static void intel_hda_reset(DeviceState *dev)
|
|
{
|
|
IntelHDAState *d = DO_UPCAST(IntelHDAState, pci.qdev, dev);
|
|
DeviceState *qdev;
|
|
HDACodecDevice *cdev;
|
|
|
|
intel_hda_regs_reset(d);
|
|
d->wall_base_ns = qemu_get_clock_ns(vm_clock);
|
|
|
|
/* reset codecs */
|
|
QTAILQ_FOREACH(qdev, &d->codecs.qbus.children, sibling) {
|
|
cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
|
|
device_reset(DEVICE(cdev));
|
|
d->state_sts |= (1 << cdev->cad);
|
|
}
|
|
intel_hda_update_irq(d);
|
|
}
|
|
|
|
static int intel_hda_init(PCIDevice *pci)
|
|
{
|
|
IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);
|
|
uint8_t *conf = d->pci.config;
|
|
|
|
d->name = object_get_typename(OBJECT(d));
|
|
|
|
pci_config_set_interrupt_pin(conf, 1);
|
|
|
|
/* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */
|
|
conf[0x40] = 0x01;
|
|
|
|
memory_region_init_io(&d->mmio, &intel_hda_mmio_ops, d,
|
|
"intel-hda", 0x4000);
|
|
pci_register_bar(&d->pci, 0, 0, &d->mmio);
|
|
if (d->msi) {
|
|
msi_init(&d->pci, 0x50, 1, true, false);
|
|
}
|
|
|
|
hda_codec_bus_init(&d->pci.qdev, &d->codecs,
|
|
intel_hda_response, intel_hda_xfer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int intel_hda_exit(PCIDevice *pci)
|
|
{
|
|
IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);
|
|
|
|
msi_uninit(&d->pci);
|
|
memory_region_destroy(&d->mmio);
|
|
return 0;
|
|
}
|
|
|
|
static void intel_hda_write_config(PCIDevice *pci, uint32_t addr,
|
|
uint32_t val, int len)
|
|
{
|
|
IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);
|
|
|
|
pci_default_write_config(pci, addr, val, len);
|
|
if (d->msi) {
|
|
msi_write_config(pci, addr, val, len);
|
|
}
|
|
}
|
|
|
|
static int intel_hda_post_load(void *opaque, int version)
|
|
{
|
|
IntelHDAState* d = opaque;
|
|
int i;
|
|
|
|
dprint(d, 1, "%s\n", __FUNCTION__);
|
|
for (i = 0; i < ARRAY_SIZE(d->st); i++) {
|
|
if (d->st[i].ctl & 0x02) {
|
|
intel_hda_parse_bdl(d, &d->st[i]);
|
|
}
|
|
}
|
|
intel_hda_update_irq(d);
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_intel_hda_stream = {
|
|
.name = "intel-hda-stream",
|
|
.version_id = 1,
|
|
.fields = (VMStateField []) {
|
|
VMSTATE_UINT32(ctl, IntelHDAStream),
|
|
VMSTATE_UINT32(lpib, IntelHDAStream),
|
|
VMSTATE_UINT32(cbl, IntelHDAStream),
|
|
VMSTATE_UINT32(lvi, IntelHDAStream),
|
|
VMSTATE_UINT32(fmt, IntelHDAStream),
|
|
VMSTATE_UINT32(bdlp_lbase, IntelHDAStream),
|
|
VMSTATE_UINT32(bdlp_ubase, IntelHDAStream),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_intel_hda = {
|
|
.name = "intel-hda",
|
|
.version_id = 1,
|
|
.post_load = intel_hda_post_load,
|
|
.fields = (VMStateField []) {
|
|
VMSTATE_PCI_DEVICE(pci, IntelHDAState),
|
|
|
|
/* registers */
|
|
VMSTATE_UINT32(g_ctl, IntelHDAState),
|
|
VMSTATE_UINT32(wake_en, IntelHDAState),
|
|
VMSTATE_UINT32(state_sts, IntelHDAState),
|
|
VMSTATE_UINT32(int_ctl, IntelHDAState),
|
|
VMSTATE_UINT32(int_sts, IntelHDAState),
|
|
VMSTATE_UINT32(wall_clk, IntelHDAState),
|
|
VMSTATE_UINT32(corb_lbase, IntelHDAState),
|
|
VMSTATE_UINT32(corb_ubase, IntelHDAState),
|
|
VMSTATE_UINT32(corb_rp, IntelHDAState),
|
|
VMSTATE_UINT32(corb_wp, IntelHDAState),
|
|
VMSTATE_UINT32(corb_ctl, IntelHDAState),
|
|
VMSTATE_UINT32(corb_sts, IntelHDAState),
|
|
VMSTATE_UINT32(corb_size, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_lbase, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_ubase, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_wp, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_cnt, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_ctl, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_sts, IntelHDAState),
|
|
VMSTATE_UINT32(rirb_size, IntelHDAState),
|
|
VMSTATE_UINT32(dp_lbase, IntelHDAState),
|
|
VMSTATE_UINT32(dp_ubase, IntelHDAState),
|
|
VMSTATE_UINT32(icw, IntelHDAState),
|
|
VMSTATE_UINT32(irr, IntelHDAState),
|
|
VMSTATE_UINT32(ics, IntelHDAState),
|
|
VMSTATE_STRUCT_ARRAY(st, IntelHDAState, 8, 0,
|
|
vmstate_intel_hda_stream,
|
|
IntelHDAStream),
|
|
|
|
/* additional state info */
|
|
VMSTATE_UINT32(rirb_count, IntelHDAState),
|
|
VMSTATE_INT64(wall_base_ns, IntelHDAState),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property intel_hda_properties[] = {
|
|
DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0),
|
|
DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void intel_hda_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
|
|
|
k->init = intel_hda_init;
|
|
k->exit = intel_hda_exit;
|
|
k->config_write = intel_hda_write_config;
|
|
k->vendor_id = PCI_VENDOR_ID_INTEL;
|
|
k->device_id = 0x2668;
|
|
k->revision = 1;
|
|
k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO;
|
|
dc->desc = "Intel HD Audio Controller";
|
|
dc->reset = intel_hda_reset;
|
|
dc->vmsd = &vmstate_intel_hda;
|
|
dc->props = intel_hda_properties;
|
|
}
|
|
|
|
static TypeInfo intel_hda_info = {
|
|
.name = "intel-hda",
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(IntelHDAState),
|
|
.class_init = intel_hda_class_init,
|
|
};
|
|
|
|
static void hda_codec_device_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *k = DEVICE_CLASS(klass);
|
|
k->init = hda_codec_dev_init;
|
|
k->exit = hda_codec_dev_exit;
|
|
k->bus_info = &hda_codec_bus_info;
|
|
k->props = hda_props;
|
|
}
|
|
|
|
static TypeInfo hda_codec_device_type_info = {
|
|
.name = TYPE_HDA_CODEC_DEVICE,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_size = sizeof(HDACodecDevice),
|
|
.abstract = true,
|
|
.class_size = sizeof(HDACodecDeviceClass),
|
|
.class_init = hda_codec_device_class_init,
|
|
};
|
|
|
|
static void intel_hda_register_types(void)
|
|
{
|
|
type_register_static(&intel_hda_info);
|
|
type_register_static(&hda_codec_device_type_info);
|
|
}
|
|
|
|
type_init(intel_hda_register_types)
|
|
|
|
/*
|
|
* create intel hda controller with codec attached to it,
|
|
* so '-soundhw hda' works.
|
|
*/
|
|
int intel_hda_and_codec_init(PCIBus *bus)
|
|
{
|
|
PCIDevice *controller;
|
|
BusState *hdabus;
|
|
DeviceState *codec;
|
|
|
|
controller = pci_create_simple(bus, -1, "intel-hda");
|
|
hdabus = QLIST_FIRST(&controller->qdev.child_bus);
|
|
codec = qdev_create(hdabus, "hda-duplex");
|
|
qdev_init_nofail(codec);
|
|
return 0;
|
|
}
|
|
|