drivers/audio: add C-Media CMI8738/CMI8338 audio driver (#550)
Build system / Build (es_ES) (push) Successful in 2m46s
Build system / Build (ru_RU) (push) Successful in 2m49s
Build system / Build (en_US) (push) Successful in 2m52s
Build system / Publish Images (push) Successful in 2m31s

Probably the most popular sound CARD in the world.
Driver is tested on real sound cards on my side and by Jeffrey hidnplayr.

Reviewed-on: #550
Reviewed-by: Ivan B <1+dunkaist@noreply.localhost>
Reviewed-by: Burer <burer@kolibrios.org>
Co-authored-by: leency <lipatov.kiril@gmail.com>
Co-committed-by: leency <lipatov.kiril@gmail.com>
This commit was merged in pull request #550.
This commit is contained in:
2026-07-05 16:58:36 +00:00
committed by Burer
parent 1f8cd12dfc
commit adc9753e1f
3 changed files with 782 additions and 0 deletions
+1
View File
@@ -589,6 +589,7 @@ tup.append_table(img_files, {
{"DRIVERS/SOUND.SYS", VAR_DRVS .. "/audio/sound.sys"},
{"DRIVERS/INFINITY.SYS", VAR_DRVS .. "/audio/infinity/infinity.sys"},
{"DRIVERS/AC97.SYS", VAR_DRVS .. "/audio/ac97.sys"},
{"DRIVERS/CMI8738.SYS", VAR_DRVS .. "/audio/cmi8738.sys"},
{"DRIVERS/EMU10K1X.SYS", VAR_DRVS .. "/audio/emu10k1x.sys"},
{"DRIVERS/FM801.SYS", VAR_DRVS .. "/audio/fm801.sys"},
{"DRIVERS/VT823X.SYS", VAR_DRVS .. "/audio/vt823x.sys"},
+769
View File
@@ -0,0 +1,769 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2026. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; C-Media CMI8738 / CMI8338 PCI audio driver.
; Playback only: 16-bit / 48000 Hz / stereo.
; Native linear-DMA engine (DMA channel 0) + built-in SB16-compatible mixer.
;
; The driver framework (SOUND service contract, ping-pong buffer, PCI/IRQ
; bring-up, port-I/O helpers, StallExec) is derived from the KolibriOS
; fm801 driver (C) KolibriOS team.
;
; The CMI8738 register map and the playback / mixer programming sequence were
; referenced and cross-checked against:
; - Linux ALSA "snd-cmipci" (sound/pci/cmipci.c), GPL, (C) Takashi Iwai et al.
;
; Developed with AI assistance (Claude); Tested on real hardware by Leency.
format PE DLL native 0.05
entry START
DEBUG = 1
API_VERSION equ 0x01000100
;irq 0,13 unavailable
; FEDCBA9876543210
VALID_IRQ equ 1101111111111110b
CPU_FREQ equ 2000d
VID_CMEDIA equ 0x13F6
CTRL_CMI8338A equ 0x0100
CTRL_CMI8338B equ 0x0101
CTRL_CMI8738 equ 0x0111
CTRL_CMI8738B equ 0x0112
; --- I/O register map (offsets from BAR0) ---
CM_FUNCTRL0 equ 0x00 ; channel enable / reset / direction
CM_FUNCTRL1 equ 0x04 ; sample rate fields, bus-master enable
CM_CHFORMAT equ 0x08 ; channel data format
CM_INT_HLDCLR equ 0x0C ; interrupt enable / hold-clear
CM_INT_STATUS equ 0x10 ; interrupt status
CM_LEGACY_CTRL equ 0x14
CM_MISC_CTRL equ 0x18
CM_SB16_DATA equ 0x22 ; SB16 mixer data port
CM_SB16_ADDR equ 0x23 ; SB16 mixer index port
CM_MIXER1 equ 0x24 ; C-Media mixer1 (wave/FM mute, speaker mode)
CM_WSMUTE equ 0x40 ; mixer1 bit: mute wave/PCM
CM_CH0_FRAME1 equ 0x80 ; channel 0 DMA base address
CM_CH0_FRAME2 equ 0x84 ; channel 0 DMA counts (dwords-1)
; FUNCTRL0 bits
CM_CHADC0 equ 0x00000001 ; 0 = playback, 1 = record (channel 0)
CM_PAUSE0 equ 0x00000004
CM_CHEN0 equ 0x00010000 ; channel 0 enable
CM_RST_CH0 equ 0x00040000 ; channel 0 DMA reset
CM_RST_CH1 equ 0x00080000
; FUNCTRL1 bits
CM_BREQ equ 0x00000010 ; bus-master request enable
; FUNCTRL1 holds two sample-rate fields. Sources disagree on whether the
; [15:13] / [12:10] fields are named per channel (ch0/ch1) or per converter
; (DAC/ADC), so we program BOTH to 48 kHz; the unused channel does not care.
CM_DSFC_MASK equ 0x0000E000 ; rate field [15:13]
CM_DSFC_SHIFT equ 13
CM_ASFC_MASK equ 0x00001C00 ; rate field [12:10]
CM_ASFC_SHIFT equ 10
; CHFORMAT bits (format value: bit0 = stereo, bit1 = 16-bit)
; Channel 0 format is bits [1:0] of reg 0x08 (verified vs Linux cmipci.c,
; 86Box, OSS/BSD). Channel 1 is bits [3:2]. (Earlier [13:12] was wrong and
; left the format at 8-bit mono -> silence.)
CM_CH0FMT_MASK equ 0x00000003 ; channel 0 format field [1:0]
CM_CH0FMT_SHIFT equ 0
CM_FMT_16ST equ 3 ; 16-bit stereo (bit1=16-bit, bit0=stereo)
; INT_HLDCLR bits
CM_CH0_INT_EN equ 0x00010000
CM_CH1_INT_EN equ 0x00020000
; INT_STATUS bits
CM_INTR equ 0x80000000 ; any interrupt pending (read only)
CM_CHINT0 equ 0x00000001 ; channel 0 interrupt
; MISC_CTRL bits
CM_PWD equ 0x80000000 ; power down
CM_RESET equ 0x40000000
; SB16 mixer register indices
SB16_RESET equ 0x00
SB16_MASTER_L equ 0x30
SB16_MASTER_R equ 0x31
SB16_VOICE_L equ 0x32
SB16_VOICE_R equ 0x33
; sample-rate field value for 48000 Hz
; hw table: 5512,11025,22050,44100,8000,16000,32000,48000 -> index 7
CM_RATE_48000 equ 7
; service ioctl codes
SRV_GETVERSION equ 0
DEV_PLAY equ 1
DEV_STOP equ 2
DEV_CALLBACK equ 3
DEV_SET_BUFF equ 4
DEV_NOTIFY equ 5
DEV_SET_MASTERVOL equ 6
DEV_GET_MASTERVOL equ 7
DEV_GET_INFO equ 8
; primary buffer geometry: two halves of DMA_HALF bytes each
BUFFER_SIZE equ 0x10000 ; allocation size
DMA_TOTAL equ 0x8000 ; bytes used by the DMA ring (two halves)
DMA_HALF equ 0x4000 ; bytes per half = one callback chunk
struc CMI_CTRL
{ .bus dd ?
.devfn dd ?
.vendor dd ?
.dev_id dd ?
.pci_cmd dd ?
.io_base dd ?
.int_line dd ?
.buffer dd ? ; linear address of primary buffer
.buffer_pg dd ? ; physical address of primary buffer
.user_callback dd ?
.master_vol dd ? ; cached master volume (-10000..0)
}
section '.flat' code readable writable executable
include '../proc32.inc'
include '../struct.inc'
include '../macros.inc'
include '../peimport.inc'
proc START c uses ebx esi edi, state:dword, cmdline:dword
cmp [state], 1
jne .stop
if DEBUG
mov esi, msgInit
invoke SysMsgBoardStr
end if
call detect_controller
test eax, eax
jz .fail
call init_controller
test eax, eax
jz .fail
call reset_controller
call init_mixer
call create_primary_buff
test eax, eax
jz .fail
mov eax, VALID_IRQ
mov ebx, [ctrl.int_line]
bt eax, ebx
jnc .fail_irq
invoke AttachIntHandler, ebx, cmi_irq, dword 0
if DEBUG
mov esi, msgDone
invoke SysMsgBoardStr
end if
invoke RegService, sz_sound_srv, service_proc
ret
.fail_irq:
if DEBUG
mov esi, msgInvIRQ
invoke SysMsgBoardStr
end if
xor eax, eax
ret
.fail:
if DEBUG
mov esi, msgFail
invoke SysMsgBoardStr
end if
xor eax, eax
ret
.stop:
call stop
xor eax, eax
ret
endp
handle equ IOCTL.handle
io_code equ IOCTL.io_code
input equ IOCTL.input
inp_size equ IOCTL.inp_size
output equ IOCTL.output
out_size equ IOCTL.out_size
align 4
proc service_proc stdcall, ioctl:dword
mov edi, [ioctl]
mov eax, [edi+io_code]
cmp eax, SRV_GETVERSION
jne @F
mov eax, [edi+output]
cmp [edi+out_size], 4
jne .fail
mov [eax], dword API_VERSION
xor eax, eax
ret
@@:
cmp eax, DEV_PLAY
jne @F
if DEBUG
mov esi, msgPlay
invoke SysMsgBoardStr
end if
call play
ret
@@:
cmp eax, DEV_STOP
jne @F
if DEBUG
mov esi, msgStop
invoke SysMsgBoardStr
end if
call stop
ret
@@:
cmp eax, DEV_CALLBACK
jne @F
mov ebx, [edi+input]
stdcall set_callback, [ebx]
ret
@@:
cmp eax, DEV_SET_MASTERVOL
jne @F
mov eax, [edi+input]
mov eax, [eax]
call set_master_vol ; eax = vol
ret
@@:
cmp eax, DEV_GET_MASTERVOL
jne @F
mov ebx, [edi+output]
stdcall get_master_vol, ebx
ret
@@:
.fail:
or eax, -1
ret
endp
restore handle
restore io_code
restore input
restore inp_size
restore output
restore out_size
align 4
proc detect_controller
push ebx
invoke GetPCIList
mov ebx, eax ; list head (sentinel)
.next_dev:
mov eax, [eax+PCIDEV.fd]
cmp eax, ebx
je .err
mov ecx, [eax+PCIDEV.vendor_device_id]
cmp cx, VID_CMEDIA
jne .next_dev
shr ecx, 16 ; device id
cmp cx, CTRL_CMI8738
je .found
cmp cx, CTRL_CMI8738B
je .found
cmp cx, CTRL_CMI8338A
je .found
cmp cx, CTRL_CMI8338B
je .found
jmp .next_dev
.err:
xor eax, eax
pop ebx
ret
.found:
movzx edx, [eax+PCIDEV.bus]
mov [ctrl.bus], edx
movzx edx, [eax+PCIDEV.devfn]
mov [ctrl.devfn], edx
mov edx, [eax+PCIDEV.vendor_device_id]
movzx ecx, dx
mov [ctrl.vendor], ecx
shr edx, 16
mov [ctrl.dev_id], edx
xor eax, eax
inc eax
pop ebx
ret
endp
align 4
proc init_controller
; I/O base from BAR0
invoke PciRead32, [ctrl.bus], [ctrl.devfn], 0x10
and eax, 0xFFFFFFFC
mov [ctrl.io_base], eax
; interrupt line
invoke PciRead32, [ctrl.bus], [ctrl.devfn], 0x3C
and eax, 0xFF
mov [ctrl.int_line], eax
; enable I/O space + bus mastering
invoke PciRead32, [ctrl.bus], [ctrl.devfn], 4
mov [ctrl.pci_cmd], eax
or al, 101b ; bit0 IO, bit2 busmaster
invoke PciWrite16, [ctrl.bus], [ctrl.devfn], 4, eax
xor eax, eax
inc eax
ret
endp
align 4
proc reset_controller
if DEBUG
mov esi, msgInitCtrl
invoke SysMsgBoardStr
end if
; stop all channels
xor eax, eax
mov edx, CM_FUNCTRL0
call io_w32
; pulse reset on both DMA channels
mov eax, CM_RST_CH0 or CM_RST_CH1
mov edx, CM_FUNCTRL0
call io_w32
mov eax, 10
call StallExec
xor eax, eax
mov edx, CM_FUNCTRL0
call io_w32
; FUNCTRL1: clear legacy/SPDIF, keep bus-master request enabled
mov eax, CM_BREQ
mov edx, CM_FUNCTRL1
call io_w32
; CHFORMAT: cleared (set per playback)
xor eax, eax
mov edx, CM_CHFORMAT
call io_w32
; power up the analog section (clear power-down and reset bits)
mov edx, CM_MISC_CTRL
call io_r32
and eax, not (CM_PWD or CM_RESET)
mov edx, CM_MISC_CTRL
call io_w32
; disable every interrupt source for now
xor eax, eax
mov edx, CM_INT_HLDCLR
call io_w32
; clear legacy control (SPDIF loopback, legacy emulation, ...)
xor eax, eax
mov edx, CM_LEGACY_CTRL
call io_w32
ret
endp
align 4
proc init_mixer
if DEBUG
mov esi, msgInitMix
invoke SysMsgBoardStr
end if
; reset the SB16 mixer
stdcall mixer_write, SB16_RESET, 0
mov eax, 10
call StallExec
; voice (PCM/DAC) volume to maximum
stdcall mixer_write, SB16_VOICE_L, 0xF8
stdcall mixer_write, SB16_VOICE_R, 0xF8
; master volume to maximum (0 dB attenuation)
xor eax, eax
call set_master_vol
; unmute the C-Media wave/FM path (clear WSMUTE/FMMUTE in mixer1 0x24)
xor eax, eax
mov edx, CM_MIXER1
call io_w8
ret
endp
align 4
proc create_primary_buff
invoke KernelAlloc, BUFFER_SIZE
mov [ctrl.buffer], eax
test eax, eax
jz .fail
mov edi, eax
mov ecx, BUFFER_SIZE/4
xor eax, eax
cld
rep stosd
mov eax, [ctrl.buffer]
invoke GetPgAddr
mov [ctrl.buffer_pg], eax
xor eax, eax
inc eax
ret
.fail:
xor eax, eax
ret
endp
; Pre-load both buffer halves before the DMA engine starts.
align 4
proc prime_buffer
cmp [ctrl.user_callback], 0
je .exit
mov esi, [ctrl.buffer]
stdcall [ctrl.user_callback], esi ; half 0
mov esi, [ctrl.buffer]
add esi, DMA_HALF
stdcall [ctrl.user_callback], esi ; half 1
.exit:
ret
endp
; Refill the half that the hardware has just finished playing.
align 4
proc fill_buffer
cmp [ctrl.user_callback], 0
je .exit
mov esi, [ctrl.buffer]
test [int_flip_flop], 1
jz @f
add esi, DMA_HALF
@@:
stdcall [ctrl.user_callback], esi
inc [int_flip_flop]
.exit:
ret
endp
align 4
proc cmi_irq
mov edx, CM_INT_STATUS
call io_r32
test eax, CM_INTR
jz .not_ours
mov [irq_status], eax
; acknowledge: drop channel interrupt enables, then restore them
mov edx, CM_INT_HLDCLR
call io_r32
and eax, not (CM_CH0_INT_EN or CM_CH1_INT_EN)
mov edx, CM_INT_HLDCLR
call io_w32
test [irq_status], CM_CHINT0
jz @f
call fill_buffer
@@:
; do not re-arm the channel interrupt after DEV_STOP has disabled it
mov edx, CM_FUNCTRL0
call io_r32
test eax, CM_CHEN0
jz .no_rearm
mov edx, CM_INT_HLDCLR
call io_r32
or eax, CM_CH0_INT_EN
mov edx, CM_INT_HLDCLR
call io_w32
.no_rearm:
mov eax, 1 ; interrupt handled
ret
.not_ours:
xor eax, eax
ret
endp
align 4
play:
; prime both halves of the ring buffer
xor eax, eax
mov [int_flip_flop], eax
call prime_buffer
; reset channel 0 first (canonical order: reset -> program -> enable)
mov edx, CM_FUNCTRL0
call io_r32
or eax, CM_RST_CH0
mov edx, CM_FUNCTRL0
call io_w32
mov edx, CM_FUNCTRL0
call io_r32
and eax, not CM_RST_CH0
mov edx, CM_FUNCTRL0
call io_w32
; data format: channel 0 = 16-bit stereo
mov edx, CM_CHFORMAT
call io_r32
and eax, not CM_CH0FMT_MASK
or eax, CM_FMT_16ST shl CM_CH0FMT_SHIFT
mov edx, CM_CHFORMAT
call io_w32
; sample rate: both rate fields = 48000 Hz, keep bus-master request
mov edx, CM_FUNCTRL1
call io_r32
and eax, not (CM_DSFC_MASK or CM_ASFC_MASK)
or eax, (CM_RATE_48000 shl CM_DSFC_SHIFT) or (CM_RATE_48000 shl CM_ASFC_SHIFT) or CM_BREQ
mov edx, CM_FUNCTRL1
call io_w32
; DMA base address
mov eax, [ctrl.buffer_pg]
mov edx, CM_CH0_FRAME1
call io_w32
; DMA counts (in dwords - 1): total in low word, period in high word
mov eax, (DMA_TOTAL/4)-1
mov edx, CM_CH0_FRAME2
call io_w16
mov eax, (DMA_HALF/4)-1
mov edx, CM_CH0_FRAME2+2
call io_w16
; direction = playback, not paused
mov edx, CM_FUNCTRL0
call io_r32
and eax, not (CM_CHADC0 or CM_PAUSE0)
mov edx, CM_FUNCTRL0
call io_w32
; enable channel 0 interrupt
mov edx, CM_INT_HLDCLR
call io_r32
or eax, CM_CH0_INT_EN
mov edx, CM_INT_HLDCLR
call io_w32
; enable channel 0
mov edx, CM_FUNCTRL0
call io_r32
or eax, CM_CHEN0
mov edx, CM_FUNCTRL0
call io_w32
xor eax, eax
ret
align 4
stop:
; disable channel 0
mov edx, CM_FUNCTRL0
call io_r32
and eax, not CM_CHEN0
mov edx, CM_FUNCTRL0
call io_w32
; disable channel 0 interrupt
mov edx, CM_INT_HLDCLR
call io_r32
and eax, not CM_CH0_INT_EN
mov edx, CM_INT_HLDCLR
call io_w32
; reset channel 0 DMA
mov edx, CM_FUNCTRL0
call io_r32
or eax, CM_RST_CH0
mov edx, CM_FUNCTRL0
call io_w32
mov eax, 10
call StallExec
mov edx, CM_FUNCTRL0
call io_r32
and eax, not CM_RST_CH0
mov edx, CM_FUNCTRL0
call io_w32
xor eax, eax
ret
align 4
proc set_callback stdcall, handler:dword
mov eax, [handler]
mov [ctrl.user_callback], eax
ret
endp
; eax = volume in hundredths of dB, 0 = loudest.
; The KolibriOS volume applet (programs/media/volume) drives master volume over
; the range 0 (loudest) .. -VOL_MIN (its lowest step), and sends the SAME -VOL_MIN
; for both "volume 0" and "mute" -- there is no separate mute ioctl. So we map
; that range onto the SB16 5-bit master register and gate the wave with WSMUTE at
; the bottom, making mute / lowest step true silence instead of merely quiet.
VOL_MIN equ 4790 ; applet range: 10 steps * 479
align 4
set_master_vol:
cmp eax, 0
jle @f
xor eax, eax
@@:
cmp eax, -VOL_MIN
jge @f
mov eax, -VOL_MIN
@@:
mov [ctrl.master_vol], eax
; level = 31 - (-vol)*31/VOL_MIN (5-bit attenuation)
neg eax
imul eax, 31
xor edx, edx
mov ebx, VOL_MIN
div ebx
mov ebx, 31
sub ebx, eax ; ebx = level 0..31
; True mute at the lowest level: the SB16 master register bottoms out at
; a still-audible attenuation, so gate the native PCM with WSMUTE (0x24).
xor eax, eax
test ebx, ebx
jnz @f
mov al, CM_WSMUTE ; level 0 -> mute wave/PCM entirely
@@:
mov edx, CM_MIXER1
call io_w8
shl ebx, 3 ; level into bits [7:3]
stdcall mixer_write, SB16_MASTER_L, ebx
stdcall mixer_write, SB16_MASTER_R, ebx
xor eax, eax
ret
align 4
proc get_master_vol stdcall, pvol:dword
mov eax, [ctrl.master_vol]
mov ebx, [pvol]
mov [ebx], eax
xor eax, eax
ret
endp
align 4
proc mixer_write stdcall uses eax edx, idx:dword, val:dword
mov eax, [idx]
mov edx, CM_SB16_ADDR
call io_w8
mov eax, [val]
mov edx, CM_SB16_DATA
call io_w8
ret
endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; I/O port helpers
; edx = register offset
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align 4
proc io_r8
add edx, [ctrl.io_base]
in al, dx
ret
endp
align 4
proc io_r16
add edx, [ctrl.io_base]
in ax, dx
ret
endp
align 4
proc io_r32
add edx, [ctrl.io_base]
in eax, dx
ret
endp
align 4
proc io_w8
add edx, [ctrl.io_base]
out dx, al
ret
endp
align 4
proc io_w16
add edx, [ctrl.io_base]
out dx, ax
ret
endp
align 4
proc io_w32
add edx, [ctrl.io_base]
out dx, eax
ret
endp
align 4
proc StallExec
push ecx
push edx
push ebx
push eax
mov ecx, CPU_FREQ
mul ecx
mov ebx, eax ;low
mov ecx, edx ;high
rdtsc
add ebx, eax
adc ecx, edx
@@:
rdtsc
sub eax, ebx
sbb edx, ecx
js @B
pop eax
pop ebx
pop edx
pop ecx
ret
endp
align 4
sz_sound_srv db 'SOUND', 0
msgInit db 'CMI8738: detecting hardware...',13,10,0
msgFail db 'CMI8738: device not found',13,10,0
msgInvIRQ db 'CMI8738: IRQ line not assigned or invalid',13,10,0
msgInitCtrl db 'CMI8738: init controller',13,10,0
msgInitMix db 'CMI8738: init mixer',13,10,0
msgPlay db 'CMI8738: start play',13,10,0
msgStop db 'CMI8738: stop play',13,10,0
msgDone db 'CMI8738: ready',13,10,0
align 4
data fixups
end data
ctrl CMI_CTRL
int_flip_flop rd 1
irq_status rd 1
+12
View File
@@ -29,6 +29,7 @@ VID_ULI = 0x10B9
VID_TERA = 0x6549
VID_RDC = 0x17F3
VID_VMWARE = 0x15AD
VID_CMEDIA = 0x13F6
CTRL_ICH = 0x2415
CTRL_ICH0 = 0x2425
@@ -57,6 +58,11 @@ CTRL_FM801 = 0x0801
CTRL_CT0200 = 0x0006 ; Dell OEM version (EMU10K1X)
CTRL_CMI8338A = 0x0100
CTRL_CMI8338B = 0x0101
CTRL_CMI8738 = 0x0111
CTRL_CMI8738B = 0x0112
CTRL_INTEL_SCH2 = 0x080a
CTRL_INTEL_HPT = 0x0c0c
CTRL_INTEL_0F04 = 0x0F04
@@ -309,6 +315,11 @@ devices dd (CTRL_ICH shl 16)+VID_INTEL, intelac97
dd (0x5880 shl 16)+0x1274, ensoniq
dd (CTRL_CT0200 shl 16)+VID_CREATIVE, emu10k1x
dd (CTRL_CMI8338A shl 16)+VID_CMEDIA, cmi8738
dd (CTRL_CMI8338B shl 16)+VID_CMEDIA, cmi8738
dd (CTRL_CMI8738 shl 16)+VID_CMEDIA, cmi8738
dd (CTRL_CMI8738B shl 16)+VID_CMEDIA, cmi8738
; Intel HDA
dd (CTRL_INTEL_SCH2 shl 16)+VID_INTEL, intelhda
dd (CTRL_INTEL_HPT shl 16)+VID_INTEL, intelhda
@@ -427,6 +438,7 @@ sis db 'SIS', 0
fm801 db 'FM801', 0
ensoniq db 'ENSONIQ', 0
emu10k1x db 'EMU10K1X', 0
cmi8738 db 'CMI8738', 0
intelhda db 'HDAUDIO', 0
sb16 db 'SB16', 0