summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuEe-GUI <[email protected]>2026-06-10 23:38:27 +0800
committerRbb666 <[email protected]>2026-06-15 10:00:57 +0800
commitb2b3331fd584576b4424448e59e9a94d4ff7f355 (patch)
tree2ddf2150f75efa3a80273073e83e843acf43a4d2
parentbf0b9feabbbe44ea205bf965de80fe63927a0a7c (diff)
[dm][audio] support Intel HDA
make DM import in audio easier Signed-off-by: GuEe-GUI <[email protected]>
-rw-r--r--components/drivers/audio/Kconfig13
-rw-r--r--components/drivers/audio/SConscript8
-rwxr-xr-xcomponents/drivers/audio/audio-hda.c1979
3 files changed, 1997 insertions, 3 deletions
diff --git a/components/drivers/audio/Kconfig b/components/drivers/audio/Kconfig
index 48770ac0f0..3f21b2a1d8 100644
--- a/components/drivers/audio/Kconfig
+++ b/components/drivers/audio/Kconfig
@@ -1,4 +1,4 @@
-config RT_USING_AUDIO
+menuconfig RT_USING_AUDIO
bool "Using Audio device drivers"
default n
@@ -16,3 +16,14 @@ config RT_USING_AUDIO
default 2048
endif
+
+config RT_AUDIO_INTEL_HDA
+ bool "Enable Intel High Definition Audio"
+ depends on RT_USING_DM
+ depends on RT_USING_AUDIO
+ depends on RT_USING_PCI
+ default n
+
+if RT_USING_DM && RT_USING_AUDIO
+ osource "$(SOC_DM_AUDIO_DIR)/Kconfig"
+endif
diff --git a/components/drivers/audio/SConscript b/components/drivers/audio/SConscript
index 7cf8f8118c..455002b610 100644
--- a/components/drivers/audio/SConscript
+++ b/components/drivers/audio/SConscript
@@ -1,8 +1,12 @@
from building import *
cwd = GetCurrentDir()
-src = Glob('*.c')
-CPPPATH = [cwd]
+CPPPATH = [cwd, cwd + '/../include']
+
+src = ['dev_audio.c', 'dev_audio_pipe.c']
+
+if GetDepend(['RT_AUDIO_INTEL_HDA']):
+ src += ['audio-hda.c']
group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_AUDIO'], CPPPATH = CPPPATH)
diff --git a/components/drivers/audio/audio-hda.c b/components/drivers/audio/audio-hda.c
new file mode 100755
index 0000000000..83db3b49a5
--- /dev/null
+++ b/components/drivers/audio/audio-hda.c
@@ -0,0 +1,1979 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2022-10-24 GuEe-GUI first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <drivers/pci.h>
+#include <drivers/dma.h>
+#include <ipc/dataqueue.h>
+
+#include <cpuport.h>
+
+#define DBG_TAG "audio.hda"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define ICH6_REG_BAR 0
+
+/* Controller MMIO registers (intel_hda_* uses ICH6_REG_##name token pasting) */
+#define ICH6_REG_VMIN 0x02
+#define ICH6_REG_VMAJ 0x03
+#define ICH6_REG_GCTL 0x08
+#define ICH6_GCTL_RESET (1 << 0)
+#define ICH6_REG_WAKEEN 0x0c
+#define ICH6_REG_STATESTS 0x0e
+#define ICH6_REG_INTCTL 0x20
+#define ICH6_REG_INTSTS 0x24
+#define ICH6_REG_CORBLBASE 0x40
+#define ICH6_REG_CORBUBASE 0x44
+#define ICH6_REG_CORBWP 0x48
+#define ICH6_REG_CORBRP 0x4a
+#define ICH6_CORBRP_RST (1 << 15)
+#define ICH6_REG_CORBCTL 0x4c
+#define ICH6_CORBCTL_RUN (1 << 1)
+#define ICH6_CORBCTL_CMEIE (1 << 0)
+#define ICH6_REG_CORBSTS 0x4d
+#define ICH6_CORBSTS_CMEI (1 << 0)
+#define ICH6_REG_CORBSIZE 0x4e
+
+#define ICH6_REG_RIRBLBASE 0x50
+#define ICH6_REG_RIRBUBASE 0x54
+#define ICH6_REG_RIRBWP 0x58
+#define ICH6_RIRBWP_RST (1 << 15)
+#define ICH6_REG_RINTCNT 0x5a
+#define ICH6_REG_RIRBCTL 0x5c
+#define ICH6_RBCTL_IRQ_EN (1 << 0)
+#define ICH6_RBCTL_DMA_EN (1 << 1)
+#define ICH6_RBCTL_OVERRUN_EN (1 << 2)
+#define ICH6_REG_RIRBSTS 0x5d
+#define ICH6_RBSTS_IRQ (1 << 0)
+#define ICH6_RBSTS_OVERRUN (1 << 2)
+#define ICH6_REG_RIRBSIZE 0x5e
+
+#define ICH6_REG_DPLBASE 0x70
+#define ICH6_REG_DPUBASE 0x74
+#define ICH6_DPLBASE_ENABLE 0x1
+
+#define ICH6_SDO0_OFF 0x100
+/* SDO0 is stream 4 (streams 0-3 are SDI); INTCTL/INTSTS use one bit per stream */
+#define HDA_SDO0_STREAM_NR 4
+#define HDA_SDO0_STREAM_INT (1u << HDA_SDO0_STREAM_NR)
+
+/* Stream descriptor register offsets (sd_* uses ICH6_REG_SD_##name) */
+#define ICH6_REG_SD_CTL 0x00
+#define ICH6_REG_SD_STS 0x03
+#define ICH6_REG_SD_LPIB 0x04
+#define ICH6_REG_SD_CBL 0x08
+#define ICH6_REG_SD_LVI 0x0c
+#define ICH6_REG_SD_FORMAT 0x12
+#define ICH6_REG_SD_BDLPL 0x18
+#define ICH6_REG_SD_BDLPU 0x1c
+
+#define AZX_MAX_CODECS 8
+
+#define SD_CTL_STREAM_RESET 0x01
+#define SD_CTL_DMA_START 0x02
+#define SD_CTL_STREAM_TAG_SHIFT 20
+
+#define SD_INT_DESC_ERR 0x10
+#define SD_INT_FIFO_ERR 0x08
+#define SD_INT_COMPLETE 0x04
+#define SD_INT_MASK (SD_INT_DESC_ERR | SD_INT_FIFO_ERR | SD_INT_COMPLETE)
+
+#define ICH6_INT_CTRL_EN 0x40000000
+#define ICH6_INT_GLOBAL_EN 0x80000000
+
+#define ICH6_MAX_CORB_ENTRIES 256
+#define ICH6_MAX_RIRB_ENTRIES 256
+
+#define AC_NODE_ROOT 0x00
+
+enum
+{
+ AC_GRP_AUDIO_FUNCTION = 0x01,
+};
+
+enum
+{
+ AC_WID_AUD_OUT,
+ AC_WID_AUD_IN,
+ AC_WID_AUD_MIX,
+ AC_WID_AUD_SEL,
+ AC_WID_PIN,
+};
+
+enum
+{
+ AC_JACK_LINE_OUT,
+ AC_JACK_SPEAKER,
+ AC_JACK_HP_OUT,
+};
+
+#define AC_VERB_PARAMETERS 0xf0000
+#define AC_VERB_GET_CONNECT_LIST 0xf0200
+#define AC_VERB_GET_PIN_WIDGET_CONTROL 0xf0700
+#define AC_VERB_GET_EAPD_BTLENABLE 0xf0c00
+#define AC_VERB_GET_CONFIG_DEFAULT 0xf1c00
+
+#define AC_VERB_SET_STREAM_FORMAT 0x20000
+#define AC_VERB_SET_AMP_GAIN_MUTE 0x30000
+#define AC_VERB_SET_CONNECT_SEL 0x70100
+#define AC_VERB_SET_POWER_STATE 0x70500
+#define AC_VERB_SET_CHANNEL_STREAMID 0x70600
+#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x70700
+#define AC_VERB_SET_EAPD_BTLENABLE 0x70c00
+#define AC_VERB_SET_CODEC_RESET 0x7ff00
+
+#define AC_PAR_NODE_COUNT 0x04
+#define AC_PAR_FUNCTION_TYPE 0x05
+#define AC_PAR_AUDIO_WIDGET_CAP 0x09
+#define AC_PAR_PIN_CAP 0x0c
+#define AC_PAR_CONNLIST_LEN 0x0e
+#define AC_PAR_AMP_OUT_CAP 0x12
+
+#define AC_FGT_TYPE (0xff << 0)
+
+#define AC_WCAP_POWER (1 << 10)
+#define AC_WCAP_TYPE (0xf << 20)
+#define AC_WCAP_TYPE_SHIFT 20
+
+#define AC_FMT_BITS_16 (1 << 4)
+
+#define AC_PINCAP_OUT (1 << 4)
+
+#define AC_AMPCAP_NUM_STEPS_SHIFT 8
+
+#define AC_CLIST_LENGTH (0x7f << 0)
+#define AC_CLIST_LONG (1 << 7)
+
+#define AC_PWRST_D0 0x00
+
+#define AC_AMP_MUTE (1 << 7)
+#define AC_AMP_GAIN (0x7f)
+#define AC_AMP_SET_RIGHT (1 << 12)
+#define AC_AMP_SET_LEFT (1 << 13)
+#define AC_AMP_SET_INPUT (1 << 14)
+#define AC_AMP_SET_OUTPUT (1 << 15)
+
+#define AC_PINCTL_OUT_EN (1 << 6)
+
+#define AC_DEFCFG_DEVICE_SHIFT 20
+
+/* BDL (Buffer Descriptor List) entry - Intel HDA spec format */
+rt_packed(struct hda_bdl_entry
+{
+ rt_uint64_t addr; /* Address (64-bit) */
+ rt_uint32_t length; /* Length (32-bit, but only bits 15:0 valid) */
+ rt_uint32_t flags; /* Flags (32-bit, but only bits 2:0 valid) */
+});
+
+#define HDA_BDL_FLAGS_IOC (1 << 0) /* Interrupt On Completion */
+
+/* Stream configuration defaults (2 x 4KB = 8KB, matches codec ring in emulators) */
+#define HDA_BDL_COUNT 2
+#define HDA_BUFFER_SIZE 4096 /* Match framework BUFSZ */
+/*
+ * MP pool size == BDL count. Each period keeps its mp_block until LPIB reclaim.
+ * evt_notify calls replay_push directly after each queue push.
+ */
+#define HDA_REPLAY_POLL_MS 2
+#define HDA_SAMPLE_RATE_48K 0
+#define HDA_SAMPLE_RATE_44K (1 << 14)
+
+/* CORB/RIRB memory layout offsets within the shared ring buffer */
+#define HDA_CORB_OFFSET 0
+#define HDA_CORB_MAX_BYTES (ICH6_MAX_CORB_ENTRIES * 4) /* 1024 */
+#define HDA_RIRB_OFFSET HDA_CORB_MAX_BYTES
+#define HDA_RIRB_MAX_BYTES (ICH6_MAX_RIRB_ENTRIES * 8) /* 2048 */
+#define HDA_BDL_OFFSET (HDA_CORB_MAX_BYTES + HDA_RIRB_MAX_BYTES) /* 3072 */
+#define HDA_BDL_BYTES_ROUNDED ((HDA_BDL_COUNT * sizeof(struct hda_bdl_entry) + 127) & ~127)
+#define HDA_DMA_POS_OFFSET (HDA_BDL_OFFSET + HDA_BDL_BYTES_ROUNDED)
+#define HDA_RING_BUF_SIZE (HDA_DMA_POS_OFFSET + 128) /* Total ring buffer area */
+
+struct hda_output
+{
+ rt_uint8_t codec;
+ rt_uint16_t node_id; /* DAC / audio output widget */
+ rt_uint16_t pin_nid; /* Pin complex feeding the DAC */
+
+ rt_uint32_t sample_rate;
+ int amp_gain_steps;
+ int num_channels;
+ rt_uint8_t volume;
+};
+
+struct intel_hda
+{
+ struct rt_audio_device parent;
+
+ void *ich6_reg_base;
+ struct rt_pci_device *pdev;
+
+ int stream;
+ struct rt_audio_configure config;
+
+ struct hda_output output;
+
+ /* Shared DMA-able memory for CORB, RIRB, BDL, DMA Position */
+ void *ring_buffer;
+ rt_ubase_t ring_buffer_dma;
+
+ rt_uint32_t *corb; /* Command Outbound Ring Buffer */
+ rt_uint64_t *rirb; /* Response Inbound Ring Buffer */
+ struct hda_bdl_entry *bdl; /* Buffer Descriptor List */
+ rt_uint64_t *dma_pos; /* DMA Position in Current Buffer */
+
+ rt_size_t corb_entries; /* Number of CORB entries */
+ rt_size_t rirb_entries; /* Number of RIRB entries */
+ rt_uint16_t rirb_read_pointer; /* RIRB software read pointer */
+
+ /* Audio data buffer for DMA */
+ void *audio_buffer;
+ rt_ubase_t audio_buffer_dma;
+ rt_uint32_t audio_buffer_size;
+
+ /* Per-BDL period state */
+ struct intel_hda_period
+ {
+ rt_bool_t pending;
+ void *mp_block;
+ rt_tick_t submit_tick; /* when PCM was handed to DMA (for pace reclaim) */
+ } periods[HDA_BDL_COUNT];
+
+ rt_uint32_t last_reclaimed_period; /* Last BDL period reclaimed for refill */
+ rt_uint32_t last_lpib; /* Last LPIB (bytes consumed by controller) */
+ volatile rt_bool_t playback_active;
+ volatile rt_bool_t dma_started;
+
+ /* Replay worker (bypass dev_audio tx_complete) */
+ struct rt_semaphore replay_sem;
+ struct rt_thread *replay_thread;
+ rt_bool_t replay_thread_exit;
+ struct rt_mutex replay_push_lock;
+
+ /* Polling timer: track LPIB consumption (matches controller DMA read) */
+ struct rt_timer poll_timer;
+};
+
+static struct intel_hda *intel_hda_replay_notify_dev;
+
+#define raw_to_intel_hda(raw) rt_container_of(raw, struct intel_hda, parent)
+
+#define intel_hda_writel(hda, reg, val) HWREG32((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg) = (val)
+#define intel_hda_writew(hda, reg, val) HWREG16((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg) = (val)
+#define intel_hda_writeb(hda, reg, val) HWREG8((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg) = (val)
+
+#define intel_hda_readl(hda, reg) HWREG32((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg)
+#define intel_hda_readw(hda, reg) HWREG16((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg)
+#define intel_hda_readb(hda, reg) HWREG8((rt_ubase_t)(hda)->ich6_reg_base + ICH6_REG_##reg)
+
+/* Output stream descriptor base offset (SDO0) */
+#define HDA_SD_OUT_BASE ICH6_SDO0_OFF
+
+/* Stream descriptor register access macros */
+#define sd_writel(hda, base, reg, val) HWREG32((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg) = (val)
+#define sd_writew(hda, base, reg, val) HWREG16((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg) = (val)
+#define sd_writeb(hda, base, reg, val) HWREG8((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg) = (val)
+#define sd_readl(hda, base, reg) HWREG32((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg)
+#define sd_readw(hda, base, reg) HWREG16((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg)
+#define sd_readb(hda, base, reg) HWREG8((rt_ubase_t)(hda)->ich6_reg_base + (base) + ICH6_REG_SD_##reg)
+
+/*
+ * CORB/RIRB - Codec command/response ring buffers
+ */
+
+static rt_uint32_t intel_hda_codec_query(struct intel_hda *hda,
+ int codec, int nid, rt_uint32_t payload);
+
+static rt_uint16_t intel_hda_conn_list_entry(struct intel_hda *hda, int codec,
+ int wid, rt_uint32_t conn_len, int index)
+{
+ rt_uint32_t resp;
+ rt_uint16_t conn;
+
+ if (conn_len & AC_CLIST_LONG)
+ {
+ resp = intel_hda_codec_query(hda, codec, wid,
+ AC_VERB_GET_CONNECT_LIST | (index / 2));
+ if (resp == 0xffffffff)
+ {
+ return 0xffff;
+ }
+ conn = (index & 1) ? (resp >> 16) : (resp & 0xffff);
+ }
+ else
+ {
+ resp = intel_hda_codec_query(hda, codec, wid,
+ AC_VERB_GET_CONNECT_LIST | index);
+ if (resp == 0xffffffff)
+ {
+ return 0xffff;
+ }
+ conn = resp & 0xff;
+ }
+
+ return conn;
+}
+
+static rt_bool_t intel_hda_route_to_dac(struct intel_hda *hda, int codec,
+ int wid, int dac_nid)
+{
+ rt_uint32_t conn_len;
+ int num_conns;
+ int i;
+
+ conn_len = intel_hda_codec_query(hda, codec, wid,
+ AC_VERB_PARAMETERS | AC_PAR_CONNLIST_LEN);
+ if (conn_len == 0xffffffff)
+ {
+ return RT_FALSE;
+ }
+ num_conns = conn_len & AC_CLIST_LENGTH;
+ for (i = 0; i < num_conns; i++)
+ {
+ rt_uint16_t conn_nid;
+ rt_uint32_t cap;
+ int type;
+
+ conn_nid = intel_hda_conn_list_entry(hda, codec, wid, conn_len, i);
+ if (conn_nid == 0xffff)
+ {
+ continue;
+ }
+ if (conn_nid == (rt_uint16_t)dac_nid)
+ {
+ intel_hda_codec_query(hda, codec, wid, AC_VERB_SET_CONNECT_SEL | i);
+ return RT_TRUE;
+ }
+
+ cap = intel_hda_codec_query(hda, codec, conn_nid,
+ AC_VERB_PARAMETERS | AC_PAR_AUDIO_WIDGET_CAP);
+ if (cap == 0xffffffff)
+ {
+ continue;
+ }
+ type = (cap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+ if (type == AC_WID_AUD_SEL || type == AC_WID_AUD_MIX)
+ {
+ if (intel_hda_route_to_dac(hda, codec, conn_nid, dac_nid))
+ {
+ intel_hda_codec_query(hda, codec, wid, AC_VERB_SET_CONNECT_SEL | i);
+ return RT_TRUE;
+ }
+ }
+ }
+
+ return RT_FALSE;
+}
+
+static void intel_hda_set_widget_amp(struct intel_hda *hda, int codec, int nid,
+ int gain, rt_bool_t output)
+{
+ rt_uint32_t meta = AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
+
+ meta |= output ? AC_AMP_SET_OUTPUT : AC_AMP_SET_INPUT;
+ intel_hda_codec_query(hda, codec, nid,
+ AC_VERB_SET_AMP_GAIN_MUTE | meta | (gain & AC_AMP_GAIN));
+}
+
+static void intel_hda_setup_playback_path(struct intel_hda *hda)
+{
+ int gain;
+
+ if (!hda->output.node_id)
+ {
+ return;
+ }
+ if (hda->output.pin_nid)
+ {
+ if (!intel_hda_route_to_dac(hda, hda->output.codec,
+ hda->output.pin_nid, hda->output.node_id))
+ {
+ LOG_W("Failed to route pin %d to DAC %d",
+ hda->output.pin_nid, hda->output.node_id);
+ }
+ }
+
+ gain = hda->output.amp_gain_steps;
+ if (gain > 0)
+ {
+ gain--;
+ }
+ intel_hda_set_widget_amp(hda, hda->output.codec, hda->output.node_id,
+ gain, RT_TRUE);
+
+ if (hda->output.pin_nid)
+ {
+ intel_hda_set_widget_amp(hda, hda->output.codec, hda->output.pin_nid,
+ gain, RT_TRUE);
+ }
+}
+
+static void intel_hda_setup_corb(struct intel_hda *hda)
+{
+ rt_uint8_t reg;
+ rt_uint64_t corb_base;
+ int timeout;
+
+ reg = intel_hda_readb(hda, CORBSIZE);
+
+ /* Choose the largest supported CORB size */
+ if (reg & (1 << 6))
+ {
+ hda->corb_entries = 256;
+ reg |= 0x2;
+ }
+ else if (reg & (1 << 5))
+ {
+ hda->corb_entries = 16;
+ reg |= 0x1;
+ }
+ else if (reg & (1 << 4))
+ {
+ hda->corb_entries = 2;
+ }
+ else
+ {
+ LOG_E("No supported CORB size!");
+ return;
+ }
+
+ intel_hda_writeb(hda, CORBSIZE, reg);
+
+ /* Reset CORB pointers */
+ intel_hda_writew(hda, CORBWP, 0);
+
+ /* Reset CORB read pointer: set RST, wait for ack, clear RST, wait for ack */
+ intel_hda_writew(hda, CORBRP, ICH6_CORBRP_RST);
+ timeout = 10000;
+ while (!(intel_hda_readw(hda, CORBRP) & ICH6_CORBRP_RST))
+ {
+ if (--timeout == 0)
+ {
+ intel_hda_writew(hda, CORBRP, 0);
+ goto corbrp_done;
+ }
+ rt_hw_cpu_relax();
+ }
+ intel_hda_writew(hda, CORBRP, 0);
+ timeout = 10000;
+ while (intel_hda_readw(hda, CORBRP) & ICH6_CORBRP_RST)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+corbrp_done:
+
+ /* Set CORB base address */
+ corb_base = hda->ring_buffer_dma + HDA_CORB_OFFSET;
+ intel_hda_writel(hda, CORBLBASE, (rt_uint32_t)(corb_base & 0xffffffff));
+ intel_hda_writel(hda, CORBUBASE, (rt_uint32_t)(corb_base >> 32));
+
+ /* Set virtual pointer */
+ hda->corb = (rt_uint32_t *)((rt_ubase_t)hda->ring_buffer + HDA_CORB_OFFSET);
+
+ /* Clear CORB status */
+ intel_hda_writeb(hda, CORBSTS, ICH6_CORBSTS_CMEI);
+
+ /* Start CORB DMA engine + enable memory error interrupt */
+ intel_hda_writeb(hda, CORBCTL, ICH6_CORBCTL_CMEIE | ICH6_CORBCTL_RUN);
+
+ LOG_D("CORB setup: %d entries", hda->corb_entries);
+}
+
+static void intel_hda_setup_rirb(struct intel_hda *hda)
+{
+ rt_uint8_t reg;
+ rt_uint64_t rirb_base;
+
+ reg = intel_hda_readb(hda, RIRBSIZE);
+
+ /* Choose the largest supported RIRB size */
+ if (reg & (1 << 6))
+ {
+ hda->rirb_entries = 256;
+ reg |= 0x2;
+ }
+ else if (reg & (1 << 5))
+ {
+ hda->rirb_entries = 16;
+ reg |= 0x1;
+ }
+ else if (reg & (1 << 4))
+ {
+ hda->rirb_entries = 2;
+ }
+ else
+ {
+ LOG_E("No supported RIRB size!");
+ return;
+ }
+
+ intel_hda_writeb(hda, RIRBSIZE, reg);
+
+ /* Reset RIRB write pointer */
+ intel_hda_writew(hda, RIRBWP, ICH6_RIRBWP_RST);
+ hda->rirb_read_pointer = 0;
+
+ /* Set RIRB base address */
+ rirb_base = hda->ring_buffer_dma + HDA_RIRB_OFFSET;
+ intel_hda_writel(hda, RIRBLBASE, (rt_uint32_t)(rirb_base & 0xffffffff));
+ intel_hda_writel(hda, RIRBUBASE, (rt_uint32_t)(rirb_base >> 32));
+
+ /* Set virtual pointer */
+ hda->rirb = (rt_uint64_t *)((rt_ubase_t)hda->ring_buffer + HDA_RIRB_OFFSET);
+
+ /* Set response interrupt count threshold */
+ intel_hda_writeb(hda, RINTCNT, hda->rirb_entries - 1);
+
+ /* Clear RIRB status */
+ intel_hda_writeb(hda, RIRBSTS, ICH6_RBSTS_IRQ | ICH6_RBSTS_OVERRUN);
+
+ /* Start RIRB DMA engine + enable IRQ + enable overrun interrupt */
+ intel_hda_writeb(hda, RIRBCTL, ICH6_RBCTL_IRQ_EN | ICH6_RBCTL_DMA_EN | ICH6_RBCTL_OVERRUN_EN);
+
+ LOG_D("RIRB setup: %d entries", hda->rirb_entries);
+}
+
+static int intel_hda_corb_write(struct intel_hda *hda, rt_uint32_t verb)
+{
+ rt_uint16_t wp, rp, next;
+ int timeout = 10000;
+
+ wp = intel_hda_readw(hda, CORBWP) & 0xff;
+ next = (wp + 1) % hda->corb_entries;
+
+ /* Wait until there is a free entry in the CORB with timeout */
+ do
+ {
+ rp = intel_hda_readw(hda, CORBRP) & 0xff;
+ if (--timeout == 0)
+ {
+ LOG_E("CORB write timeout! wp=%d rp=%d next=%d", wp, rp, next);
+ return -RT_ETIMEOUT;
+ }
+ } while (next == rp);
+
+ /* Write the command verb and advance the write pointer */
+ hda->corb[next] = verb;
+ rt_hw_dmb();
+ intel_hda_writew(hda, CORBWP, next);
+
+ return RT_EOK;
+}
+
+static int intel_hda_rirb_read(struct intel_hda *hda, rt_uint64_t *response)
+{
+ rt_uint16_t wp;
+ rt_uint16_t rp = hda->rirb_read_pointer;
+ int timeout = 10000; /* ~10ms timeout */
+
+ /* Wait for an unread entry in the RIRB with timeout */
+ do
+ {
+ wp = intel_hda_readw(hda, RIRBWP) & 0xff;
+ if (--timeout == 0)
+ {
+ LOG_E("RIRB read timeout! wp=%d rp=%d", wp, rp);
+ return -RT_ETIMEOUT;
+ }
+ } while (wp == rp);
+
+ /* Read from RIRB */
+ rp = (rp + 1) % hda->rirb_entries;
+ hda->rirb_read_pointer = rp;
+
+ *response = hda->rirb[rp];
+
+ /* Clear RIRB status */
+ intel_hda_writeb(hda, RIRBSTS, ICH6_RBSTS_IRQ | ICH6_RBSTS_OVERRUN);
+
+ return RT_EOK;
+}
+
+/*
+ * Codec communication - send a verb and get a response
+ * verb encoding: [codec_addr:4][nid:8][payload:20]
+ */
+static rt_uint32_t intel_hda_codec_query(struct intel_hda *hda,
+ int codec, int nid, rt_uint32_t payload)
+{
+ rt_uint64_t response;
+ rt_uint32_t verb = ((codec & 0xf) << 28) | ((nid & 0xff) << 20) | (payload & 0xfffff);
+
+ LOG_D("codec_query: codec=%d nid=%d payload=0x%05x verb=0x%08x",
+ codec, nid, payload & 0xfffff, verb);
+
+ if (intel_hda_corb_write(hda, verb) != RT_EOK)
+ {
+ LOG_E("CORB write failed: codec=%d nid=%d", codec, nid);
+ return 0xffffffff;
+ }
+
+ if (intel_hda_rirb_read(hda, &response) != RT_EOK)
+ {
+ LOG_E("RIRB read failed: codec=%d nid=%d", codec, nid);
+ return 0xffffffff;
+ }
+
+ LOG_D(" response=0x%08x", (rt_uint32_t)(response & 0xffffffff));
+
+ return (rt_uint32_t)(response & 0xffffffff);
+}
+
+/*
+ * Codec enumeration and widget initialization
+ */
+
+static void intel_hda_widget_init(struct intel_hda *hda, int codec, int nid)
+{
+ rt_uint32_t widget_cap, amp_cap, eapd_btl;
+ int type;
+
+ widget_cap = intel_hda_codec_query(hda, codec, nid,
+ AC_VERB_PARAMETERS | AC_PAR_AUDIO_WIDGET_CAP);
+ if (widget_cap == 0xffffffff)
+ {
+ return;
+ }
+ type = (widget_cap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+
+ amp_cap = intel_hda_codec_query(hda, codec, nid,
+ AC_VERB_PARAMETERS | AC_PAR_AMP_OUT_CAP);
+ eapd_btl = intel_hda_codec_query(hda, codec, nid, AC_VERB_GET_EAPD_BTLENABLE);
+
+ switch (type)
+ {
+ case AC_WID_PIN:
+ {
+ rt_uint32_t pin_cap, ctl, defcfg;
+ int device;
+
+ pin_cap = intel_hda_codec_query(hda, codec, nid,
+ AC_VERB_PARAMETERS | AC_PAR_PIN_CAP);
+ if (!(pin_cap & AC_PINCAP_OUT))
+ {
+ return;
+ }
+ defcfg = intel_hda_codec_query(hda, codec, nid, AC_VERB_GET_CONFIG_DEFAULT);
+ device = (defcfg >> AC_DEFCFG_DEVICE_SHIFT) & 0xf;
+
+ if (!hda->output.pin_nid || device == AC_JACK_SPEAKER ||
+ device == AC_JACK_HP_OUT || device == AC_JACK_LINE_OUT)
+ {
+ hda->output.pin_nid = nid;
+ }
+ ctl = intel_hda_codec_query(hda, codec, nid, AC_VERB_GET_PIN_WIDGET_CONTROL);
+ ctl |= AC_PINCTL_OUT_EN;
+ intel_hda_codec_query(hda, codec, nid, AC_VERB_SET_PIN_WIDGET_CONTROL | ctl);
+
+ /* Enable EAPD */
+ intel_hda_codec_query(hda, codec, nid, AC_VERB_SET_EAPD_BTLENABLE | eapd_btl | 0x2);
+ break;
+ }
+
+ case AC_WID_AUD_OUT:
+ {
+ if (!hda->output.node_id)
+ {
+ LOG_D("Using audio output widget: codec=%d nid=%d", codec, nid);
+ hda->output.codec = codec;
+ hda->output.node_id = nid;
+ hda->output.amp_gain_steps = (amp_cap >> AC_AMPCAP_NUM_STEPS_SHIFT) & 0x7f;
+ }
+
+ intel_hda_codec_query(hda, codec, nid, AC_VERB_SET_EAPD_BTLENABLE | eapd_btl | 0x2);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ /* Power up widget if it has power control */
+ if (widget_cap & AC_WCAP_POWER)
+ {
+ intel_hda_codec_query(hda, codec, nid, AC_VERB_SET_POWER_STATE | AC_PWRST_D0);
+ }
+}
+
+static rt_err_t intel_hda_codec_enumerate_widgets(struct intel_hda *hda, int codec)
+{
+ rt_uint32_t param;
+ int num_fg, fg_start;
+ int i, j;
+
+ param = intel_hda_codec_query(hda, codec, AC_NODE_ROOT,
+ AC_VERB_PARAMETERS | AC_PAR_NODE_COUNT);
+
+ if (param == 0xffffffff)
+ {
+ return -RT_ERROR;
+ }
+ num_fg = param & 0xff;
+ fg_start = (param >> 16) & 0xff;
+
+ LOG_D("Codec %d: %d function groups starting at ID %d", codec, num_fg, fg_start);
+
+ for (i = 0; i < num_fg; i++)
+ {
+ int num_widgets, widgets_start;
+ int fg_id = fg_start + i;
+
+ param = intel_hda_codec_query(hda, codec, fg_id,
+ AC_VERB_PARAMETERS | AC_PAR_FUNCTION_TYPE);
+
+ if (param == 0xffffffff)
+ {
+ continue;
+ }
+ if ((param & AC_FGT_TYPE) != AC_GRP_AUDIO_FUNCTION)
+ {
+ LOG_D(" FG %d: non-audio type %d, skipping", fg_id, param & AC_FGT_TYPE);
+ continue;
+ }
+
+ /* Power up the function group */
+ intel_hda_codec_query(hda, codec, fg_id,
+ AC_VERB_SET_POWER_STATE | AC_PWRST_D0);
+ rt_hw_us_delay(1000);
+
+ param = intel_hda_codec_query(hda, codec, fg_id,
+ AC_VERB_PARAMETERS | AC_PAR_NODE_COUNT);
+
+ if (param == 0xffffffff)
+ {
+ continue;
+ }
+ num_widgets = param & 0xff;
+ widgets_start = (param >> 16) & 0xff;
+
+ LOG_D(" FG %d: %d widgets starting at ID %d", fg_id, num_widgets, widgets_start);
+
+ for (j = 0; j < num_widgets; j++)
+ {
+ intel_hda_widget_init(hda, codec, widgets_start + j);
+ }
+ }
+
+ if (hda->output.node_id)
+ {
+ intel_hda_setup_playback_path(hda);
+ }
+ return hda->output.node_id ? RT_EOK : -RT_ERROR;
+}
+
+static void intel_hda_enumerate_codecs(struct intel_hda *hda)
+{
+ rt_uint16_t statests;
+ int i;
+
+ statests = intel_hda_readw(hda, STATESTS);
+ LOG_D("STATESTS = 0x%04x", statests);
+
+ for (i = 0; i < AZX_MAX_CODECS; i++)
+ {
+ if (statests & (1 << i))
+ {
+ LOG_I("Found codec at address %d", i);
+ intel_hda_codec_query(hda, i, AC_NODE_ROOT, AC_VERB_SET_CODEC_RESET);
+ rt_hw_us_delay(1000);
+ if (intel_hda_codec_enumerate_widgets(hda, i) == RT_EOK)
+ {
+ return;
+ }
+ }
+ }
+
+ LOG_W("No usable audio output widget found!");
+}
+
+/*
+ * Configure the output widget for the current sample rate/channels
+ */
+static void intel_hda_configure_output(struct intel_hda *hda)
+{
+ rt_uint16_t format;
+
+ if (!hda->output.node_id)
+ {
+ LOG_W("configure_output: no output node");
+ return;
+ }
+
+ /* Build the HDA format register value */
+ format = AC_FMT_BITS_16 | hda->output.sample_rate | (hda->output.num_channels - 1);
+
+ /* Set converter format */
+ intel_hda_codec_query(hda, hda->output.codec, hda->output.node_id,
+ AC_VERB_SET_STREAM_FORMAT | format);
+
+ /* Set stream tag and channel */
+ /* Stream 1 (bit 7:4), Channel 0 (bit 3:0) => 0x10 */
+ intel_hda_codec_query(hda, hda->output.codec, hda->output.node_id,
+ AC_VERB_SET_CHANNEL_STREAMID | 0x10);
+
+ /* Set the same format in the controller stream descriptor */
+ sd_writew(hda, HDA_SD_OUT_BASE, FORMAT, format);
+
+}
+
+/*
+ * Setup the BDL (Buffer Descriptor List) and stream descriptor
+ */
+static void intel_hda_setup_stream(struct intel_hda *hda)
+{
+ rt_uint64_t bdl_base, dma_pos_base;
+ int i;
+
+ /* Reset the stream descriptor */
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, SD_CTL_STREAM_RESET);
+ while (!(sd_readb(hda, HDA_SD_OUT_BASE, CTL) & SD_CTL_STREAM_RESET))
+ {
+ rt_hw_cpu_relax();
+ }
+ rt_hw_us_delay(100);
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, 0);
+ while (sd_readb(hda, HDA_SD_OUT_BASE, CTL) & SD_CTL_STREAM_RESET)
+ {
+ rt_hw_cpu_relax();
+ }
+
+ /* Set stream tag in bits [23:20] of CTL (32-bit register) */
+ sd_writel(hda, HDA_SD_OUT_BASE, CTL, (1 << SD_CTL_STREAM_TAG_SHIFT));
+
+ /* Set cyclic buffer length = total buffer size */
+ sd_writel(hda, HDA_SD_OUT_BASE, CBL, hda->audio_buffer_size);
+
+ /* Set last valid index = BDL_COUNT - 1 (all BDL entries are valid) */
+ sd_writew(hda, HDA_SD_OUT_BASE, LVI, HDA_BDL_COUNT - 1);
+
+ /* Set the format */
+ {
+ rt_uint16_t fmt = AC_FMT_BITS_16 | hda->output.sample_rate | (hda->output.num_channels - 1);
+ sd_writew(hda, HDA_SD_OUT_BASE, FORMAT, fmt);
+ }
+
+ /* Set BDL base address */
+ bdl_base = hda->ring_buffer_dma + HDA_BDL_OFFSET;
+ sd_writel(hda, HDA_SD_OUT_BASE, BDLPL, (rt_uint32_t)(bdl_base & 0xffffffff));
+ sd_writel(hda, HDA_SD_OUT_BASE, BDLPU, (rt_uint32_t)(bdl_base >> 32));
+
+ /* Fill BDL entries - each entry is a period, set IOC on each */
+ hda->bdl = (struct hda_bdl_entry *)((rt_ubase_t)hda->ring_buffer + HDA_BDL_OFFSET);
+ for (i = 0; i < HDA_BDL_COUNT; i++)
+ {
+ rt_uint64_t buf_addr = hda->audio_buffer_dma + (i * HDA_BUFFER_SIZE);
+ hda->bdl[i].addr = buf_addr;
+ hda->bdl[i].length = HDA_BUFFER_SIZE; /* Intel HDA: only bits 15:0 used */
+ /* Each BDL entry is a period - set IOC on each for per-period interrupt */
+ hda->bdl[i].flags = HDA_BDL_FLAGS_IOC;
+ }
+
+ /* Clear audio buffer */
+ rt_memset(hda->audio_buffer, 0, hda->audio_buffer_size);
+
+ /* Initialize DMA Position Buffer */
+ /* Intel HDA spec: Each stream position entry is 32 bytes (aligned) */
+ hda->dma_pos = (rt_uint64_t *)((rt_ubase_t)hda->ring_buffer + HDA_DMA_POS_OFFSET);
+ /* Clear position buffer for stream 0 (output stream) - 32 bytes */
+ rt_memset(hda->dma_pos, 0, 32);
+
+ dma_pos_base = hda->ring_buffer_dma + HDA_DMA_POS_OFFSET;
+ intel_hda_writel(hda, DPLBASE, ((rt_uint32_t)(dma_pos_base & 0xffffffff)) | ICH6_DPLBASE_ENABLE);
+ intel_hda_writel(hda, DPUBASE, (rt_uint32_t)(dma_pos_base >> 32));
+
+ /*
+ * BDL IOC flags stay set for the hardware; stream completion IRQs are
+ * left disabled. LPIB polling drives reclaim/refill — QEMU fires IOC
+ * far too often and starves the soft timer / replay worker.
+ */
+}
+
+/*
+ * Audio ops implementation
+ */
+
+static rt_err_t intel_hda_audio_getcaps(struct rt_audio_device *audio,
+ struct rt_audio_caps *caps)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ switch (caps->main_type)
+ {
+ case AUDIO_TYPE_QUERY:
+ switch (caps->sub_type)
+ {
+ case AUDIO_TYPE_QUERY:
+ caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER;
+ break;
+ default:
+ return -RT_ENOSYS;
+ }
+ break;
+
+ case AUDIO_TYPE_OUTPUT:
+ switch (caps->sub_type)
+ {
+ case AUDIO_DSP_PARAM:
+ caps->udata.config.samplerate = hda->config.samplerate;
+ caps->udata.config.channels = hda->config.channels;
+ caps->udata.config.samplebits = hda->config.samplebits;
+ break;
+ case AUDIO_DSP_SAMPLERATE:
+ caps->udata.config.samplerate = hda->config.samplerate;
+ break;
+ case AUDIO_DSP_CHANNELS:
+ caps->udata.config.channels = hda->config.channels;
+ break;
+ case AUDIO_DSP_SAMPLEBITS:
+ caps->udata.config.samplebits = hda->config.samplebits;
+ break;
+ default:
+ return -RT_ENOSYS;
+ }
+ break;
+
+ case AUDIO_TYPE_MIXER:
+ switch (caps->sub_type)
+ {
+ case AUDIO_MIXER_QUERY:
+ caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_MUTE;
+ break;
+ case AUDIO_MIXER_VOLUME:
+ caps->udata.value = hda->output.volume;
+ break;
+ case AUDIO_MIXER_MUTE:
+ caps->udata.value = 0;
+ break;
+ default:
+ return -RT_ENOSYS;
+ }
+ break;
+
+ default:
+ return -RT_ENOSYS;
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t intel_hda_audio_configure(struct rt_audio_device *audio,
+ struct rt_audio_caps *caps)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ switch (caps->main_type)
+ {
+ case AUDIO_TYPE_OUTPUT:
+ switch (caps->sub_type)
+ {
+ case AUDIO_DSP_SAMPLERATE:
+ hda->config.samplerate = caps->udata.config.samplerate;
+ if (hda->config.samplerate == 44100)
+ {
+ hda->output.sample_rate = HDA_SAMPLE_RATE_44K;
+ }
+ else
+ {
+ hda->config.samplerate = 48000;
+ hda->output.sample_rate = HDA_SAMPLE_RATE_48K;
+ }
+ intel_hda_configure_output(hda);
+ break;
+ case AUDIO_DSP_CHANNELS:
+ hda->config.channels = caps->udata.config.channels;
+ if (hda->config.channels < 1 || hda->config.channels > 2)
+ {
+ hda->config.channels = 2;
+ }
+ hda->output.num_channels = hda->config.channels;
+ intel_hda_configure_output(hda);
+ break;
+ case AUDIO_DSP_SAMPLEBITS:
+ hda->config.samplebits = caps->udata.config.samplebits;
+ break;
+ case AUDIO_DSP_PARAM:
+ hda->config.samplerate = caps->udata.config.samplerate;
+ hda->config.channels = caps->udata.config.channels;
+ hda->config.samplebits = caps->udata.config.samplebits;
+ if (hda->config.samplerate == 44100)
+ {
+ hda->output.sample_rate = HDA_SAMPLE_RATE_44K;
+ }
+ else
+ {
+ hda->config.samplerate = 48000;
+ hda->output.sample_rate = HDA_SAMPLE_RATE_48K;
+ }
+ hda->output.num_channels = hda->config.channels;
+ intel_hda_configure_output(hda);
+ break;
+ default:
+ return -RT_ENOSYS;
+ }
+ break;
+
+ case AUDIO_TYPE_MIXER:
+ switch (caps->sub_type)
+ {
+ case AUDIO_MIXER_VOLUME:
+ {
+ rt_uint32_t vol = caps->udata.value;
+ int gain;
+ int meta = AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT | AC_AMP_SET_OUTPUT;
+
+ hda->output.volume = (vol > AUDIO_VOLUME_MAX) ? AUDIO_VOLUME_MAX : vol;
+
+ if (hda->output.volume == 0)
+ {
+ gain = AC_AMP_MUTE;
+ }
+ else
+ {
+ gain = hda->output.volume * hda->output.amp_gain_steps / AUDIO_VOLUME_MAX;
+ }
+
+ if (hda->output.node_id)
+ {
+ intel_hda_codec_query(hda, hda->output.codec, hda->output.node_id,
+ AC_VERB_SET_AMP_GAIN_MUTE | meta | gain);
+ }
+ break;
+ }
+ case AUDIO_MIXER_MUTE:
+ {
+ int meta = AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT | AC_AMP_SET_OUTPUT;
+ if (hda->output.node_id)
+ {
+ intel_hda_codec_query(hda, hda->output.codec, hda->output.node_id,
+ AC_VERB_SET_AMP_GAIN_MUTE | meta | AC_AMP_MUTE);
+ }
+ break;
+ }
+ default:
+ return -RT_ENOSYS;
+ }
+ break;
+
+ default:
+ return -RT_ENOSYS;
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t intel_hda_audio_init(struct rt_audio_device *audio)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ /* Set default audio configuration */
+ hda->config.samplerate = 48000;
+ hda->config.channels = 2;
+ hda->config.samplebits = 16;
+ hda->output.sample_rate = HDA_SAMPLE_RATE_48K;
+ hda->output.num_channels = 2;
+ hda->output.volume = AUDIO_VOLUME_MAX;
+ hda->last_reclaimed_period = 0xffffffff;
+
+ return RT_EOK;
+}
+
+/* Forward declaration */
+static void intel_hda_poll_timer(void *parameter);
+static void intel_hda_poll_lpib_nolock(struct intel_hda *hda);
+static rt_tick_t intel_hda_period_ticks(struct intel_hda *hda);
+
+static rt_bool_t intel_hda_period_ready_to_reclaim(struct intel_hda *hda, rt_uint32_t period)
+{
+ rt_tick_t need, elapsed;
+
+ if (!hda->periods[period].pending)
+ {
+ return RT_FALSE;
+ }
+ if (hda->periods[period].submit_tick == 0)
+ {
+ return RT_TRUE;
+ }
+ need = intel_hda_period_ticks(hda);
+ elapsed = rt_tick_get() - hda->periods[period].submit_tick;
+
+ return elapsed >= need;
+}
+
+/*
+ * dev_audio _audio_send_replay_frame() can spin when read_index is stale;
+ * this driver pops the replay queue directly instead of rt_audio_tx_complete.
+ */
+
+static void intel_hda_sync_output_format(struct intel_hda *hda)
+{
+ if (hda->config.samplerate == 44100)
+ {
+ hda->output.sample_rate = HDA_SAMPLE_RATE_44K;
+ }
+ else
+ {
+ hda->output.sample_rate = HDA_SAMPLE_RATE_48K;
+ if (hda->config.samplerate != 48000)
+ {
+ hda->config.samplerate = 48000;
+ }
+ }
+
+ if (hda->config.channels >= 1 && hda->config.channels <= 2)
+ {
+ hda->output.num_channels = hda->config.channels;
+ }
+ else
+ {
+ hda->output.num_channels = 2;
+ }
+}
+
+static void intel_hda_replay_queue_reset(struct intel_hda *hda)
+{
+ struct rt_audio_replay *replay = hda->parent.replay;
+
+ if (!replay)
+ {
+ return;
+ }
+ replay->read_index = 0;
+ replay->pos = 0;
+ replay->write_index = 0;
+}
+
+static void intel_hda_schedule_replay(struct intel_hda *hda)
+{
+ rt_sem_release(&hda->replay_sem);
+}
+
+static void intel_hda_period_reset_all(struct intel_hda *hda)
+{
+ rt_uint32_t i;
+
+ for (i = 0; i < HDA_BDL_COUNT; i++)
+ {
+ if (hda->periods[i].mp_block)
+ {
+ rt_mp_free(hda->periods[i].mp_block);
+ hda->periods[i].mp_block = RT_NULL;
+ }
+ hda->periods[i].pending = RT_FALSE;
+ hda->periods[i].submit_tick = 0;
+ }
+}
+
+static rt_tick_t intel_hda_period_ticks(struct intel_hda *hda)
+{
+ rt_uint32_t ch = hda->output.num_channels ? hda->output.num_channels : 2;
+ rt_uint32_t rate = hda->config.samplerate ? hda->config.samplerate : 48000;
+ rt_uint32_t frames = HDA_BUFFER_SIZE / (ch * 2);
+ rt_uint32_t ms;
+
+ if (rate == 0)
+ {
+ rate = 48000;
+ }
+ ms = (frames * 1000U + rate - 1U) / rate;
+ if (ms == 0)
+ {
+ ms = 1;
+ }
+ return rt_tick_from_millisecond(ms);
+}
+
+static rt_int32_t intel_hda_find_free_period(struct intel_hda *hda)
+{
+ rt_uint32_t i;
+
+ for (i = 0; i < HDA_BDL_COUNT; i++)
+ {
+ if (!hda->periods[i].pending)
+ {
+ return (rt_int32_t)i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Copy one replay-queue block into a BDL period and hand it to DMA.
+ * mp_block is retained until LPIB reclaim.
+ */
+static rt_bool_t intel_hda_push_one_period(struct intel_hda *hda, rt_uint32_t period)
+{
+ struct rt_audio_device *audio = &hda->parent;
+ struct rt_audio_replay *replay = audio->replay;
+ const void *data;
+ rt_size_t src_size;
+ rt_uint8_t *dst;
+ rt_err_t err;
+
+ if (!replay || period >= HDA_BDL_COUNT || hda->periods[period].pending)
+ {
+ return RT_FALSE;
+ }
+ err = rt_data_queue_peek(&replay->queue, &data, &src_size);
+ if (err != RT_EOK || src_size == 0)
+ {
+ return RT_FALSE;
+ }
+
+ dst = (rt_uint8_t *)hda->audio_buffer + period * HDA_BUFFER_SIZE;
+ rt_memset(dst, 0, HDA_BUFFER_SIZE);
+ if (src_size > HDA_BUFFER_SIZE)
+ {
+ src_size = HDA_BUFFER_SIZE;
+ }
+ rt_memcpy(dst, data, src_size);
+
+ err = rt_data_queue_pop(&replay->queue, &data, &src_size, RT_WAITING_NO);
+ if (err != RT_EOK)
+ {
+ return RT_FALSE;
+ }
+
+ hda->periods[period].mp_block = (void *)data;
+ hda->periods[period].pending = RT_TRUE;
+ if (!hda->dma_started)
+ {
+ hda->periods[period].submit_tick = rt_tick_get() +
+ intel_hda_period_ticks(hda) * period;
+ }
+ else
+ {
+ hda->periods[period].submit_tick = rt_tick_get();
+ }
+ if (audio->ops && audio->ops->transmit)
+ {
+ audio->ops->transmit(audio, dst, RT_NULL, HDA_BUFFER_SIZE);
+ }
+ return RT_TRUE;
+}
+
+static void intel_hda_replay_push_nolock(struct intel_hda *hda)
+{
+ struct rt_audio_replay *replay = hda->parent.replay;
+ rt_int32_t period;
+ rt_uint32_t i;
+ rt_bool_t all_pending;
+
+ if (!replay || !hda->playback_active)
+ {
+ return;
+ }
+ if (!hda->dma_started)
+ {
+ for (i = 0; i < HDA_BDL_COUNT; i++)
+ {
+ if (hda->periods[i].pending)
+ {
+ continue;
+ }
+ if (rt_data_queue_len(&replay->queue) == 0)
+ {
+ break;
+ }
+ if (!intel_hda_push_one_period(hda, i))
+ {
+ break;
+ }
+ }
+
+ all_pending = RT_TRUE;
+ for (i = 0; i < HDA_BDL_COUNT; i++)
+ {
+ if (!hda->periods[i].pending)
+ {
+ all_pending = RT_FALSE;
+ break;
+ }
+ }
+
+ if (all_pending)
+ {
+ replay->pos = 0;
+ replay->read_index = 0;
+
+ hda->last_lpib = sd_readl(hda, HDA_SD_OUT_BASE, LPIB);
+ hda->last_reclaimed_period = (HDA_BDL_COUNT - 1);
+ rt_hw_dmb();
+
+ sd_writel(hda, HDA_SD_OUT_BASE, CTL,
+ sd_readl(hda, HDA_SD_OUT_BASE, CTL) | SD_CTL_DMA_START);
+
+ hda->dma_started = RT_TRUE;
+ }
+
+ return;
+ }
+
+ if (rt_data_queue_len(&replay->queue) == 0)
+ {
+ return;
+ }
+ period = intel_hda_find_free_period(hda);
+ if (period < 0)
+ {
+ return;
+ }
+ intel_hda_push_one_period(hda, (rt_uint32_t)period);
+}
+
+static void intel_hda_replay_worker(struct intel_hda *hda)
+{
+ if (!hda->playback_active || !hda->parent.replay)
+ {
+ return;
+ }
+ if (rt_interrupt_get_nest() != 0)
+ {
+ intel_hda_schedule_replay(hda);
+ return;
+ }
+
+ rt_mutex_take(&hda->replay_push_lock, RT_WAITING_FOREVER);
+
+ if (hda->dma_started)
+ {
+ intel_hda_poll_lpib_nolock(hda);
+
+ if (rt_data_queue_len(&hda->parent.replay->queue) > 0 &&
+ intel_hda_find_free_period(hda) >= 0)
+ {
+ intel_hda_replay_push_nolock(hda);
+ }
+ else if (intel_hda_find_free_period(hda) >= 0)
+ {
+ intel_hda_schedule_replay(hda);
+ }
+ }
+ else
+ {
+ intel_hda_replay_push_nolock(hda);
+ }
+
+ rt_mutex_release(&hda->replay_push_lock);
+}
+
+static void intel_hda_replay_push(struct intel_hda *hda)
+{
+ intel_hda_replay_worker(hda);
+}
+
+static void intel_hda_replay_queue_notify(struct rt_data_queue *queue, rt_uint32_t event)
+{
+ struct intel_hda *hda = intel_hda_replay_notify_dev;
+
+ RT_UNUSED(queue);
+
+ if (event != RT_DATAQUEUE_EVENT_PUSH || hda == RT_NULL ||
+ !hda->playback_active)
+ {
+ return;
+ }
+
+ intel_hda_replay_push(hda);
+}
+
+static void intel_hda_replay_thread_entry(void *parameter)
+{
+ struct intel_hda *hda = parameter;
+
+ while (!hda->replay_thread_exit)
+ {
+ (void)rt_sem_take(&hda->replay_sem,
+ rt_tick_from_millisecond(HDA_REPLAY_POLL_MS));
+
+ if (hda->replay_thread_exit)
+ {
+ break;
+ }
+ intel_hda_replay_push(hda);
+ }
+}
+
+static void intel_hda_replay_thread_stop(struct intel_hda *hda)
+{
+ if (!hda->replay_thread)
+ {
+ return;
+ }
+ hda->replay_thread_exit = RT_TRUE;
+ rt_sem_release(&hda->replay_sem);
+ rt_thread_mdelay(10);
+ rt_thread_delete(hda->replay_thread);
+ hda->replay_thread = RT_NULL;
+}
+
+static void intel_hda_reclaim_period_index(struct intel_hda *hda, rt_uint32_t period)
+{
+ if (!hda->playback_active || !hda->dma_started ||
+ hda->stream != AUDIO_STREAM_REPLAY)
+ {
+ return;
+ }
+ if (period >= HDA_BDL_COUNT)
+ {
+ return;
+ }
+ hda->last_reclaimed_period = period;
+
+ if (hda->periods[period].mp_block)
+ {
+ rt_mp_free(hda->periods[period].mp_block);
+ hda->periods[period].mp_block = RT_NULL;
+ }
+
+ hda->periods[period].pending = RT_FALSE;
+ hda->periods[period].submit_tick = 0;
+
+}
+
+/*
+ * LPIB tells which period finished; wall-clock gates reclaim so QEMU cannot
+ * run ahead of the configured sample rate. At most one reclaim per worker pass.
+ */
+static void intel_hda_poll_lpib_nolock(struct intel_hda *hda)
+{
+ rt_uint32_t lpib, last;
+ rt_uint32_t last_p, cur_p, p;
+ rt_bool_t reclaimed;
+
+ if (!hda->dma_started)
+ {
+ return;
+ }
+ lpib = sd_readl(hda, HDA_SD_OUT_BASE, LPIB);
+ last = hda->last_lpib;
+
+ if (lpib == last)
+ {
+ return;
+ }
+ last_p = last / HDA_BUFFER_SIZE;
+ cur_p = lpib / HDA_BUFFER_SIZE;
+ reclaimed = RT_FALSE;
+
+ hda->last_lpib = lpib;
+
+ if (lpib >= last)
+ {
+ for (p = last_p; p < cur_p && !reclaimed; p++)
+ {
+ if (intel_hda_period_ready_to_reclaim(hda, p))
+ {
+ intel_hda_reclaim_period_index(hda, p);
+ reclaimed = RT_TRUE;
+ }
+ }
+ }
+ else
+ {
+ for (p = last_p; p < HDA_BDL_COUNT && !reclaimed; p++)
+ {
+ if (intel_hda_period_ready_to_reclaim(hda, p))
+ {
+ intel_hda_reclaim_period_index(hda, p);
+ reclaimed = RT_TRUE;
+ }
+ }
+
+ for (p = 0; p < cur_p && !reclaimed; p++)
+ {
+ if (intel_hda_period_ready_to_reclaim(hda, p))
+ {
+ intel_hda_reclaim_period_index(hda, p);
+ reclaimed = RT_TRUE;
+ }
+ }
+ }
+}
+
+static void intel_hda_stream_irq(struct intel_hda *hda)
+{
+ rt_uint8_t sts;
+
+ RT_UNUSED(hda);
+
+ sts = sd_readb(hda, HDA_SD_OUT_BASE, STS);
+ if (sts & SD_INT_MASK)
+ {
+ sd_writeb(hda, HDA_SD_OUT_BASE, STS, sts & SD_INT_MASK);
+ }
+}
+
+static rt_err_t intel_hda_audio_start(struct rt_audio_device *audio, int stream)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ if (stream == AUDIO_STREAM_REPLAY)
+ {
+ hda->stream = AUDIO_STREAM_REPLAY;
+ hda->playback_active = RT_TRUE;
+ hda->dma_started = RT_FALSE;
+ hda->last_reclaimed_period = 0xffffffff;
+ hda->last_lpib = 0;
+
+ intel_hda_period_reset_all(hda);
+ intel_hda_replay_queue_reset(hda);
+ intel_hda_sync_output_format(hda);
+
+ /* Configure the output widget and codec */
+ intel_hda_configure_output(hda);
+
+ /* Setup stream descriptor, BDL, and DMA position */
+ intel_hda_setup_stream(hda);
+
+ rt_timer_start(&hda->poll_timer);
+
+ if (audio->replay &&
+ rt_data_queue_len(&audio->replay->queue) > 0)
+ {
+ intel_hda_replay_push(hda);
+ }
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t intel_hda_audio_stop(struct rt_audio_device *audio, int stream)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ if (stream == AUDIO_STREAM_REPLAY)
+ {
+ /* Stop poll timer */
+ rt_timer_stop(&hda->poll_timer);
+
+ hda->playback_active = RT_FALSE;
+ hda->dma_started = RT_FALSE;
+
+ intel_hda_period_reset_all(hda);
+ intel_hda_replay_queue_reset(hda);
+
+ /* Stop DMA - use 32-bit access to preserve Stream Tag */
+ sd_writel(hda, HDA_SD_OUT_BASE, CTL,
+ sd_readl(hda, HDA_SD_OUT_BASE, CTL) & ~SD_CTL_DMA_START);
+
+ /* Disable SDO0 stream interrupt in INTCTL */
+ intel_hda_writel(hda, INTCTL,
+ intel_hda_readl(hda, INTCTL) & ~HDA_SDO0_STREAM_INT);
+
+ /* Reset stream */
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, SD_CTL_STREAM_RESET);
+ while (!(sd_readb(hda, HDA_SD_OUT_BASE, CTL) & SD_CTL_STREAM_RESET))
+ {
+ rt_hw_cpu_relax();
+ }
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, 0);
+
+ /* Clear status */
+ sd_writeb(hda, HDA_SD_OUT_BASE, STS, SD_INT_MASK);
+
+ hda->stream = -1;
+ hda->last_reclaimed_period = 0xffffffff;
+ hda->last_lpib = 0;
+ }
+
+ return RT_EOK;
+}
+
+static rt_ssize_t intel_hda_audio_transmit(struct rt_audio_device *audio,
+ const void *write_buf, void *read_buf, rt_size_t size)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+ rt_uint32_t offset;
+
+ RT_UNUSED(read_buf);
+
+ offset = (rt_uint32_t)((rt_ubase_t)write_buf - (rt_ubase_t)hda->audio_buffer);
+ if (offset >= hda->audio_buffer_size)
+ {
+ LOG_E("transmit: invalid offset %u >= %u", offset, hda->audio_buffer_size);
+ return 0;
+ }
+
+ rt_hw_dmb();
+
+ return size;
+}
+
+static void intel_hda_audio_buffer_info(struct rt_audio_device *audio,
+ struct rt_audio_buf_info *info)
+{
+ struct intel_hda *hda = raw_to_intel_hda(audio);
+
+ info->buffer = (rt_uint8_t *)hda->audio_buffer;
+ info->total_size = hda->audio_buffer_size;
+ info->block_size = HDA_BUFFER_SIZE;
+ info->block_count = HDA_BDL_COUNT;
+
+}
+
+const static struct rt_audio_ops intel_hda_audio_ops =
+{
+ .getcaps = intel_hda_audio_getcaps,
+ .configure = intel_hda_audio_configure,
+ .init = intel_hda_audio_init,
+ .start = intel_hda_audio_start,
+ .stop = intel_hda_audio_stop,
+ .transmit = intel_hda_audio_transmit,
+ .buffer_info = intel_hda_audio_buffer_info,
+};
+
+static void intel_hda_poll_timer(void *parameter)
+{
+ struct intel_hda *hda = parameter;
+
+ if (hda->stream != AUDIO_STREAM_REPLAY || !hda->playback_active)
+ {
+ return;
+ }
+ intel_hda_schedule_replay(hda);
+}
+
+/*
+ * Interrupt handler - following Linux driver pattern
+ */
+static void intel_hda_isr(int irqno, void *param)
+{
+ struct intel_hda *hda = param;
+ rt_uint32_t isr;
+ rt_uint8_t sts;
+ rt_uint8_t rirbsts;
+
+ RT_UNUSED(irqno);
+
+ isr = intel_hda_readl(hda, INTSTS);
+
+ /* Handle controller interrupt (RIRB, state change) */
+ if (isr & ICH6_INT_CTRL_EN)
+ {
+ rirbsts = intel_hda_readb(hda, RIRBSTS);
+ if (rirbsts & (ICH6_RBSTS_IRQ | ICH6_RBSTS_OVERRUN))
+ {
+ intel_hda_writeb(hda, RIRBSTS, rirbsts);
+ }
+
+ /* Clear STATESTS if any */
+ rt_uint16_t statests = intel_hda_readw(hda, STATESTS);
+ if (statests)
+ {
+ intel_hda_writew(hda, STATESTS, statests);
+ }
+ }
+
+ /* SDO0 is stream #4 in INTSTS (see QEMU intel_hda_update_int_sts) */
+ if (isr & HDA_SDO0_STREAM_INT)
+ {
+ sts = sd_readb(hda, HDA_SD_OUT_BASE, STS);
+
+ if (hda->stream == AUDIO_STREAM_REPLAY && hda->playback_active)
+ {
+ if (sts & SD_INT_COMPLETE)
+ {
+ intel_hda_stream_irq(hda);
+ }
+ }
+
+ if (sts & (SD_INT_DESC_ERR | SD_INT_FIFO_ERR))
+ {
+ LOG_E("Stream error: sts=0x%02x", sts);
+ }
+ }
+
+ if (isr)
+ {
+ intel_hda_writel(hda, INTSTS, isr);
+ }
+}
+
+/*
+ * Controller reset
+ */
+static void intel_hda_reset(struct intel_hda *hda)
+{
+ int timeout;
+
+ /* Clear CORB/RIRB RUN bits before reset */
+ intel_hda_writeb(hda, CORBCTL, 0);
+ intel_hda_writeb(hda, RIRBCTL, 0);
+
+ timeout = 10000;
+ while ((intel_hda_readb(hda, CORBCTL) & ICH6_CORBCTL_RUN) ||
+ (intel_hda_readb(hda, RIRBCTL) & ICH6_RBCTL_DMA_EN))
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+
+ /* Reset the CRST bit and wait until hardware is in reset */
+ intel_hda_writel(hda, GCTL, 0);
+
+ timeout = 10000;
+ while (intel_hda_readl(hda, GCTL) & ICH6_GCTL_RESET)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+
+ rt_hw_dmb();
+
+ /* Take the hardware out of reset */
+ intel_hda_writel(hda, GCTL, ICH6_GCTL_RESET);
+
+ timeout = 10000;
+ while ((intel_hda_readl(hda, GCTL) & ICH6_GCTL_RESET) == 0)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+
+ /* Second reset cycle for reliable codec discovery */
+ intel_hda_writew(hda, WAKEEN, 0);
+ intel_hda_writew(hda, STATESTS, intel_hda_readw(hda, STATESTS));
+
+ intel_hda_writel(hda, GCTL, 0);
+ timeout = 10000;
+ while (intel_hda_readl(hda, GCTL) & ICH6_GCTL_RESET)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+ rt_hw_dmb();
+ intel_hda_writel(hda, GCTL, ICH6_GCTL_RESET);
+ timeout = 10000;
+ while ((intel_hda_readl(hda, GCTL) & ICH6_GCTL_RESET) == 0)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+
+ intel_hda_writew(hda, WAKEEN, 0x7fff);
+ intel_hda_writel(hda, INTCTL, 0);
+
+ intel_hda_setup_corb(hda);
+ intel_hda_setup_rirb(hda);
+
+ /* Wait 25 frames (521 us) before enumerating codecs */
+ rt_hw_us_delay(521);
+
+ intel_hda_enumerate_codecs(hda);
+}
+
+/*
+ * PCI driver: probe and remove
+ */
+static rt_err_t intel_hda_probe(struct rt_pci_device *pdev)
+{
+ rt_err_t err;
+ const char *audio_name;
+ char dev_name[RT_NAME_MAX];
+ struct intel_hda *hda;
+ int timeout;
+
+ hda = rt_calloc(1, sizeof(*hda));
+ if (!hda)
+ {
+ return -RT_ENOMEM;
+ }
+ hda->pdev = pdev;
+ hda->stream = -1;
+ hda->playback_active = RT_FALSE;
+ hda->last_reclaimed_period = 0xffffffff;
+
+ hda->ich6_reg_base = rt_pci_iomap(pdev, ICH6_REG_BAR);
+ if (!hda->ich6_reg_base)
+ {
+ err = -RT_EIO;
+ goto _free_hda;
+ }
+
+ hda->ring_buffer = rt_dma_alloc_coherent(&pdev->parent, HDA_RING_BUF_SIZE, &hda->ring_buffer_dma);
+ if (!hda->ring_buffer)
+ {
+ err = -RT_ENOMEM;
+ goto _free_iomap;
+ }
+ rt_memset(hda->ring_buffer, 0, HDA_RING_BUF_SIZE);
+
+ hda->audio_buffer_size = HDA_BDL_COUNT * HDA_BUFFER_SIZE;
+ hda->audio_buffer = rt_dma_alloc_coherent(&pdev->parent, hda->audio_buffer_size, &hda->audio_buffer_dma);
+ if (!hda->audio_buffer)
+ {
+ err = -RT_ENOMEM;
+ goto _free_ring;
+ }
+ rt_memset(hda->audio_buffer, 0, hda->audio_buffer_size);
+
+ rt_pci_set_master(pdev);
+ intel_hda_reset(hda);
+
+ if (!hda->output.node_id)
+ {
+ LOG_W("No audio output widget found");
+ err = -RT_ERROR;
+ goto _free_ring;
+ }
+
+ rt_dm_dev_set_name_auto(&hda->parent.parent, "sound");
+ audio_name = rt_dm_dev_get_name(&hda->parent.parent);
+ hda->parent.ops = (struct rt_audio_ops *)&intel_hda_audio_ops;
+
+ rt_timer_init(&hda->poll_timer, "hda_poll",
+ intel_hda_poll_timer, hda,
+ rt_tick_from_millisecond(HDA_REPLAY_POLL_MS),
+ RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
+
+ if ((err = rt_audio_register(&hda->parent, audio_name, RT_DEVICE_FLAG_RDWR, hda)) != RT_EOK)
+ {
+ goto _free_audio_buf;
+ }
+ if (hda->parent.replay)
+ {
+ intel_hda_replay_notify_dev = hda;
+ hda->parent.replay->queue.evt_notify = intel_hda_replay_queue_notify;
+ }
+
+ rt_mutex_init(&hda->replay_push_lock, "hda_plk", RT_IPC_FLAG_PRIO);
+ rt_sem_init(&hda->replay_sem, "hda_rpl", 0, RT_IPC_FLAG_FIFO);
+ hda->replay_thread = rt_thread_create("hda_rpl",
+ intel_hda_replay_thread_entry, hda, 8192, 12, 10);
+ if (!hda->replay_thread)
+ {
+ err = -RT_ENOMEM;
+ goto _free_audio_buf;
+ }
+ rt_thread_startup(hda->replay_thread);
+
+ pdev->parent.user_data = hda;
+
+ rt_snprintf(dev_name, sizeof(dev_name), "%s-hda", audio_name);
+ rt_hw_interrupt_install(pdev->irq, intel_hda_isr, hda, dev_name);
+
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, SD_CTL_STREAM_RESET);
+ timeout = 10000;
+ while (!(sd_readb(hda, HDA_SD_OUT_BASE, CTL) & SD_CTL_STREAM_RESET))
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+ rt_hw_us_delay(100);
+ sd_writeb(hda, HDA_SD_OUT_BASE, CTL, 0);
+ timeout = 10000;
+ while (sd_readb(hda, HDA_SD_OUT_BASE, CTL) & SD_CTL_STREAM_RESET)
+ {
+ if (--timeout == 0)
+ {
+ break;
+ }
+ rt_hw_cpu_relax();
+ }
+
+ sd_writeb(hda, HDA_SD_OUT_BASE, STS, 0xff);
+ intel_hda_writeb(hda, RIRBSTS, ICH6_RBSTS_IRQ | ICH6_RBSTS_OVERRUN);
+ intel_hda_writel(hda, INTSTS, intel_hda_readl(hda, INTSTS));
+ intel_hda_writel(hda, INTCTL, ICH6_INT_GLOBAL_EN | ICH6_INT_CTRL_EN);
+ rt_pci_irq_unmask(pdev);
+
+ LOG_I("Intel HD Audio v%d.%d codec=%d dac=%d pin=%d",
+ intel_hda_readb(hda, VMAJ), intel_hda_readb(hda, VMIN),
+ hda->output.codec, hda->output.node_id, hda->output.pin_nid);
+
+ return RT_EOK;
+
+_free_audio_buf:
+ intel_hda_replay_thread_stop(hda);
+ rt_timer_detach(&hda->poll_timer);
+ rt_dma_free_coherent(&pdev->parent, hda->audio_buffer_size, hda->audio_buffer, hda->audio_buffer_dma);
+_free_ring:
+ rt_dma_free_coherent(&pdev->parent, HDA_RING_BUF_SIZE, hda->ring_buffer, hda->ring_buffer_dma);
+_free_iomap:
+ rt_iounmap(hda->ich6_reg_base);
+_free_hda:
+ rt_free(hda);
+
+ return err;
+}
+
+static rt_err_t intel_hda_remove(struct rt_pci_device *pdev)
+{
+ struct intel_hda *hda = pdev->parent.user_data;
+
+ if (intel_hda_replay_notify_dev == hda)
+ {
+ intel_hda_replay_notify_dev = RT_NULL;
+ }
+ intel_hda_replay_thread_stop(hda);
+ rt_timer_stop(&hda->poll_timer);
+ rt_timer_detach(&hda->poll_timer);
+ rt_pci_irq_mask(pdev);
+ rt_pic_detach_irq(pdev->irq, hda);
+
+ rt_device_unregister(&hda->parent.parent);
+
+ if (hda->audio_buffer)
+ {
+ rt_dma_free_coherent(&pdev->parent, hda->audio_buffer_size, hda->audio_buffer, hda->audio_buffer_dma);
+ }
+ if (hda->ring_buffer)
+ {
+ rt_dma_free_coherent(&pdev->parent, HDA_RING_BUF_SIZE, hda->ring_buffer, hda->ring_buffer_dma);
+ }
+ rt_iounmap(hda->ich6_reg_base);
+ rt_free(hda);
+
+ return RT_EOK;
+}
+
+static const struct rt_pci_device_id intel_hda_pci_ids[] =
+{
+ { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x2668), .data = "6" },
+ { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x27d8), .data = "9" },
+ { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x293e), .data = "9" },
+ { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x293f), .data = "9" },
+ { /* Sentinel */ }
+};
+
+static struct rt_pci_driver intel_hda_driver =
+{
+ .name = "intel-hda",
+
+ .ids = intel_hda_pci_ids,
+ .probe = intel_hda_probe,
+ .remove = intel_hda_remove,
+};
+RT_PCI_DRIVER_EXPORT(intel_hda_driver);