#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; }