[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cdi-devel] [PATCH 3/2] hdaudio: Driver for Intel HD Audio



+ hdaudio: Added a CDI driver for Intel HD Audio. Tested on qemu and
  VirtualBox.

Signed-off-by: Kevin Wolf <kevin@xxxxxxxxxx>
---
 hdaudio/device.h  |  195 ++++++++++++++++++
 hdaudio/main.c    |  566 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/cdi/pci.h |    4 +
 3 files changed, 765 insertions(+), 0 deletions(-)
 create mode 100644 hdaudio/device.h
 create mode 100644 hdaudio/main.c

diff --git a/hdaudio/device.h b/hdaudio/device.h
new file mode 100644
index 0000000..8552521
--- /dev/null
+++ b/hdaudio/device.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2010 The tyndur Project. All rights reserved.
+ *
+ * This code is derived from software contributed to the tyndur Project
+ * by Kevin Wolf.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DEVICE_H
+#define DEVICE_H
+
+#include <stdint.h>
+
+#include "cdi/audio.h"
+#include "cdi/mem.h"
+
+#define BDL_SIZE                      4
+#define BUFFER_SIZE             0x10000
+
+#define BDL_BYTES_ROUNDED \
+    ((BDL_SIZE * sizeof(struct hda_bdl_entry) + 127) & ~127)
+
+#define SAMPLES_PER_BUFFER (BUFFER_SIZE / 2)
+
+/** HD Audio Memory Mapped Registers */
+enum hda_reg {
+    REG_GCTL        = 0x08, ///< GCTL - Global Control
+    REG_WAKEEN      = 0x0c, ///< WAKEEN - Wake Enable
+    REG_STATESTS    = 0x0e, ///< STATESTS - State Change Status
+    REG_INTCTL      = 0x20, ///< INTCTL - Interrupt Control
+    REG_INTSTS      = 0x24, ///< INTSTS - Interrupt Status
+
+    REG_CORBLBASE   = 0x40, ///< CORBLBASE - CORB Lower Base Address
+    REG_CORBUBASE   = 0x44, ///< CORBUBASE - CORB Upper Base Address
+    REG_CORBWP      = 0x48, ///< CORBWP - CORB Write Pointer
+    REG_CORBRP      = 0x4a, ///< CORBRP - CORB Read Pointer
+    REG_CORBCTL     = 0x4c, ///< CORBCTL - CORB Control
+    REG_CORBSIZE    = 0x4e, ///< CORBSIZE - CORB size
+
+    REG_RIRBLBASE   = 0x50, ///< RIRBLBASE - RIRB Lower Base Address
+    REG_RIRBUBASE   = 0x54, ///< RIRBUBASE - RIRB Upper Base Address
+    REG_RIRBWP      = 0x58, ///< RIRBWP - RIRB Write Pointer
+    REG_RINTCNT     = 0x5a, ///< RINTCNT - Respone Interrupt Count
+    REG_RIRBCTL     = 0x5c, ///< RIRBCTL - RIRB Control
+    REG_RIRBSTS     = 0x5d, ///< RIRBSTS - RIRB Interrupt Status
+    REG_RIRBSIZE    = 0x5e, ///< RIRBSIZE - RIRB size
+
+    REG_DPLBASE     = 0x70, ///< DPLBASE - DMA Position Lower Base Address
+    REG_DPUBASE     = 0x74, ///< DPUBASE - DMA Posiition Upper Base Address
+
+    REG_O0_CTLL     = 0x100,    ///< Output 0 - Control Lower
+    REG_O0_CTLU     = 0x102,    ///< Output 0 - Control Upper
+    REG_O0_STS      = 0x103,    ///< Output 0 - Status
+    REG_O0_CBL      = 0x108,    ///< Output 0 - Cyclic Buffer Length
+    REG_O0_STLVI    = 0x10c,    ///< Output 0 - Last Valid Index
+    REG_O0_BDLPL    = 0x118,    ///< Output 0 - BDL Pointer Lower
+    REG_O0_BDLPU    = 0x11c,    ///< Output 0 - BDL Pointer Upper
+};
+
+/* GCTL bits */
+enum hda_reg_gctl {
+    GCTL_RESET  =   (1 << 0),
+};
+
+/* CORBCTL bits */
+enum hda_reg_corbctl {
+    CORBCTL_CORBRUN = (1 << 1),
+};
+
+/* RIRBCTL bits */
+enum hda_reg_rirbctl {
+    RIRBCTL_RIRBRUN = (1 << 1),
+};
+
+/* Stream Descriptor Control Register bits */
+enum hda_reg_sdctl {
+    SDCTL_RUN   =   0x2, ///< Enable DMA Engine
+    SDCTL_IOCE  =   0x4, ///< Enable Interrupt on Complete
+};
+
+/** Represents an address of an output widget */
+struct hda_output {
+    struct cdi_audio_stream stream;
+
+    uint8_t     codec;
+    uint16_t    nid;
+
+    uint32_t    sample_rate;
+    int         num_channels;
+};
+
+/** Buffer Descriptor List Entry */
+struct hda_bdl_entry {
+    uint64_t paddr;
+    uint32_t length;
+    uint32_t flags;
+} __attribute__((packed));
+
+/** Represents the state of an HD Audio PCI card */
+struct hda_device {
+    struct cdi_audio_device audio;
+
+    struct hda_output output;
+
+    struct cdi_mem_area* mmio;
+    uintptr_t mmio_base;
+
+
+    struct cdi_mem_area*    rings;          ///< Buffer for CORB and RIRB
+    uint32_t*               corb;           ///< Command Outbound Ring Buffer
+    volatile uint64_t*      rirb;           ///< Response Inbound Ring Buffer
+    struct hda_bdl_entry*   bdl;            ///< Buffer Descriptor List
+    volatile uint64_t*      dma_pos;        ///< DMA Position in Current Buffer
+
+    size_t                  corb_entries;   ///< Number of CORB entries
+    size_t                  rirb_entries;   ///< Number of RIRB entries
+    uint16_t                rirbrp;         ///< RIRB Read Pointer
+
+    struct cdi_mem_area*    buffer;
+    int                     buffers_completed;
+};
+
+static inline void reg_outl(struct hda_device* hda, int reg, uint32_t value) {
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    *mmio = value;
+}
+
+static inline uint32_t reg_inl(struct hda_device* hda, int reg) {
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    return *mmio;
+}
+
+
+static inline void reg_outw(struct hda_device* hda, int reg, uint16_t value) {
+    volatile uint16_t* mmio = (uint16_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    *mmio = value;
+}
+
+static inline uint16_t reg_inw(struct hda_device* hda, int reg) {
+    volatile uint16_t* mmio = (uint16_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    return *mmio;
+}
+
+
+static inline void reg_outb(struct hda_device* hda, int reg, uint8_t value) {
+    volatile uint8_t* mmio = (uint8_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    *mmio = value;
+}
+
+static inline uint8_t reg_inb(struct hda_device* hda, int reg) {
+    volatile uint8_t* mmio = (uint8_t*) ((uint8_t*) hda->mmio->vaddr + reg);
+    return *mmio;
+}
+
+enum codec_verbs {
+    VERB_GET_PARAMETER      = 0xf0000,
+    VERB_SET_STREAM_CHANNEL = 0x70600,
+    VERB_SET_FORMAT         = 0x20000,
+    VERB_SET_AMP_GAIN_MUTE  = 0x30000,
+};
+
+enum codec_parameters {
+    PARAM_NODE_COUNT        = 0x04,
+    PARAM_AUDIO_WID_CAP     = 0x09,
+};
+
+enum sample_format {
+    SR_48_KHZ               = 0,
+    SR_44_KHZ               = (1 << 14),
+    BITS_32                 = (4 <<  4),
+    BITS_16                 = (1 <<  4),
+};
+
+
+#endif
diff --git a/hdaudio/main.c b/hdaudio/main.c
new file mode 100644
index 0000000..ce9e506
--- /dev/null
+++ b/hdaudio/main.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2010 The tyndur Project. All rights reserved.
+ *
+ * This code is derived from software contributed to the tyndur Project
+ * by Kevin Wolf.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "cdi/pci.h"
+#include "cdi/misc.h"
+
+#include "device.h"
+
+#define DRIVER_NAME "hdaudio"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DPRINTF(fmt, ...) \
+    do { stdout = NULL; printf(DRIVER_NAME ": " fmt, ##__VA_ARGS__); } while(0)
+#else
+#define DPRINTF(...) do {} while(0)
+#endif
+
+static struct cdi_audio_driver driver;
+
+static void hda_handle_interrupt(struct cdi_device* device)
+{
+    struct hda_device* hda = (struct hda_device*) device;
+    uint32_t isr = reg_inl(hda, REG_INTSTS);
+    uint8_t sts = reg_inb(hda, REG_O0_STS);
+
+    DPRINTF("hda_handle_interrupt, status %x, O0 status %x [%d]\n",
+        isr, sts, hda->buffers_completed);
+
+    /* An interrupt for stream 1 means that a buffer has completed */
+    if (sts & 0x4) {
+        cdi_audio_buffer_completed(&hda->output.stream,
+            hda->buffers_completed);
+
+        hda->buffers_completed++;
+        hda->buffers_completed %= BDL_SIZE;
+    }
+
+    /* Reset interrupt status registers */
+    reg_outl(hda, REG_INTSTS, isr);
+    reg_outb(hda, REG_O0_STS, sts);
+}
+
+static void setup_corb(struct hda_device* hda)
+{
+    uint8_t reg;
+    uint64_t corb_base;
+
+    reg = reg_inb(hda, REG_CORBSIZE);
+
+    /* Check CORB size capabilities and choose the largest size */
+    if (reg & (1 << 6)) {
+        hda->corb_entries = 256;
+        reg |= 0x2;
+    } else if (reg & (1 << 5)) {
+        hda->corb_entries = 16;
+        reg |= 0x1;
+    } else if (reg & (1 << 4)) {
+        hda->corb_entries = 2;
+        reg |= 0x0;
+    } else {
+        printf("hdaudio: No supported CORB size!\n");
+    }
+
+    reg_outb(hda, REG_CORBSIZE, reg);
+
+    /* Set CORB base address */
+    corb_base = (uintptr_t) hda->rings->paddr.items[0].start + 0;
+    reg_outl(hda, REG_CORBLBASE, corb_base & 0xffffffff);
+    reg_outl(hda, REG_CORBUBASE, corb_base >> 32);
+
+    /* Start DMA engine */
+    reg_outb(hda, REG_CORBCTL, 0x02);
+}
+
+static void setup_rirb(struct hda_device* hda)
+{
+    uint8_t reg;
+    uint64_t rirb_base;
+
+    reg = reg_inb(hda, REG_RIRBSIZE);
+
+    /* Check RIRB size capabilities and choose the largest size */
+    if (reg & (1 << 6)) {
+        hda->rirb_entries = 256;
+        reg |= 0x2;
+    } else if (reg & (1 << 5)) {
+        hda->rirb_entries = 16;
+        reg |= 0x1;
+    } else if (reg & (1 << 4)) {
+        hda->rirb_entries = 2;
+        reg |= 0x0;
+    } else {
+        printf("hdaudio: No supported RIRB size!\n");
+    }
+
+    reg_outb(hda, REG_RIRBSIZE, reg);
+
+    /* Set RIRB base address */
+    rirb_base = (uintptr_t) hda->rings->paddr.items[0].start + 1024;
+    reg_outl(hda, REG_RIRBLBASE, rirb_base & 0xffffffff);
+    reg_outl(hda, REG_RIRBUBASE, rirb_base >> 32);
+
+    reg_outb(hda, REG_RINTCNT, 0x42); /* TODO Is this only needed for qemu? */
+
+    /* Start DMA engine */
+    reg_outb(hda, REG_RIRBCTL, 0x02);
+}
+
+static void corb_write(struct hda_device* hda, uint32_t verb)
+{
+    uint16_t wp = reg_inw(hda, REG_CORBWP) & 0xff;
+    uint16_t rp;
+    uint16_t next;
+
+    /* Wait until there's a free entry in the CORB */
+    next = (wp + 1) % hda->corb_entries;
+
+    do {
+        rp = reg_inw(hda, REG_CORBRP) & 0xff;
+    } while (next == rp);
+
+    /* Write to CORB */
+    hda->corb[next] = verb;
+    reg_outw(hda, REG_CORBWP, next);
+}
+
+static void rirb_read(struct hda_device* hda, uint64_t* response)
+{
+    uint16_t wp;
+    uint16_t rp = hda->rirbrp;
+
+    /* Wait for an unread entry in the RIRB */
+    do {
+       wp = reg_inw(hda, REG_RIRBWP) & 0xff;
+    } while (wp == rp);
+
+    /* Read from RIRB */
+    rp = (rp + 1) % hda->rirb_entries;
+    hda->rirbrp = rp;
+
+    *response = hda->rirb[rp];
+    reg_outb(hda, REG_RIRBSTS, 0x5); /* TODO Is this only needed for qemu? */
+}
+
+static uint32_t codec_query(struct hda_device* hda, int codec, int nid,
+    uint32_t payload)
+{
+    uint64_t response;
+    uint32_t verb =
+        ((codec & 0xf) << 28) |
+        ((nid & 0xff) << 20) |
+        (payload & 0xfffff);
+
+    corb_write(hda, verb);
+    rirb_read(hda, &response);
+
+    return response & 0xffffffff;
+}
+
+static void configure_output_widget(struct hda_device* hda)
+{
+    /* Set sample format */
+    codec_query(hda, hda->output.codec, hda->output.nid,
+        VERB_SET_FORMAT |
+        BITS_16 | hda->output.sample_rate |
+        (hda->output.num_channels - 1));
+}
+
+static void init_output_widget(struct hda_device* hda)
+{
+    /* CDI stream properties */
+    hda->output.stream.device = &hda->audio;
+    hda->output.stream.num_buffers = BDL_SIZE;
+    hda->output.stream.buffer_size = BUFFER_SIZE / 2;
+    hda->output.stream.sample_format = CDI_AUDIO_16SI;
+
+    /* Select the right HDA stream/channel (0:0) */
+    codec_query(hda, hda->output.codec, hda->output.nid,
+        VERB_SET_STREAM_CHANNEL | 0x10);
+
+    /* Set sample rate etc. */
+    hda->output.sample_rate = SR_48_KHZ;
+    hda->output.num_channels = 2;
+    configure_output_widget(hda);
+}
+
+static int find_output_widget(struct hda_device* hda, int codec)
+{
+    uint32_t param;
+    int num_fg, num_widgets;
+    int fg_start, widgets_start;
+    int i, j;
+
+    param = codec_query(hda, codec, 0, VERB_GET_PARAMETER | PARAM_NODE_COUNT);
+
+    num_fg = param & 0xff;
+    fg_start = (param >> 16) & 0xff;
+
+    DPRINTF("    %d function groups starting at ID %d\n", num_fg, fg_start);
+
+    for (i = 0; i < num_fg; i++) {
+        param = codec_query(hda, codec, fg_start + i,
+            VERB_GET_PARAMETER | PARAM_NODE_COUNT);
+
+        num_widgets = param & 0xff;
+        widgets_start = (param >> 16) & 0xff;
+
+        DPRINTF("    %d widgets starting at ID %d\n",
+            num_widgets, widgets_start);
+        for (j = 0; i < num_widgets; j++) {
+            param = codec_query(hda, codec, widgets_start + j,
+                VERB_GET_PARAMETER | PARAM_AUDIO_WID_CAP);
+            if ((param != 0) && ((param & 0xf00000) == 0)) {
+                DPRINTF("    Audio output at ID %d!\n", widgets_start + j);
+
+                hda->output.codec = codec;
+                hda->output.nid = widgets_start + j;
+
+                return 0;
+            }
+        }
+    }
+
+    return -1;
+}
+
+static void hda_enumerate_codecs(struct hda_device* hda)
+{
+    uint16_t statests;
+    int i;
+
+    DPRINTF("hda_enumerate_codecs\n");
+    statests = reg_inw(hda, REG_STATESTS);
+
+    /*
+     * Our model of the hardware is completely wrong: Our hda_device is the PCI
+     * card when in fact it should be a device on an HDA bus which is provided
+     * by the PCI device. This mean that we're limited to using a single
+     * output.
+     *
+     * So let's just pick the first output widget and use that one.
+     */
+    for (i = 0; i < 15; i++) {
+        if ((statests & (1 << i))) {
+            DPRINTF("   Found codec at %d\n", i);
+            if (find_output_widget(hda, i)) {
+                return;
+            }
+        }
+    }
+}
+
+static void hda_reset(struct hda_device* hda)
+{
+    DPRINTF("hda_reset\n");
+
+    /* Must clear CORB/RIRB RUN bits before reset */
+    reg_outl(hda, REG_CORBCTL, 0);
+    reg_outl(hda, REG_RIRBCTL, 0);
+    while ((reg_inl(hda, REG_CORBCTL) & CORBCTL_CORBRUN)
+        || (reg_inl(hda, REG_RIRBCTL) & RIRBCTL_RIRBRUN));
+
+    /* Reset the CRST bit and wait until hardware is in reset */
+    reg_outl(hda, REG_GCTL, 0);
+    while ((reg_inl(hda, REG_GCTL) & GCTL_RESET));
+
+    /* TODO Need a delay here? */
+
+    /* Take the hardware out of reset */
+    reg_outl(hda, REG_GCTL, GCTL_RESET);
+    while ((reg_inl(hda, REG_GCTL) & GCTL_RESET) == 0);
+
+    /* We want to get all interrupts */
+    reg_outw(hda, REG_WAKEEN, 0xffff);
+    reg_outl(hda, REG_INTCTL, 0x800000ff);
+
+    /* Setup data structures */
+    setup_corb(hda);
+    setup_rirb(hda);
+    DPRINTF("CORB size: %d entries; RIRB size: %d entries\n",
+        hda->corb_entries, hda->rirb_entries);
+
+    /* Need to wait 25 frames (521 µs) before enumerating codecs */
+    cdi_sleep_ms(1);
+    hda_enumerate_codecs(hda);
+}
+
+static void init_stream_descriptor(struct hda_device* hda)
+{
+    uint64_t bdl_base, dma_pos_base;
+    int i;
+
+    /* Set some Stream Descriptor registers for Output 0 */
+    reg_outb(hda, REG_O0_CTLU, 0x10);
+    reg_outl(hda, REG_O0_CBL, BDL_SIZE * BUFFER_SIZE);
+    reg_outw(hda, REG_O0_STLVI, BDL_SIZE - 1);
+
+    /* Setup Buffer Description List */
+    bdl_base = (uintptr_t) hda->rings->paddr.items[0].start + 3072;
+    reg_outl(hda, REG_O0_BDLPL, bdl_base & 0xffffffff);
+    reg_outl(hda, REG_O0_BDLPU, bdl_base >> 32);
+
+    for (i = 0; i < BDL_SIZE; i++) {
+        hda->bdl[i].paddr =
+            hda->buffer->paddr.items[0].start + (i * BUFFER_SIZE);
+        hda->bdl[i].length = BUFFER_SIZE;
+        hda->bdl[i].flags = 1;
+    }
+
+    memset(hda->buffer->vaddr, 0, BDL_SIZE * BUFFER_SIZE);
+
+    /* Initialise DMA Position in Buffer structure */
+    for (i = 0; i < 8; i++) {
+        hda->dma_pos[i] = 0;
+    }
+
+    dma_pos_base = (uintptr_t) hda->rings->paddr.items[0].start + 3072
+        + BDL_BYTES_ROUNDED;
+    reg_outl(hda, REG_DPLBASE, (dma_pos_base & 0xffffffff) | 0x1); /* TODO */
+    reg_outl(hda, REG_DPUBASE, dma_pos_base >> 32);
+}
+
+static struct cdi_device* hda_init_device(struct cdi_bus_data* bus_data)
+{
+    struct cdi_pci_device* pci = (struct cdi_pci_device*) bus_data;
+    struct hda_device* hda;
+    int i;
+
+    /* Check PCI class/subclass */
+    if (pci->class_id != PCI_CLASS_MULTIMEDIA ||
+        pci->subclass_id != PCI_SUBCLASS_MM_HDAUDIO)
+    {
+        return NULL;
+    }
+
+    /* Initialise device structure */
+    hda = calloc(1, sizeof(*hda));
+
+    hda->rings = cdi_mem_alloc(1024 + 2048 + BDL_BYTES_ROUNDED + 128,
+        CDI_MEM_PHYS_CONTIGUOUS | 7);
+    if (hda->rings == NULL) {
+        goto fail;
+    }
+
+    hda->corb       = (uint32_t*) ((uintptr_t) hda->rings->vaddr + 0);
+    hda->rirb       = (uint64_t*) ((uintptr_t) hda->rings->vaddr + 1024);
+    hda->bdl        = (void*    ) ((uintptr_t) hda->rings->vaddr + 3072);
+    hda->dma_pos    = (uint64_t*) ((uintptr_t) hda->rings->vaddr + 3072
+                        + BDL_BYTES_ROUNDED);
+
+    hda->buffer = cdi_mem_alloc(BDL_SIZE * BUFFER_SIZE,
+        CDI_MEM_PHYS_CONTIGUOUS | 7);
+    if (hda->buffer == NULL) {
+        goto fail;
+    }
+
+    /* Get information from PCI config space */
+    cdi_list_t reslist = pci->resources;
+    struct cdi_pci_resource* res;
+    for (i = 0; (res = cdi_list_get(reslist, i)); i++) {
+        if (res->type == CDI_PCI_MEMORY) {
+            hda->mmio = cdi_mem_map(res->start, res->length);
+            if (hda->mmio == NULL) {
+                goto fail;
+            }
+        }
+    }
+
+    cdi_register_irq(pci->irq, hda_handle_interrupt, &hda->audio.dev);
+    cdi_pci_alloc_ioports(pci);
+
+    /* Initialise hardware */
+    hda_reset(hda);
+    init_output_widget(hda);
+    init_stream_descriptor(hda);
+
+    hda->audio.record = 0;
+    hda->audio.streams = cdi_list_create();
+    cdi_list_push(hda->audio.streams, &hda->output.stream);
+
+    driver.set_volume(&hda->output.stream, 255);
+
+    return &hda->audio.dev;
+
+fail:
+    if (hda->buffer) {
+        cdi_mem_free(hda->buffer);
+    }
+    if (hda->rings) {
+        cdi_mem_free(hda->rings);
+    }
+
+    free(hda);
+    return NULL;
+}
+
+static void hda_remove_device(struct cdi_device* device)
+{
+    /* TODO */
+}
+
+
+static int hda_driver_init(void)
+{
+    cdi_driver_init(&driver.drv);
+
+    return 0;
+}
+
+static int hda_driver_destroy(void)
+{
+    cdi_driver_destroy(&driver.drv);
+
+    return 0;
+}
+
+static void hda_set_volume(struct cdi_audio_stream* stream, uint8_t volume)
+{
+    struct hda_device* hda = (struct hda_device*) stream->device;
+    int meta = 0xb000; /* Output Amp, Left and Right */
+
+    if (volume == 0) {
+        /* Set the mute bit */
+        volume = 0x80;
+    } else {
+        /* Scale to 7-bit value */
+        volume >>= 1;
+    }
+
+    codec_query(hda, hda->output.codec, hda->output.nid,
+        VERB_SET_AMP_GAIN_MUTE | meta | volume);
+}
+
+static int hda_set_sample_rate(struct cdi_audio_stream* stream,
+    int sample_rate)
+{
+    struct hda_device* hda = (struct hda_device*) stream->device;
+
+    switch (sample_rate) {
+        case 44100:
+            hda->output.sample_rate = SR_44_KHZ;
+            break;
+        default:
+            sample_rate = 48000;
+            hda->output.sample_rate = SR_48_KHZ;
+            break;
+    }
+
+    configure_output_widget(hda);
+
+    return sample_rate;
+}
+
+static int hda_set_channel_count(struct cdi_audio_device* dev, int channels)
+{
+    struct hda_device* hda = (struct hda_device*) dev;
+
+    if (channels < 1 || channels > 2) {
+        channels = 2;
+    }
+
+    hda->output.num_channels = channels;
+    configure_output_widget(hda);
+
+    return channels;
+}
+
+static int hda_transfer_data(struct cdi_audio_stream* stream, size_t buffer,
+    struct cdi_mem_area* memory, size_t offset)
+{
+    struct hda_device* hda = (struct hda_device*) stream->device;
+
+    DPRINTF("hda_transfer_data: buf %d, off %d\n", buffer, offset);
+
+    if (buffer >= BDL_SIZE || offset >= SAMPLES_PER_BUFFER) {
+        return -1;
+    }
+
+    memcpy((uint8_t*)hda->buffer->vaddr + buffer * BUFFER_SIZE + (offset * 2),
+        memory->vaddr,
+        (SAMPLES_PER_BUFFER - offset) * 2);
+
+    return 0;
+}
+
+static cdi_audio_status_t hda_change_status(struct cdi_audio_device* device,
+    cdi_audio_status_t status)
+{
+    struct hda_device* hda = (struct hda_device*) device;
+    uint16_t ctl;
+
+    DPRINTF("hda_change_status: %d\n", status);
+
+    if (status == CDI_AUDIO_PLAY) {
+        ctl = SDCTL_RUN | SDCTL_IOCE;
+    } else {
+        ctl = 0;
+    }
+
+    reg_outw(hda, REG_O0_CTLL, ctl);
+
+    return status;
+}
+
+static void hda_get_position(struct cdi_audio_stream* stream,
+    cdi_audio_position_t* position)
+{
+    struct hda_device* hda = (struct hda_device*) stream->device;
+    uint32_t pos = hda->dma_pos[4] & 0xffffffff;
+
+    position->buffer = pos / BUFFER_SIZE;
+    position->frame = (pos % BUFFER_SIZE) / 2;
+
+    DPRINTF("hda_get_position: %d/%d\n", position->buffer, position->frame);
+}
+
+static struct cdi_audio_driver driver = {
+    .drv = {
+        .name           = DRIVER_NAME,
+        .type           = CDI_AUDIO,
+        .bus            = CDI_PCI,
+        .init           = hda_driver_init,
+        .destroy        = hda_driver_destroy,
+        .init_device    = hda_init_device,
+        .remove_device  = hda_remove_device,
+    },
+
+    .set_volume             = hda_set_volume,
+    .set_sample_rate        = hda_set_sample_rate,
+    .set_number_of_channels = hda_set_channel_count,
+    .transfer_data          = hda_transfer_data,
+    .change_device_status   = hda_change_status,
+    .get_position           = hda_get_position,
+};
+
+CDI_DRIVER(DRIVER_NAME, driver)
diff --git a/include/cdi/pci.h b/include/cdi/pci.h
index 23bea62..d60a105 100644
--- a/include/cdi/pci.h
+++ b/include/cdi/pci.h
@@ -17,6 +17,10 @@
 #include <cdi-osdep.h>
 #include <cdi/lists.h>
 
+#define PCI_CLASS_MULTIMEDIA      0x04
+#define PCI_SUBCLASS_MM_HDAUDIO   0x03
+
+
 /**
  * \german
  * Beschreibt ein PCI-Gerät
-- 
1.6.0.2