HelenOS sources
This source file includes following definitions.
- hda_ctl_reg16_set_reset
- hda_rb_size_select
- hda_rb_entries
- hda_corb_init
- hda_corb_fini
- hda_rirb_init
- hda_rirb_fini
- hda_get_corbrp
- hda_get_corbwp
- hda_set_corbwp
- hda_get_rirbwp
- hda_corb_avail
- hda_corb_write
- hda_rirb_read
- hda_solrb_read
- hda_ctl_init
- hda_ctl_fini
- hda_cmd
- hda_ctl_process_rirb
- hda_ctl_check_fifo_error
- hda_ctl_interrupt
- hda_ctl_dump_info
#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 *);
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;
}
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;
}
}
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()");
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));
}
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);
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");
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");
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;
}
hda_reg16_write(&hda->regs->corbwp, 0);
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;
}
static void hda_corb_fini(hda_t *hda)
{
uint8_t ctl;
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);
}
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()");
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));
}
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);
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");
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");
hda_reg16_write(&hda->regs->rirbwp, BIT_V(uint16_t, rirbwp_rst));
hda_reg16_write(&hda->regs->rintcnt, hda->ctl->rirb_entries / 2);
hda->ctl->rirb_rp = 0;
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;
}
static void hda_rirb_fini(hda_t *hda)
{
uint8_t ctl;
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);
}
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;
}
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) {
wcnt = corb_wait_max;
while (hda_corb_avail(hda) < 1 && wcnt > 0) {
fibril_usleep(100);
--wcnt;
}
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) {
*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));
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));
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);
fibril_usleep(codec_enum_wait_us);
ddf_msg(LVL_DEBUG, "STATESTS = 0x%x",
hda_reg16_read(&hda->regs->statests));
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;
*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;
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);
}
HelenOS homepage, sources at GitHub