major refactorings
Signed-off-by: kaguya3311 <kaguya3311@national.shitposting.agency>
This commit is contained in:
@@ -0,0 +1,537 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user