HelenOS sources

root/uspace/drv/audio/hdaudio/hdactl.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. hda_ctl_reg16_set_reset
  2. hda_rb_size_select
  3. hda_rb_entries
  4. hda_corb_init
  5. hda_corb_fini
  6. hda_rirb_init
  7. hda_rirb_fini
  8. hda_get_corbrp
  9. hda_get_corbwp
  10. hda_set_corbwp
  11. hda_get_rirbwp
  12. hda_corb_avail
  13. hda_corb_write
  14. hda_rirb_read
  15. hda_solrb_read
  16. hda_ctl_init
  17. hda_ctl_fini
  18. hda_cmd
  19. hda_ctl_process_rirb
  20. hda_ctl_check_fifo_error
  21. hda_ctl_interrupt
  22. hda_ctl_dump_info

/*
 * Copyright (c) 2022 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - 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.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/** @addtogroup hdaudio
 * @{
 */
/** @file High Definition Audio controller
 */

#include <as.h>
#include <async.h>
#include <bitops.h>
#include <ddf/log.h>
#include <ddi.h>
#include <errno.h>
#include <fibril_synch.h>
#include <macros.h>
#include <stdint.h>

#include "codec.h"
#include "hdactl.h"
#include "regif.h"
#include "spec/regs.h"

enum {
        ctrl_init_wait_max = 10,
        codec_enum_wait_us = 512,
        corb_wait_max = 10,
        rirb_wait_max = 100,
        solrb_wait_us = 100 * 1000
};

static void hda_ctl_process_rirb(hda_ctl_t *);

/** Perform set-reset handshake on a 16-bit register.
 *
 * The bit(s) specified in the mask are written as 1, then we wait
 * for them to read as 1. Then we write them as 0 and we wait for them
 * to read as 0.
 */
static errno_t hda_ctl_reg16_set_reset(uint16_t *reg, uint16_t mask)
{
        uint16_t val;
        int wcnt;

        val = hda_reg16_read(reg);
        hda_reg16_write(reg, val | mask);

        wcnt = 1000;
        while (wcnt > 0) {
                val = hda_reg16_read(reg);
                if ((val & mask) == mask)
                        break;

                fibril_usleep(1000);
                --wcnt;
        }

        if ((val & mask) != mask)
                return ETIMEOUT;

        val = hda_reg16_read(reg);
        hda_reg16_write(reg, val & ~mask);

        wcnt = 1000;
        while (wcnt > 0) {
                val = hda_reg16_read(reg);
                if ((val & mask) == 0)
                        break;

                fibril_usleep(1000);
                --wcnt;
        }

        if ((val & mask) != 0)
                return ETIMEOUT;

        return EOK;
}

/** Select an appropriate CORB/RIRB size.
 *
 * We always use the largest available size. In @a sizecap each of bits
 * 0, 1, 2 determine whether one of the supported size (0 == 2 entries,
 * 1 == 16 entries, 2 == 256 entries) is supported. @a *selsz is set to
 * one of 0, 1, 2 on success.
 *
 * @param sizecap       CORB/RIRB Size Capability
 * @param selsz         Place to store CORB/RIRB Size
 * @return              EOK on success, EINVAL if sizecap has no valid bits set
 *
 */
static errno_t hda_rb_size_select(uint8_t sizecap, uint8_t *selsz)
{
        int i;

        for (i = 2; i >= 0; --i) {
                if ((sizecap & BIT_V(uint8_t, i)) != 0) {
                        *selsz = i;
                        return EOK;
                }
        }

        return EINVAL;
}

static size_t hda_rb_entries(uint8_t selsz)
{
        switch (selsz) {
        case 0:
                return 2;
        case 1:
                return 16;
        case 2:
                return 256;
        default:
                assert(false);
                return 0;
        }
}

/** Initialize the CORB */
static errno_t hda_corb_init(hda_t *hda)
{
        uint8_t ctl;
        uint8_t corbsz;
        uint8_t sizecap;
        uint8_t selsz;
        errno_t rc;

        ddf_msg(LVL_DEBUG, "hda_corb_init()");

        /* Stop CORB if not stopped */
        ctl = hda_reg8_read(&hda->regs->corbctl);
        if ((ctl & BIT_V(uint8_t, corbctl_run)) != 0) {
                ddf_msg(LVL_DEBUG, "CORB is enabled, disabling first.");
                hda_reg8_write(&hda->regs->corbctl, ctl & ~BIT_V(uint8_t,
                    corbctl_run));
        }

        /* Determine CORB size and allocate CORB buffer */
        corbsz = hda_reg8_read(&hda->regs->corbsize);
        sizecap = BIT_RANGE_EXTRACT(uint8_t, corbsize_cap_h,
            corbsize_cap_l, corbsz);
        rc = hda_rb_size_select(sizecap, &selsz);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Invalid CORB Size Capability");
                goto error;
        }
        corbsz = corbsz & ~BIT_RANGE(uint8_t, corbsize_size_h, corbsize_size_l);
        corbsz = corbsz | selsz;

        ddf_msg(LVL_DEBUG, "Setting CORB Size register to 0x%x", corbsz);
        hda_reg8_write(&hda->regs->corbsize, corbsz);
        hda->ctl->corb_entries = hda_rb_entries(selsz);

        /*
         * CORB must be aligned to 128 bytes. If 64OK is not set,
         * it must be within the 32-bit address space.
         */
        hda->ctl->corb_virt = AS_AREA_ANY;
        rc = dmamem_map_anonymous(hda->ctl->corb_entries * sizeof(uint32_t),
            hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0,
            &hda->ctl->corb_phys, &hda->ctl->corb_virt);
        if (rc != EOK) {
                hda->ctl->corb_virt = NULL;
                ddf_msg(LVL_ERROR, "Failed allocating DMA memory for CORB");
                goto error;
        }

        ddf_msg(LVL_DEBUG, "Set CORB base registers");

        /* Update CORB base registers */
        hda_reg32_write(&hda->regs->corblbase, LOWER32(hda->ctl->corb_phys));
        hda_reg32_write(&hda->regs->corbubase, UPPER32(hda->ctl->corb_phys));

        ddf_msg(LVL_DEBUG, "Reset CORB Read/Write pointers");

        /* Reset CORB Read Pointer */
        rc = hda_ctl_reg16_set_reset(&hda->regs->corbrp,
            BIT_V(uint16_t, corbrp_rst));
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed resetting CORBRP");
                goto error;
        }

        /* Reset CORB Write Pointer */
        hda_reg16_write(&hda->regs->corbwp, 0);

        /* Start CORB */
        ctl = hda_reg8_read(&hda->regs->corbctl);
        ddf_msg(LVL_DEBUG, "CORBctl (0x%x) = 0x%x",
            (unsigned)((void *)&hda->regs->corbctl - (void *)hda->regs), ctl | BIT_V(uint8_t, corbctl_run));
        hda_reg8_write(&hda->regs->corbctl, ctl | BIT_V(uint8_t, corbctl_run));

        ddf_msg(LVL_DEBUG, "CORB initialized");
        return EOK;
error:
        if (hda->ctl->corb_virt != NULL) {
                dmamem_unmap_anonymous(hda->ctl->corb_virt);
                hda->ctl->corb_virt = NULL;
        }
        return EIO;
}

/** Tear down the CORB */
static void hda_corb_fini(hda_t *hda)
{
        uint8_t ctl;

        /* Stop CORB */
        ctl = hda_reg8_read(&hda->regs->corbctl);
        hda_reg8_write(&hda->regs->corbctl, ctl & ~BIT_V(uint8_t, corbctl_run));

        if (hda->ctl->corb_virt != NULL)
                dmamem_unmap_anonymous(hda->ctl->corb_virt);
}

/** Initialize the RIRB */
static errno_t hda_rirb_init(hda_t *hda)
{
        uint8_t ctl;
        uint8_t rirbsz;
        uint8_t sizecap;
        uint8_t selsz;
        errno_t rc;

        ddf_msg(LVL_DEBUG, "hda_rirb_init()");

        /* Stop RIRB if not stopped */
        ctl = hda_reg8_read(&hda->regs->rirbctl);
        if ((ctl & BIT_V(uint8_t, rirbctl_run)) != 0) {
                ddf_msg(LVL_DEBUG, "RIRB is enabled, disabling first.");
                hda_reg8_write(&hda->regs->corbctl, ctl & ~BIT_V(uint8_t,
                    rirbctl_run));
        }

        /* Determine RIRB size and allocate RIRB buffer */
        rirbsz = hda_reg8_read(&hda->regs->rirbsize);
        sizecap = BIT_RANGE_EXTRACT(uint8_t, rirbsize_cap_h,
            rirbsize_cap_l, rirbsz);
        rc = hda_rb_size_select(sizecap, &selsz);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Invalid RIRB Size Capability");
                goto error;
        }
        rirbsz = rirbsz & ~BIT_RANGE(uint8_t, rirbsize_size_h, rirbsize_size_l);
        rirbsz = rirbsz | (selsz << rirbsize_size_l);

        ddf_msg(LVL_DEBUG, "Setting RIRB Size register to 0x%x", rirbsz);
        hda_reg8_write(&hda->regs->rirbsize, rirbsz);
        hda->ctl->rirb_entries = hda_rb_entries(selsz);

        /*
         * RIRB must be aligned to 128 bytes. If 64OK is not set,
         * it must be within the 32-bit address space.
         */
        hda->ctl->rirb_virt = AS_AREA_ANY;
        rc = dmamem_map_anonymous(hda->ctl->rirb_entries * sizeof(uint64_t),
            hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0,
            &hda->ctl->rirb_phys, &hda->ctl->rirb_virt);
        if (rc != EOK) {
                hda->ctl->rirb_virt = NULL;
                ddf_msg(LVL_ERROR, "Failed allocating DMA memory for RIRB");
                goto error;
        }

        ddf_msg(LVL_DEBUG, "Set RIRB base registers");

        /* Update RIRB base registers */
        hda_reg32_write(&hda->regs->rirblbase, LOWER32(hda->ctl->rirb_phys));
        hda_reg32_write(&hda->regs->rirbubase, UPPER32(hda->ctl->rirb_phys));

        ddf_msg(LVL_DEBUG, "Reset RIRB Write pointer");

        /* Reset RIRB Write Pointer */
        hda_reg16_write(&hda->regs->rirbwp, BIT_V(uint16_t, rirbwp_rst));

        /* Set RINTCNT - Qemu won't read from CORB if this is zero */
        hda_reg16_write(&hda->regs->rintcnt, hda->ctl->rirb_entries / 2);

        hda->ctl->rirb_rp = 0;

        /* Start RIRB and enable RIRB interrupt */
        ctl = hda_reg8_read(&hda->regs->rirbctl);
        ddf_msg(LVL_DEBUG, "RIRBctl (0x%x) = 0x%x",
            (unsigned)((void *)&hda->regs->rirbctl - (void *)hda->regs), ctl | BIT_V(uint8_t, rirbctl_run));
        hda_reg8_write(&hda->regs->rirbctl, ctl | BIT_V(uint8_t, rirbctl_run) |
            BIT_V(uint8_t, rirbctl_int));

        ddf_msg(LVL_DEBUG, "RIRB initialized");
        return EOK;
error:
        if (hda->ctl->rirb_virt != NULL) {
                dmamem_unmap_anonymous(hda->ctl->rirb_virt);
                hda->ctl->rirb_virt = NULL;
        }
        return EIO;
}

/** Tear down the RIRB */
static void hda_rirb_fini(hda_t *hda)
{
        uint8_t ctl;

        /* Stop RIRB and disable RIRB interrupt */
        ctl = hda_reg8_read(&hda->regs->rirbctl);
        hda_reg8_write(&hda->regs->rirbctl, ctl &
            ~(BIT_V(uint8_t, rirbctl_run) | BIT_V(uint8_t, rirbctl_int)));

        if (hda->ctl->rirb_virt != NULL)
                dmamem_unmap_anonymous(hda->ctl->rirb_virt);
}

static size_t hda_get_corbrp(hda_t *hda)
{
        uint16_t corbrp;

        corbrp = hda_reg16_read(&hda->regs->corbrp);
        return BIT_RANGE_EXTRACT(uint16_t, corbrp_rp_h, corbrp_rp_l, corbrp);
}

static size_t hda_get_corbwp(hda_t *hda)
{
        uint16_t corbwp;

        corbwp = hda_reg16_read(&hda->regs->corbwp);
        return BIT_RANGE_EXTRACT(uint16_t, corbwp_wp_h, corbwp_wp_l, corbwp);
}

static void hda_set_corbwp(hda_t *hda, size_t wp)
{
        ddf_msg(LVL_DEBUG2, "Set CORBWP = %zu", wp);
        hda_reg16_write(&hda->regs->corbwp, wp);
}

static size_t hda_get_rirbwp(hda_t *hda)
{
        uint16_t rirbwp;

        rirbwp = hda_reg16_read(&hda->regs->rirbwp);
        return BIT_RANGE_EXTRACT(uint16_t, rirbwp_wp_h, rirbwp_wp_l, rirbwp);
}

/** Determine number of free entries in CORB */
static size_t hda_corb_avail(hda_t *hda)
{
        int rp, wp;
        int avail;

        rp = hda_get_corbrp(hda);
        wp = hda_get_corbwp(hda);

        avail = rp - wp - 1;
        while (avail < 0)
                avail += hda->ctl->corb_entries;

        return avail;
}

/** Write to CORB */
static errno_t hda_corb_write(hda_t *hda, uint32_t *data, size_t count)
{
        size_t avail;
        size_t wp;
        size_t idx;
        size_t now;
        size_t i;
        uint32_t *corb;
        int wcnt;

        avail = hda_corb_avail(hda);
        wp = hda_get_corbwp(hda);
        corb = (uint32_t *)hda->ctl->corb_virt;

        idx = 0;
        while (idx < count) {
                now = min(avail, count - idx);

                for (i = 0; i < now; i++) {
                        wp = (wp + 1) % hda->ctl->corb_entries;
                        corb[wp] = data[idx++];
                }

                hda_set_corbwp(hda, wp);

                if (idx < count) {
                        /* We filled up CORB but still data remaining */
                        wcnt = corb_wait_max;
                        while (hda_corb_avail(hda) < 1 && wcnt > 0) {
                                fibril_usleep(100);
                                --wcnt;
                        }

                        /* If CORB is still full return timeout error */
                        if (hda_corb_avail(hda) < 1)
                                return ETIMEOUT;
                }
        }

        return EOK;
}

static errno_t hda_rirb_read(hda_t *hda, hda_rirb_entry_t *data)
{
        size_t wp;
        hda_rirb_entry_t resp;
        hda_rirb_entry_t *rirb;

        rirb = (hda_rirb_entry_t *)hda->ctl->rirb_virt;

        wp = hda_get_rirbwp(hda);
        ddf_msg(LVL_DEBUG2, "hda_rirb_read: wp=%zu", wp);
        if (hda->ctl->rirb_rp == wp)
                return ENOENT;

        hda->ctl->rirb_rp = (hda->ctl->rirb_rp + 1) % hda->ctl->rirb_entries;
        resp = rirb[hda->ctl->rirb_rp];

        ddf_msg(LVL_DEBUG2, "RESPONSE resp=0x%x respex=0x%x",
            resp.resp, resp.respex);
        *data = resp;
        return EOK;
}

static errno_t hda_solrb_read(hda_t *hda, hda_rirb_entry_t *data, size_t count)
{
        hda_rirb_entry_t resp;

        ddf_msg(LVL_DEBUG, "hda_solrb_read()");

        fibril_mutex_lock(&hda->ctl->solrb_lock);

        while (count > 0) {
                while (count > 0 && hda->ctl->solrb_rp != hda->ctl->solrb_wp) {
                        hda->ctl->solrb_rp = (hda->ctl->solrb_rp + 1) % softrb_entries;
                        resp = hda->ctl->solrb[hda->ctl->solrb_rp];

                        ddf_msg(LVL_DEBUG2, "solrb RESPONSE resp=0x%x respex=0x%x",
                            resp.resp, resp.respex);
                        if ((resp.respex & BIT_V(uint32_t, respex_unsol)) == 0) {
                                /* Solicited response */
                                *data++ = resp;
                                --count;
                        }
                }

                if (count > 0) {
                        if (hda->ctl->solrb_wp == hda->ctl->solrb_rp) {
                                fibril_condvar_wait_timeout(
                                    &hda->ctl->solrb_cv, &hda->ctl->solrb_lock,
                                    solrb_wait_us);
                        }

                        if (hda->ctl->solrb_wp == hda->ctl->solrb_rp) {
                                ddf_msg(LVL_DEBUG, "hda_solrb_read() - last ditch effort process RIRB");
                                fibril_mutex_unlock(&hda->ctl->solrb_lock);
                                hda_ctl_process_rirb(hda->ctl);
                                fibril_mutex_lock(&hda->ctl->solrb_lock);
                        }

                        if (hda->ctl->solrb_wp == hda->ctl->solrb_rp) {
                                ddf_msg(LVL_DEBUG, "hda_solrb_read() time out");
                                fibril_mutex_unlock(&hda->ctl->solrb_lock);
                                return ETIMEOUT;
                        }
                }
        }

        fibril_mutex_unlock(&hda->ctl->solrb_lock);
        return EOK;
}

hda_ctl_t *hda_ctl_init(hda_t *hda)
{
        hda_ctl_t *ctl;
        uint32_t gctl;
        uint32_t intctl;
        int cnt;
        errno_t rc;

        ctl = calloc(1, sizeof(hda_ctl_t));
        if (ctl == NULL)
                return NULL;

        fibril_mutex_initialize(&ctl->solrb_lock);
        fibril_condvar_initialize(&ctl->solrb_cv);

        hda->ctl = ctl;
        ctl->hda = hda;

        uint8_t vmaj = hda_reg8_read(&hda->regs->vmaj);
        uint8_t vmin = hda_reg8_read(&hda->regs->vmin);
        ddf_msg(LVL_NOTE, "HDA version %d.%d", vmaj, vmin);

        if (vmaj != 1 || vmin != 0) {
                ddf_msg(LVL_ERROR, "Unsupported HDA version (%d.%d).",
                    vmaj, vmin);
                goto error;
        }

        ddf_msg(LVL_DEBUG, "reg 0x%zx STATESTS = 0x%x",
            (void *)&hda->regs->statests - (void *)hda->regs,
            hda_reg16_read(&hda->regs->statests));
        /**
         * Clear STATESTS bits so they don't generate an interrupt later
         * when we enable interrupts.
         */
        hda_reg16_write(&hda->regs->statests, 0x7f);

        ddf_msg(LVL_DEBUG, "after clearing reg 0x%zx STATESTS = 0x%x",
            (void *)&hda->regs->statests - (void *)hda->regs,
            hda_reg16_read(&hda->regs->statests));

        gctl = hda_reg32_read(&hda->regs->gctl);
        if ((gctl & BIT_V(uint32_t, gctl_crst)) != 0) {
                ddf_msg(LVL_DEBUG, "Controller not in reset. Resetting.");
                hda_reg32_write(&hda->regs->gctl, gctl & ~BIT_V(uint32_t, gctl_crst));
        }

        ddf_msg(LVL_DEBUG, "Taking controller out of reset.");
        hda_reg32_write(&hda->regs->gctl, gctl | BIT_V(uint32_t, gctl_crst));

        /* Wait for CRST to read as 1 */
        cnt = ctrl_init_wait_max;
        while (cnt > 0) {
                gctl = hda_reg32_read(&hda->regs->gctl);
                if ((gctl & BIT_V(uint32_t, gctl_crst)) != 0) {
                        ddf_msg(LVL_DEBUG, "gctl=0x%x", gctl);
                        break;
                }

                ddf_msg(LVL_DEBUG, "Waiting for controller to initialize.");
                fibril_usleep(100 * 1000);
                --cnt;
        }

        if (cnt == 0) {
                ddf_msg(LVL_ERROR, "Timed out waiting for controller to come up.");
                goto error;
        }

        ddf_msg(LVL_DEBUG, "Controller is out of reset.");

        ddf_msg(LVL_DEBUG, "Read GCAP");
        uint16_t gcap = hda_reg16_read(&hda->regs->gcap);
        ctl->ok64bit = (gcap & BIT_V(uint16_t, gcap_64ok)) != 0;
        ctl->oss = BIT_RANGE_EXTRACT(uint16_t, gcap_oss_h, gcap_oss_l, gcap);
        ctl->iss = BIT_RANGE_EXTRACT(uint16_t, gcap_iss_h, gcap_iss_l, gcap);
        ctl->bss = BIT_RANGE_EXTRACT(uint16_t, gcap_bss_h, gcap_bss_l, gcap);
        ddf_msg(LVL_DEBUG, "GCAP: 0x%x (64OK=%d)", gcap, ctl->ok64bit);
        ddf_msg(LVL_DEBUG, "iss: %d, oss: %d, bss: %d\n",
            ctl->iss, ctl->oss, ctl->bss);
        /* Give codecs enough time to enumerate themselves */
        fibril_usleep(codec_enum_wait_us);

        ddf_msg(LVL_DEBUG, "STATESTS = 0x%x",
            hda_reg16_read(&hda->regs->statests));

        /* Enable interrupts */
        intctl = hda_reg32_read(&hda->regs->intctl);
        ddf_msg(LVL_DEBUG, "intctl (0x%x) := 0x%x",
            (unsigned)((void *)&hda->regs->intctl - (void *)hda->regs),
            intctl | BIT_V(uint32_t, intctl_gie) | BIT_V(uint32_t, intctl_cie));
        hda_reg32_write(&hda->regs->intctl, intctl |
            BIT_V(uint32_t, intctl_gie) | BIT_V(uint32_t, intctl_cie) |
            0x3fffffff);

        rc = hda_corb_init(hda);
        if (rc != EOK)
                goto error;

        rc = hda_rirb_init(hda);
        if (rc != EOK)
                goto error;

        ddf_msg(LVL_DEBUG, "call hda_codec_init()");
        hda->ctl->codec = hda_codec_init(hda, 0);
        if (hda->ctl->codec == NULL) {
                ddf_msg(LVL_DEBUG, "hda_codec_init() failed");
                goto error;
        }

        ddf_msg(LVL_DEBUG, "intsts=0x%x", hda_reg32_read(&hda->regs->intsts));
        ddf_msg(LVL_DEBUG, "sdesc[%d].sts=0x%x",
            hda->ctl->iss, hda_reg8_read(&hda->regs->sdesc[hda->ctl->iss].sts));

        return ctl;
error:
        hda_rirb_fini(hda);
        hda_corb_fini(hda);
        free(ctl);
        hda->ctl = NULL;
        return NULL;
}

void hda_ctl_fini(hda_ctl_t *ctl)
{
        ddf_msg(LVL_DEBUG, "hda_ctl_fini()");
        hda_rirb_fini(ctl->hda);
        hda_corb_fini(ctl->hda);
        free(ctl);
}

errno_t hda_cmd(hda_t *hda, uint32_t verb, uint32_t *resp)
{
        errno_t rc;
        hda_rirb_entry_t rentry;

        rc = hda_corb_write(hda, &verb, 1);
        if (rc != EOK)
                return rc;

        if (resp != NULL) {
                rc = hda_solrb_read(hda, &rentry, 1);
                if (rc != EOK)
                        return rc;

                /* XXX Verify that response came from the correct codec */
                *resp = rentry.resp;
        }

        return EOK;
}

static void hda_ctl_process_rirb(hda_ctl_t *ctl)
{
        hda_rirb_entry_t resp;
        errno_t rc;

        while (true) {
                rc = hda_rirb_read(ctl->hda, &resp);
                if (rc != EOK) {
                        ddf_msg(LVL_DEBUG2, "nothing in rirb");
                        break;
                }

                ddf_msg(LVL_DEBUG2, "writing to solrb");
                fibril_mutex_lock(&ctl->solrb_lock);
                ctl->solrb_wp = (ctl->solrb_wp + 1) % softrb_entries;
                ctl->solrb[ctl->solrb_wp] = resp;
                fibril_mutex_unlock(&ctl->solrb_lock);
                fibril_condvar_broadcast(&ctl->solrb_cv);
        }
}

static void hda_ctl_check_fifo_error(hda_ctl_t *ctl)
{
        int i;
        uint8_t sts;

        /*
         * XXX Ideally the interrupt handler would tell us which stream
         * has the error.
         */

        for (i = 0; i < 30; i++) {
                sts = hda_reg8_read(&ctl->hda->regs->sdesc[i].sts);
                if ((sts & BIT_V(uint8_t, sdsts_fifoe)) != 0 && (sts & 0x80) == 0) {
                        ddf_msg(LVL_WARN, "sts[%d] = 0x%hhx\n", i, sts);
                        hda_reg8_write(&ctl->hda->regs->sdesc[i].sts,
                            BIT_V(uint8_t, sdsts_fifoe));
                }
        }
}

void hda_ctl_interrupt(hda_ctl_t *ctl)
{
        ddf_msg(LVL_DEBUG, "hda_ctl_interrupt");
        hda_ctl_check_fifo_error(ctl);
        hda_ctl_process_rirb(ctl);
}

void hda_ctl_dump_info(hda_ctl_t *ctl)
{
        ddf_msg(LVL_DEBUG, "corbwp=%d, corbrp=%d",
            hda_reg16_read(&ctl->hda->regs->corbwp),
            hda_reg16_read(&ctl->hda->regs->corbrp));
        ddf_msg(LVL_DEBUG, "corbctl=0x%x, corbsts=0x%x",
            hda_reg8_read(&ctl->hda->regs->corbctl),
            hda_reg8_read(&ctl->hda->regs->corbsts));
        ddf_msg(LVL_DEBUG, "rirbwp=0x%x, soft-rirbrp=0x%zx",
            hda_reg16_read(&ctl->hda->regs->rirbwp),
            ctl->rirb_rp);
        ddf_msg(LVL_DEBUG, "solrb_wp=0x%zx, solrb_rp=0x%zx",
            ctl->solrb_wp, ctl->solrb_wp);
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub