qemu-e2k/hw/intel-hda.c
Alexander Graf 2507c12ab0 Add endianness as io mem parameter
As stated before, devices can be little, big or native endian. The
target endianness is not of their concern, so we need to push things
down a level.

This patch adds a parameter to cpu_register_io_memory that allows a
device to choose its endianness. For now, all devices simply choose
native endian, because that's the same behavior as before.

Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Blue Swirl <blauwirbel@gmail.com>
2010-12-11 15:24:25 +00:00

1309 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"
/* --------------------------------------------------------------------- */
/* hda bus */
static struct BusInfo hda_codec_bus_info = {
.name = "HDA",
.size = sizeof(HDACodecBus),
.props = (Property[]) {
DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1),
DEFINE_PROP_END_OF_LIST()
}
};
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, DeviceInfo *base)
{
HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus);
HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
HDACodecDeviceInfo *info = DO_UPCAST(HDACodecDeviceInfo, qdev, base);
dev->info = info;
if (dev->cad == -1) {
dev->cad = bus->next_cad;
}
if (dev->cad >= 15) {
return -1;
}
bus->next_cad = dev->cad + 1;
return info->init(dev);
}
static int hda_codec_dev_exit(DeviceState *qdev)
{
HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
if (dev->info->exit) {
dev->info->exit(dev);
}
return 0;
}
void hda_codec_register(HDACodecDeviceInfo *info)
{
info->qdev.init = hda_codec_dev_init;
info->qdev.exit = hda_codec_dev_exit;
info->qdev.bus_info = &hda_codec_bus_info;
qdev_register(&info->qdev);
}
HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad)
{
DeviceState *qdev;
HDACodecDevice *cdev;
QLIST_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 */
int mmio_addr;
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 stl_phys_le(target_phys_addr_t addr, uint32_t value)
{
uint32_t value_le = cpu_to_le32(value);
cpu_physical_memory_write(addr, (uint8_t*)(&value_le), sizeof(value_le));
}
static uint32_t ldl_phys_le(target_phys_addr_t addr)
{
uint32_t value_le;
cpu_physical_memory_read(addr, (uint8_t*)(&value_le), sizeof(value_le));
return le32_to_cpu(value_le);
}
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;
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;
}
codec->info->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_phys_le(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_phys_le(addr + 8*wp, response);
stl_phys_le(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);
IntelHDAStream *st = NULL;
target_phys_addr_t addr;
uint32_t s, copy, left;
bool irq = false;
for (s = 0; s < ARRAY_SIZE(d->st); s++) {
if (stnr == ((d->st[s].ctl >> 20) & 0x0f)) {
st = d->st + s;
break;
}
}
if (st == NULL) {
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);
cpu_physical_memory_rw(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_phys_le(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;
qemu_free(st->bpl);
st->bpl = qemu_malloc(sizeof(bpl) * st->bentries);
for (i = 0; i < st->bentries; i++, addr += 16) {
cpu_physical_memory_read(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)
{
DeviceState *qdev;
HDACodecDevice *cdev;
QLIST_FOREACH(qdev, &d->codecs.qbus.children, sibling) {
cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
if (cdev->info->stream) {
cdev->info->stream(cdev, stream, running);
}
}
}
/* --------------------------------------------------------------------- */
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)
{
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);
} else {
/* stop */
dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr);
intel_hda_notify_codecs(d, stnr, false);
}
}
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 CPUReadMemoryFunc * const intel_hda_mmio_read[3] = {
intel_hda_mmio_readb,
intel_hda_mmio_readw,
intel_hda_mmio_readl,
};
static CPUWriteMemoryFunc * const intel_hda_mmio_write[3] = {
intel_hda_mmio_writeb,
intel_hda_mmio_writew,
intel_hda_mmio_writel,
};
static void intel_hda_map(PCIDevice *pci, int region_num,
pcibus_t addr, pcibus_t size, int type)
{
IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);
cpu_register_physical_memory(addr, 0x4000, d->mmio_addr);
}
/* --------------------------------------------------------------------- */
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(vm_clock);
/* reset codecs */
QLIST_FOREACH(qdev, &d->codecs.qbus.children, sibling) {
cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
if (qdev->info->reset) {
qdev->info->reset(qdev);
}
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 = d->pci.qdev.info->name;
pci_config_set_vendor_id(conf, PCI_VENDOR_ID_INTEL);
pci_config_set_device_id(conf, 0x2668);
pci_config_set_revision(conf, 1);
pci_config_set_class(conf, PCI_CLASS_MULTIMEDIA_HD_AUDIO);
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;
d->mmio_addr = cpu_register_io_memory(intel_hda_mmio_read,
intel_hda_mmio_write, d,
DEVICE_NATIVE_ENDIAN);
pci_register_bar(&d->pci, 0, 0x4000, PCI_BASE_ADDRESS_SPACE_MEMORY,
intel_hda_map);
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);
if (d->msi) {
msi_uninit(&d->pci);
}
cpu_unregister_io_memory(d->mmio_addr);
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 PCIDeviceInfo intel_hda_info = {
.qdev.name = "intel-hda",
.qdev.desc = "Intel HD Audio Controller",
.qdev.size = sizeof(IntelHDAState),
.qdev.vmsd = &vmstate_intel_hda,
.qdev.reset = intel_hda_reset,
.init = intel_hda_init,
.exit = intel_hda_exit,
.config_write = intel_hda_write_config,
.qdev.props = (Property[]) {
DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0),
DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1),
DEFINE_PROP_END_OF_LIST(),
}
};
static void intel_hda_register(void)
{
pci_qdev_register(&intel_hda_info);
}
device_init(intel_hda_register);
/*
* 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;
}