Files
KirkOS/oldsrc/audio/hda.c
T
2026-05-18 04:02:59 -04:00

537 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "hda.h"
#include "mm/vmm.h"
#include "mm/pmm.h"
#include "mm/memory.h"
#include "libk/stdio.h"
#include "libk/debug.h"
/* ── Module globals ─────────────────────────────────────────────────────────── */
static uintptr_t g_mmio = 0; /* virtual MMIO base */
static uint32_t *g_corb = NULL; /* CORB ring buffer (virtual) */
static uint64_t *g_rirb = NULL; /* RIRB ring buffer (virtual) */
static uintptr_t g_corb_phys = 0;
static uintptr_t g_rirb_phys = 0;
static uint32_t g_corb_wp = 0; /* shadow of hardware CORBWP (starts at 0) */
static uint32_t g_rirb_rp = 0; /* our RIRB read pointer (starts at 0) */
/* Codec state discovered during enumeration */
static uint8_t g_codec = 0; /* codec address (0-15) */
static uint8_t g_afg = 0; /* Audio Function Group NID */
static uint8_t g_dac = 0; /* Output Converter NID */
static uint8_t g_pin = 0; /* Output Pin Widget NID */
static uint8_t g_iss = 0; /* number of input stream descriptors */
/* BDL (allocated once in hda_init, reused each play call) */
static hda_bdl_entry_t *g_bdl = NULL;
static uintptr_t g_bdl_phys = 0;
/* MMIO offset of the output stream descriptor we're using */
static uint32_t g_sd_base = 0;
/* ── MMIO helpers ───────────────────────────────────────────────────────────── */
static inline uint8_t r8 (uint32_t r) { return *(volatile uint8_t *)(g_mmio+r); }
static inline uint16_t r16(uint32_t r) { return *(volatile uint16_t*)(g_mmio+r); }
static inline uint32_t r32(uint32_t r) { return *(volatile uint32_t*)(g_mmio+r); }
static inline void w8 (uint32_t r, uint8_t v){ *(volatile uint8_t *)(g_mmio+r)=v; }
static inline void w16(uint32_t r, uint16_t v){ *(volatile uint16_t*)(g_mmio+r)=v; }
static inline void w32(uint32_t r, uint32_t v){ *(volatile uint32_t*)(g_mmio+r)=v; }
/* Stream descriptor helpers operate relative to g_sd_base */
static inline uint8_t sd_r8 (uint32_t o){ return r8 (g_sd_base+o); }
static inline uint16_t sd_r16(uint32_t o){ return r16(g_sd_base+o); }
static inline uint32_t sd_r32(uint32_t o){ return r32(g_sd_base+o); }
static inline void sd_w8 (uint32_t o,uint8_t v){ w8 (g_sd_base+o,v); }
static inline void sd_w16(uint32_t o,uint16_t v){ w16(g_sd_base+o,v); }
static inline void sd_w32(uint32_t o,uint32_t v){ w32(g_sd_base+o,v); }
/* ── Busy-wait delay ────────────────────────────────────────────────────────── */
static void hda_udelay(uint32_t us)
{
/* ~400 pause iterations ≈ 1 µs on a modern core (generous upper bound) */
volatile uint32_t n = us * 400;
while (n--) asm volatile("pause");
}
/* ── CORB / RIRB verb engine ────────────────────────────────────────────────── */
/*
* hda_send_verb write one 32-bit command word to the CORB and wait for the
* corresponding RIRB response. Returns the 64-bit RIRB entry (response in the
* low 32 bits).
*/
static uint64_t hda_send_verb(uint32_t cmd)
{
g_corb_wp = (g_corb_wp + 1) & 0xFF; /* 256-entry ring */
g_corb[g_corb_wp] = cmd;
/* Tell the hardware there is a new entry */
w16(HDA_CORBWP, (uint16_t)g_corb_wp);
/* Poll until RIRBWP advances (hardware writes one response per command) */
for (uint32_t i = 0; i < 1000000; i++) {
asm volatile("pause");
if ((r8(HDA_RIRBWP) & 0xFF) != (uint8_t)g_rirb_rp) {
g_rirb_rp = (g_rirb_rp + 1) & 0xFF;
return g_rirb[g_rirb_rp];
}
}
kprintf("[HDA] verb timeout cmd=%08x\n", cmd);
return (uint64_t)-1;
}
/*
* hda_verb build and send a codec verb, return the 32-bit response.
*
* Verb encoding:
* verb <= 0x0F : 4-bit verb [19:16], 16-bit payload [15:0] (SET_FMT, SET_AMP …)
* verb >= 0x100: 12-bit verb [19:8], 8-bit payload [7:0] (GET_PARAM, SET_* …)
*/
static uint32_t hda_verb(uint8_t cad, uint8_t nid, uint32_t verb, uint32_t payload)
{
uint32_t cmd;
if (verb <= 0xF)
cmd = ((uint32_t)cad << 28) | ((uint32_t)nid << 20)
| (verb << 16) | (payload & 0xFFFF);
else
cmd = ((uint32_t)cad << 28) | ((uint32_t)nid << 20)
| ((verb & 0xFFF) << 8) | (payload & 0xFF);
return (uint32_t)hda_send_verb(cmd);
}
static uint32_t hda_get_param(uint8_t cad, uint8_t nid, uint8_t param)
{
return hda_verb(cad, nid, 0xF00, param);
}
/* ── CORB initialisation ────────────────────────────────────────────────────── */
static bool corb_init(void)
{
/* Stop DMA engine */
w8(HDA_CORBCTL, r8(HDA_CORBCTL) & ~0x02u);
for (int i = 0; i < 50000 && (r8(HDA_CORBCTL) & 0x02); i++)
asm volatile("pause");
/* Allocate ring: 256 entries × 4 bytes = 1 KB, lives in one page */
void *phys = pmm_allocz(1);
if (!phys) return false;
g_corb_phys = (uintptr_t)phys;
g_corb = (uint32_t *)((uintptr_t)phys + MEM_PHYS_OFFSET);
/* Select 256-entry size (bits[1:0] = 0b10) */
w8(HDA_CORBSIZE, (r8(HDA_CORBSIZE) & ~0x03u) | 0x02u);
/* Program base address */
w32(HDA_CORBLBASE, (uint32_t)(g_corb_phys & 0xFFFFFFFF));
w32(HDA_CORBUBASE, (uint32_t)(g_corb_phys >> 32));
/* Reset WP to 0 */
w16(HDA_CORBWP, 0);
g_corb_wp = 0;
/* Reset RP: set CORBRPRST, wait, clear */
w16(HDA_CORBRP, 0x8000);
hda_udelay(100);
w16(HDA_CORBRP, 0x0000);
hda_udelay(100);
/* Start DMA */
w8(HDA_CORBCTL, r8(HDA_CORBCTL) | 0x02u);
for (int i = 0; i < 50000; i++) {
if (r8(HDA_CORBCTL) & 0x02) return true;
asm volatile("pause");
}
kprintf("[HDA] CORB DMA failed to start\n");
return false;
}
/* ── RIRB initialisation ────────────────────────────────────────────────────── */
static bool rirb_init(void)
{
/* Stop DMA engine */
w8(HDA_RIRBCTL, r8(HDA_RIRBCTL) & ~0x02u);
for (int i = 0; i < 50000 && (r8(HDA_RIRBCTL) & 0x02); i++)
asm volatile("pause");
/* Allocate ring: 256 entries × 8 bytes = 2 KB, one page */
void *phys = pmm_allocz(1);
if (!phys) return false;
g_rirb_phys = (uintptr_t)phys;
g_rirb = (uint64_t *)((uintptr_t)phys + MEM_PHYS_OFFSET);
/* 256-entry size */
w8(HDA_RIRBSIZE, (r8(HDA_RIRBSIZE) & ~0x03u) | 0x02u);
/* Base address */
w32(HDA_RIRBLBASE, (uint32_t)(g_rirb_phys & 0xFFFFFFFF));
w32(HDA_RIRBUBASE, (uint32_t)(g_rirb_phys >> 32));
/* Reset WP (write RIRBWPRST bit) */
w16(HDA_RIRBWP, 0x8000);
g_rirb_rp = 0;
/* Set interrupt count to 0xFF (we poll anyway, so this is a safety valve) */
w16(HDA_RINTCNT, 0xFF);
/* Start DMA */
w8(HDA_RIRBCTL, r8(HDA_RIRBCTL) | 0x02u);
for (int i = 0; i < 50000; i++) {
if (r8(HDA_RIRBCTL) & 0x02) return true;
asm volatile("pause");
}
kprintf("[HDA] RIRB DMA failed to start\n");
return false;
}
/* ── Codec enumeration ──────────────────────────────────────────────────────── */
/*
* Return the NID of connection-list entry `idx` for widget `nid` on codec `cad`.
* Assumes short form (8-bit entries) which covers all common HDA codecs.
*/
static uint8_t conn_entry(uint8_t cad, uint8_t nid, uint8_t idx)
{
/* GET_CONN_LIST_ENTRY (0xF02): payload = starting index, returns 4 packed entries */
uint32_t r = hda_verb(cad, nid, 0xF02, idx & ~3u);
return (uint8_t)(r >> ((idx & 3) * 8));
}
/*
* Walk the AFG widgets to find the first output converter (DAC) and the first
* output-capable pin that connects to it (or to any DAC if the first one is
* not in the pin's connection list).
*/
static bool enumerate_codec(uint8_t cad)
{
/* Root node: get function group range */
uint32_t root_cnt = hda_get_param(cad, 0, HDA_PARAM_NODE_COUNT);
uint8_t fg_start = (root_cnt >> 16) & 0xFF;
uint8_t fg_count = (root_cnt >> 0) & 0xFF;
/* Find the Audio Function Group */
uint8_t afg = 0;
for (uint8_t n = fg_start; n < fg_start + fg_count; n++) {
uint32_t type = hda_get_param(cad, n, HDA_PARAM_FUNC_GROUP_TYPE) & 0xFF;
if (type == 0x01) { afg = n; break; }
}
if (!afg) { kprintf("[HDA] no AFG on codec %u\n", cad); return false; }
/* Enumerate widgets */
uint32_t w_info = hda_get_param(cad, afg, HDA_PARAM_NODE_COUNT);
uint8_t w_start = (w_info >> 16) & 0xFF;
uint8_t w_count = (w_info >> 0) & 0xFF;
uint8_t dac = 0, pin = 0;
for (uint8_t w = w_start; w < w_start + w_count; w++) {
uint32_t wcap = hda_get_param(cad, w, HDA_PARAM_AUDIO_WIDGET_CAP);
uint8_t wtype = (wcap >> 20) & 0xF;
if (wtype == HDA_WTYPE_AUDIO_OUT && !dac) {
dac = w;
} else if (wtype == HDA_WTYPE_PIN) {
uint32_t pcap = hda_get_param(cad, w, HDA_PARAM_PIN_CAPABILITIES);
if ((pcap & HDA_PINCAP_OUTPUT) && !pin) {
pin = w;
}
}
}
if (!dac) { kprintf("[HDA] no DAC found\n"); return false; }
if (!pin) { kprintf("[HDA] no output pin found\n"); return false; }
g_afg = afg; g_dac = dac; g_pin = pin;
kprintf("[HDA] codec=%u AFG=%u DAC=%u Pin=%u\n", cad, afg, dac, pin);
/* Select DAC in pin's connection list if needed */
uint8_t clen = (uint8_t)(hda_get_param(cad, pin, HDA_PARAM_CONN_LIST_LEN) & 0x7F);
for (uint8_t i = 0; i < clen; i++) {
if (conn_entry(cad, pin, i) == dac) {
hda_verb(cad, pin, 0x701, i); /* SET_CONN_SELECT */
break;
}
}
return true;
}
/* ── HDA format word calculation ────────────────────────────────────────────── */
static uint16_t calc_format(hda_format_t f)
{
uint16_t fmt = 0;
/* Channels: field value = channels 1 */
fmt |= (f.channels - 1) & 0xF;
/* Bits per sample in bits [6:4] */
switch (f.bits_per_sample) {
case 8: fmt |= (0u << 4); break;
case 16: fmt |= (1u << 4); break;
case 20: fmt |= (2u << 4); break;
case 24: fmt |= (3u << 4); break;
case 32: fmt |= (4u << 4); break;
default: fmt |= (1u << 4);
kprintf("[HDA] unknown bit depth %u, using 16-bit\n", f.bits_per_sample);
}
/*
* Sample rate: bit14 = base (0 = 48 kHz, 1 = 44.1 kHz),
* bits[13:11] = multiplier 1,
* bits[10:8] = divisor 1.
*/
switch (f.sample_rate) {
case 8000: fmt |= (0u << 14) | (0u << 11) | (5u << 8); break; /* 48000 / 6 */
case 11025: fmt |= (1u << 14) | (0u << 11) | (3u << 8); break; /* 44100 / 4 */
case 16000: fmt |= (0u << 14) | (0u << 11) | (2u << 8); break; /* 48000 / 3 */
case 22050: fmt |= (1u << 14) | (0u << 11) | (1u << 8); break; /* 44100 / 2 */
case 24000: fmt |= (0u << 14) | (0u << 11) | (1u << 8); break; /* 48000 / 2 */
case 32000: fmt |= (0u << 14) | (1u << 11) | (2u << 8); break; /* 48000*2/ 3 */
case 44100: fmt |= (1u << 14) | (0u << 11) | (0u << 8); break; /* 44100 / 1 */
case 48000: break; /* 48000 / 1 */
case 88200: fmt |= (1u << 14) | (1u << 11) | (0u << 8); break; /* 44100*2/ 1 */
case 96000: fmt |= (0u << 14) | (1u << 11) | (0u << 8); break; /* 48000*2/ 1 */
case 176400: fmt |= (1u << 14) | (3u << 11) | (0u << 8); break; /* 44100*4/ 1 */
case 192000: fmt |= (0u << 14) | (3u << 11) | (0u << 8); break; /* 48000*4/ 1 */
default:
kprintf("[HDA] unsupported sample rate %u, defaulting to 48000\n",
f.sample_rate);
}
return fmt;
}
/* ── Stream descriptor reset helper ────────────────────────────────────────── */
static void sd_reset(void)
{
/* Assert stream reset */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) | HDA_SD_CTL_SRST);
for (int i = 0; i < 100000; i++) {
if (sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_SRST) break;
asm volatile("pause");
}
/* Deassert stream reset */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) & ~HDA_SD_CTL_SRST);
for (int i = 0; i < 100000; i++) {
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_SRST)) break;
asm volatile("pause");
}
}
/* ══════════════════════════════════════════════════════════════════════════════
* Public API
* ══════════════════════════════════════════════════════════════════════════════ */
bool hda_init(pci_device_t *dev)
{
/* ── 1. Enable PCI Memory Space + Bus Master ────────────────────────────── */
uint16_t pci_cmd = pci_read16(dev->addr, 0x04);
pci_cmd |= (1u << 1) | (1u << 2); /* Memory Space Enable | Bus Master Enable */
pci_write16(dev->addr, 0x04, pci_cmd);
/* ── 2. Read BAR0 (HDA MMIO, may be 64-bit) ─────────────────────────────── */
uint32_t bar0_raw = dev->bar[0];
uint64_t mmio_phys;
if ((bar0_raw & 0x6u) == 0x4u) {
/* 64-bit BAR: combine BAR0 (lower) and BAR1 (upper) */
mmio_phys = ((uint64_t)(bar0_raw & ~0xFu))
| ((uint64_t)dev->bar[1] << 32);
} else {
mmio_phys = bar0_raw & ~0xFu;
}
if (!mmio_phys) {
kprintf("[HDA] BAR0 is zero, controller not properly enumerated by firmware\n");
return false;
}
/* Map via HHDM (all physical memory is already mapped with MEM_PHYS_OFFSET) */
g_mmio = mmio_phys + MEM_PHYS_OFFSET;
kprintf("[HDA] MMIO phys=%lx virt=%lx\n", (uint64_t)mmio_phys, (uint64_t)g_mmio);
/* ── 3. Controller reset ─────────────────────────────────────────────────── */
/* Assert reset */
w32(HDA_GCTL, r32(HDA_GCTL) & ~HDA_GCTL_CRST);
for (int i = 0; i < 500000; i++) {
if (!(r32(HDA_GCTL) & HDA_GCTL_CRST)) break;
asm volatile("pause");
}
hda_udelay(100);
/* Release reset */
w32(HDA_GCTL, r32(HDA_GCTL) | HDA_GCTL_CRST);
for (int i = 0; i < 500000; i++) {
if (r32(HDA_GCTL) & HDA_GCTL_CRST) break;
asm volatile("pause");
}
/* Wait ≥ 521 µs for codecs to come out of reset */
hda_udelay(1000);
/* ── 4. GCAP: discover stream counts ─────────────────────────────────────── */
uint16_t gcap = r16(HDA_GCAP);
g_iss = (gcap >> 8) & 0xF; /* number of input SDs */
uint8_t oss = (gcap >> 12) & 0xF; /* number of output SDs */
kprintf("[HDA] gcap=%04x ISS=%u OSS=%u\n", gcap, g_iss, oss);
if (!oss) { kprintf("[HDA] no output streams\n"); return false; }
/* Output SD 0 starts immediately after all input SDs */
g_sd_base = 0x80u + (uint32_t)g_iss * HDA_SD_STRIDE;
/* ── 5. CORB + RIRB ─────────────────────────────────────────────────────── */
if (!corb_init()) { kprintf("[HDA] CORB init failed\n"); return false; }
if (!rirb_init()) { kprintf("[HDA] RIRB init failed\n"); return false; }
/* ── 6. Detect and enumerate codecs ─────────────────────────────────────── */
uint16_t statests = r16(HDA_STATESTS);
if (!statests) { kprintf("[HDA] no codecs detected\n"); return false; }
bool found = false;
for (uint8_t c = 0; c < 15; c++) {
if (!(statests & (1u << c))) continue;
kprintf("[HDA] codec %u present\n", c);
if (!found && enumerate_codec(c)) {
g_codec = c;
found = true;
}
}
if (!found) { kprintf("[HDA] no usable codec\n"); return false; }
/* ── 7. Allocate persistent BDL (1 page, 256 possible entries × 16 bytes) ─ */
void *bdl_phys = pmm_allocz(1);
if (!bdl_phys) { kprintf("[HDA] OOM: BDL\n"); return false; }
g_bdl_phys = (uintptr_t)bdl_phys;
g_bdl = (hda_bdl_entry_t *)((uintptr_t)bdl_phys + MEM_PHYS_OFFSET);
kprintf("[HDA] init complete\n");
return true;
}
bool hda_play_pcm(uintptr_t phys_buf, uint32_t size, hda_format_t fmt)
{
if (!g_mmio || !g_dac || !g_pin) {
kprintf("[HDA] not initialised\n");
return false;
}
if (!phys_buf || !size) return false;
uint16_t format_word = calc_format(fmt);
/* ── 1. Wake up codec nodes ─────────────────────────────────────────────── */
/* SET_POWER_STATE D0 (fully on) for AFG, DAC, and Pin */
hda_verb(g_codec, g_afg, 0x705, 0x00);
hda_verb(g_codec, g_dac, 0x705, 0x00);
hda_verb(g_codec, g_pin, 0x705, 0x00);
hda_udelay(100);
/* ── 2. Assign stream tag 1, channel 0 to the DAC ──────────────────────── */
/* SET_STREAM_CHANNEL: bits[7:4]=stream_tag, bits[3:0]=channel */
hda_verb(g_codec, g_dac, 0x706, (1u << 4) | 0u);
/* ── 3. Set sample format on DAC ───────────────────────────────────────── */
hda_verb(g_codec, g_dac, 0x2, format_word); /* SET_CONVERTER_FORMAT (4-bit verb) */
/* ── 4. Unmute DAC output amp to maximum gain ───────────────────────────── */
/*
* SET_AMP_GAIN_MUTE (4-bit verb 0x3) payload:
* bit 15 = output amp
* bit 14 = input amp
* bit 13 = left channel
* bit 12 = right channel
* bit 7 = mute (0 = unmuted)
* bits[6:0] = gain
*/
uint32_t dac_outcap = hda_get_param(g_codec, g_dac, HDA_PARAM_AMP_CAP_OUTPUT);
uint8_t max_gain = (dac_outcap >> 8) & 0x7F;
hda_verb(g_codec, g_dac, 0x3, 0xB000u | max_gain);
/* ── 5. Enable pin output + unmute pin amp ──────────────────────────────── */
/* SET_PIN_WIDGET_CONTROL: bit6=Out Enable */
hda_verb(g_codec, g_pin, 0x707, 0x40u);
/* If the pin has an output amp, unmute it too */
uint32_t pin_outcap = hda_get_param(g_codec, g_pin, HDA_PARAM_AMP_CAP_OUTPUT);
if (pin_outcap & (1u << 31)) { /* has output amp */
uint8_t pin_max = (pin_outcap >> 8) & 0x7F;
hda_verb(g_codec, g_pin, 0x3, 0xB000u | pin_max);
}
/* ── 6. Reset output stream descriptor ─────────────────────────────────── */
sd_reset();
/* ── 7. Build BDL (single entry covering the entire buffer) ─────────────── */
memset(g_bdl, 0, sizeof(hda_bdl_entry_t) * 2);
g_bdl[0].addr = (uint64_t)phys_buf;
g_bdl[0].length = size;
g_bdl[0].flags = 0x1; /* IOC */
/* ── 8. Program stream descriptor ──────────────────────────────────────── */
/* Cyclic Buffer Length = total audio data */
sd_w32(HDA_SD_CBL, size);
/* Last Valid Index = 0 (one BDL entry, index 0) */
sd_w16(HDA_SD_LVI, 0);
/* BDL base address */
sd_w32(HDA_SD_BDPL, (uint32_t)(g_bdl_phys & 0xFFFFFFFF));
sd_w32(HDA_SD_BDPU, (uint32_t)(g_bdl_phys >> 32));
/* Format */
sd_w16(HDA_SD_FMT, format_word);
/* Stream tag = 1 in SD CTL byte 2 bits [7:4] */
sd_w8(HDA_SD_CTL2, (sd_r8(HDA_SD_CTL2) & 0x0Fu) | (1u << 4));
/* Clear any stale status bits (write-1-to-clear) */
sd_w8(HDA_SD_STS, HDA_SD_STS_BCIS | HDA_SD_STS_FIFOE | HDA_SD_STS_DESE);
/* ── 9. Enable stream-level interrupt-on-completion and run ─────────────── */
uint8_t ctl = sd_r8(HDA_SD_CTL0);
ctl |= HDA_SD_CTL_IOCE | HDA_SD_CTL_RUN;
ctl &= ~HDA_SD_CTL_SRST;
sd_w8(HDA_SD_CTL0, ctl);
kprintf("[HDA] playback started: %u Hz %u-bit %uch %u bytes\n",
fmt.sample_rate, fmt.bits_per_sample, fmt.channels, size);
return true;
}
void hda_stop(void)
{
if (!g_mmio) return;
/* Clear RUN bit */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) & ~HDA_SD_CTL_RUN);
/* Wait for hardware to acknowledge */
for (int i = 0; i < 100000; i++) {
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_RUN)) break;
asm volatile("pause");
}
/* Clear status */
sd_w8(HDA_SD_STS, HDA_SD_STS_BCIS | HDA_SD_STS_FIFOE | HDA_SD_STS_DESE);
}
void hda_wait_complete(void)
{
if (!g_mmio) return;
/* Poll BCIS (Buffer Completion Interrupt Status) */
while (!(sd_r8(HDA_SD_STS) & HDA_SD_STS_BCIS))
asm volatile("pause");
hda_stop();
kprintf("[HDA] playback complete\n");
}
bool hda_is_playing(void)
{
if (!g_mmio) return false;
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_RUN)) return false;
if ( sd_r8(HDA_SD_STS) & HDA_SD_STS_BCIS) return false; /* finished */
return true;
}