#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vac.c,v 1.2 2026/06/22 15:12:59 rkujawa Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/bus.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/audioio.h>
#include <dev/audio/audio_if.h>
#include <dev/ic/ac97var.h>
#include <dev/ic/ac97reg.h>
#include <dev/ic/sm502reg.h>
#include <dev/pci/voyagervar.h>
#define VAC_RATE 48000
#define VAC_CHANNELS 2
#define VAC_PRECISION 16
#define VAC_FRAMESIZE (VAC_CHANNELS * (VAC_PRECISION / 8))
#define VAC_CODEC_TIMEOUT 1000
#define VAC_DP_BASE SM502_uC_SRAM_DUALPORT
#define VAC_DP_H_CMD 0x00
#define VAC_DP_H_DONE 0x01
#define VAC_DP_H_ALIVE 0x03
#define VAC_DP_H_FC0L 0x04
#define VAC_DP_H_FC0H 0x05
#define VAC_DP_H_FC1L 0x06
#define VAC_DP_H_FC1H 0x07
#define VAC_DP_BUF0 0x10
#define VAC_DP_MAXFRAMES 504
#define VAC_DP_BUFBYTES (VAC_DP_MAXFRAMES * VAC_FRAMESIZE)
#define VAC_DP_BUF1 (VAC_DP_BUF0 + VAC_DP_BUFBYTES)
#define VAC_DP_ALIVE_MAGIC 0x51
struct vac_softc {
device_t sc_dev;
bus_space_tag_t sc_memt;
bus_space_handle_t sc_regh;
void *sc_parent;
kmutex_t sc_lock;
kmutex_t sc_intr_lock;
struct ac97_host_if sc_ac97_host;
struct ac97_codec_if *sc_ac97_codec;
device_t sc_audiodev;
bool sc_active;
uint8_t *sc_pstart;
uint8_t *sc_pend;
uint8_t *sc_pcur;
int sc_pblksize;
void (*sc_pint)(void *);
void *sc_pintarg;
callout_t sc_feed_ch;
int sc_feed_ticks;
uint8_t *sc_dpstage;
uint8_t sc_cmd_seq;
uint8_t sc_done_seen;
bool sc_8051_ok;
int sc_underruns;
struct sysctllog *sc_sysctllog;
};
#define VAC_READ(sc, reg) \
bus_space_read_4((sc)->sc_memt, (sc)->sc_regh, (reg))
#define VAC_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_memt, (sc)->sc_regh, (reg), (val))
#define VAC_DP_R8(sc, off) \
bus_space_read_1((sc)->sc_memt, (sc)->sc_regh, VAC_DP_BASE + (off))
#define VAC_DP_W8(sc, off, v) \
bus_space_write_1((sc)->sc_memt, (sc)->sc_regh, VAC_DP_BASE + (off), (v))
static const uint8_t vac8051_fw[] = {
0x90, 0x30, 0x03, 0x74, 0x51, 0xf0, 0x90, 0x30, 0x01, 0xe4, 0xf0, 0x7f,
0x00, 0x90, 0x30, 0x00, 0xe0, 0xfe, 0x6f, 0x60, 0xf8, 0xee, 0x54, 0x01,
0x60, 0x10, 0x90, 0x30, 0x06, 0xe0, 0xfc, 0x90, 0x30, 0x07, 0xe0, 0xfd,
0x7a, 0xf0, 0x7b, 0x37, 0x80, 0x0e, 0x90, 0x30, 0x04, 0xe0, 0xfc, 0x90,
0x30, 0x05, 0xe0, 0xfd, 0x7a, 0x10, 0x7b, 0x30, 0x78, 0x00, 0x79, 0x00,
0x90, 0x91, 0x45, 0xe0, 0x54, 0x08, 0x60, 0x04, 0xd9, 0xf6, 0xd8, 0xf4,
0x8a, 0x82, 0x8b, 0x83, 0xe0, 0xf5, 0x20, 0xa3, 0xe0, 0xf5, 0x21, 0xa3,
0xe0, 0xf5, 0x24, 0xa3, 0xe0, 0xf5, 0x25, 0xa3, 0xaa, 0x82, 0xab, 0x83,
0x90, 0x91, 0x00, 0xe4, 0xf0, 0xa3, 0x74, 0x98, 0xf0, 0xe5, 0x20, 0xc4,
0xf5, 0x22, 0xe5, 0x21, 0xc4, 0xf5, 0x23, 0x90, 0x91, 0x0c, 0xe5, 0x22,
0x54, 0xf0, 0xf0, 0xa3, 0xe5, 0x22, 0x54, 0x0f, 0xf5, 0x26, 0xe5, 0x23,
0x54, 0xf0, 0x45, 0x26, 0xf0, 0xa3, 0xe5, 0x23, 0x54, 0x0f, 0xf0, 0xa3,
0xe4, 0xf0, 0xe5, 0x24, 0xc4, 0xf5, 0x22, 0xe5, 0x25, 0xc4, 0xf5, 0x23,
0x90, 0x91, 0x10, 0xe5, 0x22, 0x54, 0xf0, 0xf0, 0xa3, 0xe5, 0x22, 0x54,
0x0f, 0xf5, 0x26, 0xe5, 0x23, 0x54, 0xf0, 0x45, 0x26, 0xf0, 0xa3, 0xe5,
0x23, 0x54, 0x0f, 0xf0, 0xa3, 0xe4, 0xf0, 0xec, 0x70, 0x01, 0x1d, 0x1c,
0xec, 0x4d, 0x60, 0x03, 0x02, 0x00, 0x38, 0x90, 0x30, 0x01, 0xee, 0xf0,
0xff, 0x02, 0x00, 0x0d
};
static int vac_match(device_t, cfdata_t, void *);
static void vac_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(vac, sizeof(struct vac_softc),
vac_match, vac_attach, NULL, NULL);
static const struct audio_format vac_format = {
.mode = AUMODE_PLAY,
.encoding = AUDIO_ENCODING_SLINEAR_LE,
.validbits = VAC_PRECISION,
.precision = VAC_PRECISION,
.channels = VAC_CHANNELS,
.channel_mask = AUFMT_STEREO,
.frequency_type = 1,
.frequency = { VAC_RATE }
};
static int
vac_ac97_read(void *priv, uint8_t reg, uint16_t *val)
{
struct vac_softc * const sc = priv;
uint32_t addr, rx;
int i;
addr = ((uint32_t)reg << SM502_AC97_ADDR_SHIFT) & SM502_AC97_ADDR_MASK;
VAC_WRITE(sc, SM502_AC97_TX_TAG,
SM502_AC97_FRAME_VALID | SM502_AC97_S1_VALID);
VAC_WRITE(sc, SM502_AC97_TX_ADDR, addr | SM502_AC97_READ);
for (i = 0; i < VAC_CODEC_TIMEOUT; i++) {
rx = VAC_READ(sc, SM502_AC97_RX_ADDR);
if ((rx & SM502_AC97_ADDR_MASK) == addr)
break;
DELAY(1);
}
if (i == VAC_CODEC_TIMEOUT)
return 1;
*val = (VAC_READ(sc, SM502_AC97_RX_DATA) & SM502_AC97_DATA_MASK) >> 4;
return 0;
}
static int
vac_ac97_write(void *priv, uint8_t reg, uint16_t val)
{
struct vac_softc * const sc = priv;
uint32_t addr;
addr = ((uint32_t)reg << SM502_AC97_ADDR_SHIFT) & SM502_AC97_ADDR_MASK;
VAC_WRITE(sc, SM502_AC97_TX_TAG,
SM502_AC97_FRAME_VALID | SM502_AC97_S1_VALID | SM502_AC97_S2_VALID);
VAC_WRITE(sc, SM502_AC97_TX_DATA,
((uint32_t)val << 4) & SM502_AC97_DATA_MASK);
VAC_WRITE(sc, SM502_AC97_TX_ADDR, addr);
DELAY(100);
return 0;
}
static int
vac_ac97_reset(void *priv)
{
struct vac_softc * const sc = priv;
uint32_t reg;
int i;
aprint_debug_dev(sc->sc_dev, "ac97 control at reset entry 0x%08x\n",
VAC_READ(sc, SM502_AC97_CONTROL));
VAC_WRITE(sc, SM502_AC97_CONTROL,
SM502_AC97_ENABLE | SM502_AC97_COLD_RESET);
DELAY(20);
VAC_WRITE(sc, SM502_AC97_CONTROL, SM502_AC97_ENABLE);
for (i = 0; i < 2500; i++) {
reg = VAC_READ(sc, SM502_AC97_CONTROL);
if ((reg & SM502_AC97_BCLK_RUNNING) != 0 &&
(reg & SM502_AC97_STATUS_MASK) == SM502_AC97_STATUS_ON) {
aprint_debug_dev(sc->sc_dev,
"AC-link up (control 0x%08x)\n", reg);
return 0;
}
DELAY(100);
}
aprint_error_dev(sc->sc_dev,
"AC-link did not come ready (control 0x%08x)\n", reg);
return 1;
}
static int
vac_ac97_attach(void *priv, struct ac97_codec_if *codec)
{
struct vac_softc * const sc = priv;
sc->sc_ac97_codec = codec;
return 0;
}
static void
vac_unmute(struct vac_softc *sc)
{
struct ac97_codec_if * const codec = sc->sc_ac97_codec;
static const struct {
const char *class;
const char *device;
} ports[] = {
{ AudioCoutputs, AudioNmaster },
{ AudioCinputs, AudioNdac },
};
mixer_ctrl_t mc;
int i, idx;
mutex_enter(&sc->sc_lock);
for (i = 0; i < (int)__arraycount(ports); i++) {
idx = codec->vtbl->get_portnum_by_name(codec,
ports[i].class, ports[i].device, NULL);
if (idx >= 0) {
memset(&mc, 0, sizeof(mc));
mc.dev = idx;
mc.type = AUDIO_MIXER_VALUE;
mc.un.value.num_channels = 2;
mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = 200;
mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = 200;
(void)codec->vtbl->mixer_set_port(codec, &mc);
}
idx = codec->vtbl->get_portnum_by_name(codec,
ports[i].class, ports[i].device, AudioNmute);
if (idx >= 0) {
memset(&mc, 0, sizeof(mc));
mc.dev = idx;
mc.type = AUDIO_MIXER_ENUM;
mc.un.ord = 0;
(void)codec->vtbl->mixer_set_port(codec, &mc);
}
}
mutex_exit(&sc->sc_lock);
}
static int
vac_query_format(void *priv, audio_format_query_t *afp)
{
return audio_query_format(&vac_format, 1, afp);
}
static int
vac_set_format(void *priv, int setmode,
const audio_params_t *play, const audio_params_t *rec,
audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
return 0;
}
static int
vac_getdev(void *priv, struct audio_device *ad)
{
snprintf(ad->name, sizeof(ad->name), "SM502");
snprintf(ad->version, sizeof(ad->version), "AC97");
snprintf(ad->config, sizeof(ad->config), "vac");
return 0;
}
static int
vac_set_port(void *priv, mixer_ctrl_t *mc)
{
struct vac_softc * const sc = priv;
return sc->sc_ac97_codec->vtbl->mixer_set_port(sc->sc_ac97_codec, mc);
}
static int
vac_get_port(void *priv, mixer_ctrl_t *mc)
{
struct vac_softc * const sc = priv;
return sc->sc_ac97_codec->vtbl->mixer_get_port(sc->sc_ac97_codec, mc);
}
static int
vac_query_devinfo(void *priv, mixer_devinfo_t *di)
{
struct vac_softc * const sc = priv;
return sc->sc_ac97_codec->vtbl->query_devinfo(sc->sc_ac97_codec, di);
}
static int
vac_get_props(void *priv)
{
return AUDIO_PROP_PLAYBACK;
}
static int
vac_round_blocksize(void *priv, int blk, int mode,
const audio_params_t *param)
{
if (blk > VAC_DP_BUFBYTES)
blk = VAC_DP_BUFBYTES;
blk &= ~(VAC_FRAMESIZE - 1);
if (blk < VAC_FRAMESIZE)
blk = VAC_FRAMESIZE;
return blk;
}
static void
vac_dp_queue(struct vac_softc *sc)
{
const uint8_t cmd = sc->sc_cmd_seq + 1;
uint8_t *p = sc->sc_pcur;
bus_size_t base;
int n, i;
n = sc->sc_pblksize / VAC_FRAMESIZE;
if (n > VAC_DP_MAXFRAMES)
n = VAC_DP_MAXFRAMES;
for (i = 0; i < n; i++) {
uint8_t * const d = sc->sc_dpstage + i * VAC_FRAMESIZE;
d[0] = p[0];
d[1] = p[1];
d[2] = p[2];
d[3] = p[3];
p += VAC_FRAMESIZE;
if (p >= sc->sc_pend)
p = sc->sc_pstart;
}
sc->sc_pcur = p;
base = VAC_DP_BASE + ((cmd & 1) ? VAC_DP_BUF1 : VAC_DP_BUF0);
bus_space_write_region_1(sc->sc_memt, sc->sc_regh, base,
sc->sc_dpstage, n * VAC_FRAMESIZE);
if (cmd & 1) {
VAC_DP_W8(sc, VAC_DP_H_FC1L, n & 0xff);
VAC_DP_W8(sc, VAC_DP_H_FC1H, (n >> 8) & 0xff);
} else {
VAC_DP_W8(sc, VAC_DP_H_FC0L, n & 0xff);
VAC_DP_W8(sc, VAC_DP_H_FC0H, (n >> 8) & 0xff);
}
bus_space_barrier(sc->sc_memt, sc->sc_regh, VAC_DP_BASE, 0x1000,
BUS_SPACE_BARRIER_WRITE);
VAC_DP_W8(sc, VAC_DP_H_CMD, cmd);
sc->sc_cmd_seq = cmd;
}
static void
vac_feed_callout(void *arg)
{
struct vac_softc * const sc = arg;
uint8_t done;
int n;
mutex_enter(&sc->sc_intr_lock);
if (!sc->sc_active) {
mutex_exit(&sc->sc_intr_lock);
return;
}
done = VAC_DP_R8(sc, VAC_DP_H_DONE);
if (sc->sc_cmd_seq == done)
sc->sc_underruns++;
n = (uint8_t)(done - sc->sc_done_seen);
sc->sc_done_seen = done;
while (n-- > 0 && sc->sc_active && sc->sc_pint != NULL)
sc->sc_pint(sc->sc_pintarg);
while ((uint8_t)(sc->sc_cmd_seq - done) <= 1 && sc->sc_active)
vac_dp_queue(sc);
callout_schedule(&sc->sc_feed_ch, sc->sc_feed_ticks);
mutex_exit(&sc->sc_intr_lock);
}
static int
vac_trigger_output(void *priv, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, const audio_params_t *params)
{
struct vac_softc * const sc = priv;
int i;
KASSERT(mutex_owned(&sc->sc_intr_lock));
KASSERT(mutex_owned(&sc->sc_lock));
if (!sc->sc_8051_ok)
return ENXIO;
VAC_WRITE(sc, SM502_uC_RESET, 0);
for (i = 0; i < 16; i++)
VAC_DP_W8(sc, i, 0);
VAC_WRITE(sc, SM502_uC_RESET, SM502_uC_ENABLE);
for (i = 0; i < 5000 &&
VAC_DP_R8(sc, VAC_DP_H_ALIVE) != VAC_DP_ALIVE_MAGIC; i++)
DELAY(10);
sc->sc_pstart = start;
sc->sc_pend = end;
sc->sc_pcur = start;
sc->sc_pblksize = blksize;
sc->sc_pint = intr;
sc->sc_pintarg = intrarg;
sc->sc_cmd_seq = 0;
sc->sc_done_seen = 0;
sc->sc_underruns = 0;
sc->sc_active = true;
vac_dp_queue(sc);
vac_dp_queue(sc);
callout_schedule(&sc->sc_feed_ch, sc->sc_feed_ticks);
return 0;
}
static int
vac_halt_output(void *priv)
{
struct vac_softc * const sc = priv;
KASSERT(mutex_owned(&sc->sc_intr_lock));
sc->sc_active = false;
sc->sc_pint = NULL;
callout_stop(&sc->sc_feed_ch);
VAC_WRITE(sc, SM502_uC_RESET, 0);
return 0;
}
static void
vac_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread)
{
struct vac_softc * const sc = priv;
*intr = &sc->sc_intr_lock;
*thread = &sc->sc_lock;
}
static const struct audio_hw_if vac_hw_if = {
.query_format = vac_query_format,
.set_format = vac_set_format,
.getdev = vac_getdev,
.set_port = vac_set_port,
.get_port = vac_get_port,
.query_devinfo = vac_query_devinfo,
.get_props = vac_get_props,
.round_blocksize = vac_round_blocksize,
.trigger_output = vac_trigger_output,
.halt_output = vac_halt_output,
.get_locks = vac_get_locks,
};
static void
vac_load_fw(struct vac_softc *sc, bus_space_handle_t h, bus_size_t base)
{
size_t i;
for (i = 0; i < sizeof(vac8051_fw); i += 4) {
uint8_t b0 = vac8051_fw[i];
uint8_t b1 = (i + 1 < sizeof(vac8051_fw)) ? vac8051_fw[i + 1] : 0;
uint8_t b2 = (i + 2 < sizeof(vac8051_fw)) ? vac8051_fw[i + 2] : 0;
uint8_t b3 = (i + 3 < sizeof(vac8051_fw)) ? vac8051_fw[i + 3] : 0;
bus_space_write_4(sc->sc_memt, h, base + i,
b0 | (b1 << 8) | (b2 << 16) | (b3 << 24));
}
}
static void
vac_8051_bringup(struct vac_softc *sc)
{
uint32_t v;
uint8_t alive;
int i;
VAC_WRITE(sc, SM502_uC_RESET, 0);
v = VAC_READ(sc, SM502_uC_MODE_SELECT);
v &= ~(SM502_uC_SRAM_DISABLE | SM502_uC_CODEC_I2S | SM502_uC_CLOCK_MASK);
VAC_WRITE(sc, SM502_uC_MODE_SELECT, v);
vac_load_fw(sc, sc->sc_regh, SM502_uC_SRAM_PROG);
for (i = 0; i < 16; i++)
VAC_DP_W8(sc, i, 0);
VAC_WRITE(sc, SM502_uC_RESET, SM502_uC_ENABLE);
DELAY(2000);
alive = VAC_DP_R8(sc, VAC_DP_H_ALIVE);
sc->sc_8051_ok = (alive == VAC_DP_ALIVE_MAGIC);
aprint_verbose_dev(sc->sc_dev, "8051 feeder %s (alive=0x%02x)\n",
sc->sc_8051_ok ? "loaded" : "not responding", alive);
if (!sc->sc_8051_ok)
aprint_error_dev(sc->sc_dev,
"8051 feeder did not start; playback unavailable\n");
VAC_WRITE(sc, SM502_uC_RESET, 0);
}
#ifdef VAC_DEBUG
static int
vac_sysctl_diag(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct vac_softc * const sc = node.sysctl_data;
uint16_t r02 = 0, r04 = 0, r18 = 0, r26 = 0, r2a = 0, r2c = 0, r7a = 0;
char buf[200];
vac_ac97_read(sc, 0x02, &r02);
vac_ac97_read(sc, 0x04, &r04);
vac_ac97_read(sc, 0x18, &r18);
vac_ac97_read(sc, 0x26, &r26);
vac_ac97_read(sc, 0x2a, &r2a);
vac_ac97_read(sc, 0x2c, &r2c);
vac_ac97_read(sc, 0x7a, &r7a);
snprintf(buf, sizeof(buf),
"acctl=%08x mode=%08x reset=%08x | "
"m02=%04x hp04=%04x pcm18=%04x pwr26=%04x ext2a=%04x rate2c=%04x ven7a=%04x",
VAC_READ(sc, SM502_AC97_CONTROL),
VAC_READ(sc, SM502_uC_MODE_SELECT),
VAC_READ(sc, SM502_uC_RESET),
r02, r04, r18, r26, r2a, r2c, r7a);
node.sysctl_data = buf;
node.sysctl_size = strlen(buf) + 1;
return sysctl_lookup(SYSCTLFN_CALL(&node));
}
#endif
static void
vac_sysctl_setup(struct vac_softc *sc)
{
const struct sysctlnode *node = NULL;
if (sysctl_createv(&sc->sc_sysctllog, 0, NULL, &node,
0, CTLTYPE_NODE, "vac", SYSCTL_DESCR("SM502 AC97-link audio"),
NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL) != 0 || node == NULL)
return;
sysctl_createv(&sc->sc_sysctllog, 0, NULL, NULL,
CTLFLAG_READONLY, CTLTYPE_INT, "underruns",
SYSCTL_DESCR("times the 8051 ran dry during the last playback"),
NULL, 0, &sc->sc_underruns, 0, CTL_HW, node->sysctl_num,
CTL_CREATE, CTL_EOL);
#ifdef VAC_DEBUG
sysctl_createv(&sc->sc_sysctllog, 0, NULL, NULL,
CTLFLAG_READONLY, CTLTYPE_STRING, "diag",
SYSCTL_DESCR("AC97 link + codec register dump (read while idle)"),
vac_sysctl_diag, 0, (void *)sc, 0, CTL_HW, node->sysctl_num,
CTL_CREATE, CTL_EOL);
#endif
}
static int
vac_match(device_t parent, cfdata_t match, void *aux)
{
struct voyager_attach_args *vaa = aux;
if (strcmp(vaa->vaa_name, "vac") == 0)
return 100;
return 0;
}
static void
vac_attach(device_t parent, device_t self, void *aux)
{
struct vac_softc * const sc = device_private(self);
struct voyager_attach_args *vaa = aux;
int error;
sc->sc_dev = self;
sc->sc_memt = vaa->vaa_tag;
sc->sc_regh = vaa->vaa_regh;
sc->sc_parent = device_private(parent);
aprint_naive("\n");
aprint_normal(": SM502 AC97-link\n");
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);
callout_init(&sc->sc_feed_ch, CALLOUT_MPSAFE);
callout_setfunc(&sc->sc_feed_ch, vac_feed_callout, sc);
sc->sc_feed_ticks = mstohz(5);
if (sc->sc_feed_ticks < 1)
sc->sc_feed_ticks = 1;
sc->sc_dpstage = kmem_alloc(VAC_DP_BUFBYTES, KM_SLEEP);
aprint_debug_dev(self,
"power: ctrl 0x%08x cur-gate 0x%08x gate0 0x%08x gate1 0x%08x\n",
VAC_READ(sc, SM502_POWER_MODE_CONTROL),
VAC_READ(sc, SM502_CURRENT_GATE),
VAC_READ(sc, SM502_POWER_MODE0_GATE),
VAC_READ(sc, SM502_POWER_MODE1_GATE));
VAC_WRITE(sc, SM502_POWER_MODE0_GATE,
VAC_READ(sc, SM502_POWER_MODE0_GATE) |
SM502_GATE_AUDIO_ENABLE | SM502_GATE_8051_ENABLE);
VAC_WRITE(sc, SM502_POWER_MODE1_GATE,
VAC_READ(sc, SM502_POWER_MODE1_GATE) |
SM502_GATE_AUDIO_ENABLE | SM502_GATE_8051_ENABLE);
DELAY(1000);
voyager_control_gpio(sc->sc_parent, 0xffffffff, SM502_AUDIO_GPIO_MASK);
VAC_WRITE(sc, SM502_uC_MODE_SELECT,
VAC_READ(sc, SM502_uC_MODE_SELECT) & ~SM502_uC_CODEC_I2S);
sc->sc_ac97_host.arg = sc;
sc->sc_ac97_host.attach = vac_ac97_attach;
sc->sc_ac97_host.read = vac_ac97_read;
sc->sc_ac97_host.write = vac_ac97_write;
sc->sc_ac97_host.reset = vac_ac97_reset;
error = ac97_attach(&sc->sc_ac97_host, self, &sc->sc_lock);
if (error) {
aprint_error_dev(self, "couldn't attach codec (%d)\n", error);
return;
}
vac_sysctl_setup(sc);
vac_8051_bringup(sc);
sc->sc_audiodev = audio_attach_mi(&vac_hw_if, sc, self);
vac_unmute(sc);
}