#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include "audio810.h"
static int audio810_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int audio810_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static int audio810_ddi_quiesce(dev_info_t *);
static int audio810_open(void *, int, unsigned *, caddr_t *);
static void audio810_close(void *);
static int audio810_start(void *);
static void audio810_stop(void *);
static int audio810_format(void *);
static int audio810_channels(void *);
static int audio810_rate(void *);
static uint64_t audio810_count(void *);
static void audio810_sync(void *, unsigned);
static unsigned audio810_playahead(void *);
static audio_engine_ops_t audio810_engine_ops = {
AUDIO_ENGINE_VERSION,
audio810_open,
audio810_close,
audio810_start,
audio810_stop,
audio810_count,
audio810_format,
audio810_channels,
audio810_rate,
audio810_sync,
NULL,
NULL,
audio810_playahead
};
static int audio810_attach(dev_info_t *);
static int audio810_resume(dev_info_t *);
static int audio810_detach(dev_info_t *);
static int audio810_suspend(dev_info_t *);
static int audio810_alloc_port(audio810_state_t *, int, uint8_t);
static int audio810_codec_sync(audio810_state_t *);
static void audio810_write_ac97(void *, uint8_t, uint16_t);
static uint16_t audio810_read_ac97(void *, uint8_t);
static int audio810_map_regs(dev_info_t *, audio810_state_t *);
static void audio810_unmap_regs(audio810_state_t *);
static void audio810_stop_dma(audio810_state_t *);
static int audio810_chip_init(audio810_state_t *);
static void audio810_set_channels(audio810_state_t *);
static void audio810_destroy(audio810_state_t *);
static struct dev_ops audio810_dev_ops = {
DEVO_REV,
0,
NULL,
nulldev,
nulldev,
audio810_ddi_attach,
audio810_ddi_detach,
nodev,
NULL,
NULL,
NULL,
audio810_ddi_quiesce,
};
static struct modldrv audio810_modldrv = {
&mod_driverops,
I810_MOD_NAME,
&audio810_dev_ops,
};
static struct modlinkage audio810_modlinkage = {
MODREV_1,
(void *)&audio810_modldrv,
NULL
};
static struct ddi_device_acc_attr dev_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static struct ddi_device_acc_attr buf_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static ddi_dma_attr_t bdlist_dma_attr = {
DMA_ATTR_V0,
0,
0xffffffff,
0x0000ffff,
8,
0x3c,
8,
0x0000ffff,
0x00000fff,
1,
8,
0
};
static ddi_dma_attr_t sample_buf_dma_attr = {
DMA_ATTR_V0,
0,
0xffffffff,
0x0001ffff,
4,
0x3c,
4,
0x0001ffff,
0x0001ffff,
1,
4,
0,
};
int
_init(void)
{
int error;
audio_init_ops(&audio810_dev_ops, I810_NAME);
if ((error = mod_install(&audio810_modlinkage)) != 0) {
audio_fini_ops(&audio810_dev_ops);
}
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&audio810_modlinkage)) != 0) {
return (error);
}
audio_fini_ops(&audio810_dev_ops);
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&audio810_modlinkage, modinfop));
}
static int
audio810_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
return (audio810_attach(dip));
case DDI_RESUME:
return (audio810_resume(dip));
}
return (DDI_FAILURE);
}
static int
audio810_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
return (audio810_detach(dip));
case DDI_SUSPEND:
return (audio810_suspend(dip));
}
return (DDI_FAILURE);
}
static int
audio810_ddi_quiesce(dev_info_t *dip)
{
audio810_state_t *statep;
if ((statep = ddi_get_driver_private(dip)) == NULL)
return (DDI_FAILURE);
audio810_stop_dma(statep);
return (DDI_SUCCESS);
}
static int
audio810_open(void *arg, int flag, unsigned *nframes, caddr_t *bufp)
{
audio810_port_t *port = arg;
_NOTE(ARGUNUSED(flag));
port->count = 0;
*nframes = port->samp_frames;
*bufp = port->samp_kaddr;
return (0);
}
static void
audio810_close(void *arg)
{
_NOTE(ARGUNUSED(arg));
}
static void
audio810_stop(void *arg)
{
audio810_port_t *port = arg;
audio810_state_t *statep = port->statep;
uint8_t cr;
cr = I810_BM_GET8(port->regoff + I810_OFFSET_CR);
cr &= ~I810_BM_CR_RUN;
I810_BM_PUT8(port->regoff + I810_OFFSET_CR, cr);
}
static int
audio810_start(void *arg)
{
audio810_port_t *port = arg;
audio810_state_t *statep = port->statep;
uint8_t regoff, cr;
regoff = port->regoff;
port->offset = 0;
if (port->num == I810_PCM_OUT) {
audio810_set_channels(statep);
if (statep->quirk == QUIRK_SIS7012) {
I810_BM_PUT8(I810_REG_SISCTL, I810_SISCTL_UNMUTE);
}
}
I810_BM_PUT8(regoff + I810_OFFSET_CR, 0);
I810_BM_PUT8(regoff + I810_OFFSET_CR, I810_BM_CR_RST);
I810_BM_PUT32(regoff + I810_OFFSET_BD_BASE, port->bdl_paddr);
I810_BM_PUT8(regoff + I810_OFFSET_LVI, I810_BD_NUMS - 1);
cr = I810_BM_GET8(regoff + I810_OFFSET_CR);
cr |= I810_BM_CR_RUN;
I810_BM_PUT8(regoff + I810_OFFSET_CR, cr);
(void) I810_BM_GET8(regoff + I810_OFFSET_CR);
return (0);
}
static int
audio810_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (AUDIO_FORMAT_S16_LE);
}
static int
audio810_channels(void *arg)
{
audio810_port_t *port = arg;
return (port->nchan);
}
static int
audio810_rate(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (48000);
}
static uint64_t
audio810_count(void *arg)
{
audio810_port_t *port = arg;
audio810_state_t *statep = port->statep;
uint8_t regoff = port->regoff;
uint64_t val;
uint32_t offset;
uint8_t civ;
offset = I810_BM_GET16(port->picboff);
civ = I810_BM_GET8(regoff + I810_OFFSET_CIV);
I810_BM_PUT8(port->regoff + I810_OFFSET_LVI, (civ - 1) % I810_BD_NUMS);
if (statep->quirk != QUIRK_SIS7012)
offset *= 2;
offset = port->samp_size - offset;
if (offset < port->offset) {
val = (port->samp_size - port->offset) + offset;
} else {
val = offset - port->offset;
}
port->offset = offset;
port->count += (val / (port->nchan * 2));
val = port->count;
return (val);
}
static void
audio810_sync(void *arg, unsigned nframes)
{
audio810_port_t *port = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(port->samp_dmah, 0, 0, port->sync_dir);
}
static unsigned
audio810_playahead(void *arg)
{
audio810_port_t *port = arg;
audio810_state_t *statep = port->statep;
return (statep->quirk == QUIRK_OLDICH ? 1920 : 0);
}
static int
audio810_attach(dev_info_t *dip)
{
uint16_t cmdreg;
audio810_state_t *statep;
audio_dev_t *adev;
ddi_acc_handle_t pcih;
uint32_t devid;
uint32_t gsr;
const char *name;
const char *vers;
uint8_t nch;
int maxch;
statep = kmem_zalloc(sizeof (*statep), KM_SLEEP);
ddi_set_driver_private(dip, statep);
if ((adev = audio_dev_alloc(dip, 0)) == NULL) {
cmn_err(CE_WARN, "!%s%d: unable to allocate audio dev",
ddi_driver_name(dip), ddi_get_instance(dip));
goto error;
}
statep->adev = adev;
statep->dip = dip;
if (audio810_map_regs(dip, statep) != DDI_SUCCESS) {
audio_dev_warn(adev, "couldn't map registers");
goto error;
}
if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
audio_dev_warn(adev, "pci conf mapping failed");
goto error;
}
cmdreg = pci_config_get16(pcih, PCI_CONF_COMM);
pci_config_put16(pcih, PCI_CONF_COMM,
cmdreg | PCI_COMM_IO | PCI_COMM_MAE | PCI_COMM_ME);
devid = pci_config_get16(pcih, PCI_CONF_VENID);
devid <<= 16;
devid |= pci_config_get16(pcih, PCI_CONF_DEVID);
pci_config_teardown(&pcih);
name = "Unknown AC'97";
vers = "";
statep->quirk = QUIRK_NONE;
switch (devid) {
case 0x80862415:
name = "Intel AC'97";
vers = "ICH";
statep->quirk = QUIRK_OLDICH;
break;
case 0x80862425:
name = "Intel AC'97";
vers = "ICH0";
break;
case 0x80867195:
name = "Intel AC'97";
vers = "440MX";
break;
case 0x80862445:
name = "Intel AC'97";
vers = "ICH2";
break;
case 0x80862485:
name = "Intel AC'97";
vers = "ICH3";
break;
case 0x808624C5:
name = "Intel AC'97";
vers = "ICH4";
break;
case 0x808624D5:
name = "Intel AC'97";
vers = "ICH5";
break;
case 0x8086266E:
name = "Intel AC'97";
vers = "ICH6";
break;
case 0x808627DE:
name = "Intel AC'97";
vers = "ICH7";
break;
case 0x808625A6:
name = "Intel AC'97";
vers = "6300ESB";
break;
case 0x80862698:
name = "Intel AC'97";
vers = "ESB2";
break;
case 0x10397012:
name = "SiS AC'97";
vers = "7012";
statep->quirk = QUIRK_SIS7012;
break;
case 0x10de01b1:
name = "NVIDIA AC'97";
vers = "MCP1";
break;
case 0x10de006a:
name = "NVIDIA AC'97";
vers = "MCP2";
break;
case 0x10de00da:
name = "NVIDIA AC'97";
vers = "MCP3";
break;
case 0x10de00ea:
name = "NVIDIA AC'97";
vers = "CK8S";
break;
case 0x10de0059:
name = "NVIDIA AC'97";
vers = "CK804";
break;
case 0x10de008a:
name = "NVIDIA AC'97";
vers = "CK8";
break;
case 0x10de003a:
name = "NVIDIA AC'97";
vers = "MCP4";
break;
case 0x10de026b:
name = "NVIDIA AC'97";
vers = "MCP51";
break;
case 0x1022746d:
name = "AMD AC'97";
vers = "8111";
break;
case 0x10227445:
name = "AMD AC'97";
vers = "AMD768";
break;
}
audio_dev_set_description(adev, name);
audio_dev_set_version(adev, vers);
if (audio810_chip_init(statep) != DDI_SUCCESS) {
audio_dev_warn(adev, "failed to init chip");
goto error;
}
statep->ac97 = ac97_alloc(dip, audio810_read_ac97, audio810_write_ac97,
statep);
if (statep->ac97 == NULL) {
audio_dev_warn(adev, "failed to allocate ac97 handle");
goto error;
}
if (ac97_init(statep->ac97, adev) != DDI_SUCCESS) {
audio_dev_warn(adev, "ac'97 initialization failed");
goto error;
}
maxch = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"max-channels", ac97_num_channels(statep->ac97));
if (maxch < 2) {
maxch = 2;
}
gsr = I810_BM_GET32(I810_REG_GSR);
if (gsr & I810_GSR_CAP6CH) {
nch = 6;
} else if (gsr & I810_GSR_CAP4CH) {
nch = 4;
} else {
nch = 2;
}
statep->maxch = (uint8_t)min(nch, maxch);
statep->maxch &= ~1;
if ((audio810_alloc_port(statep, I810_PCM_OUT, statep->maxch) !=
DDI_SUCCESS) ||
(audio810_alloc_port(statep, I810_PCM_IN, 2) != DDI_SUCCESS)) {
goto error;
}
if (audio_dev_register(adev) != DDI_SUCCESS) {
audio_dev_warn(adev, "unable to register with framework");
goto error;
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
audio810_destroy(statep);
return (DDI_FAILURE);
}
static int
audio810_resume(dev_info_t *dip)
{
audio810_state_t *statep;
audio_dev_t *adev;
statep = ddi_get_driver_private(dip);
adev = statep->adev;
ASSERT(statep != NULL);
ASSERT(dip == statep->dip);
if (audio810_chip_init(statep) != DDI_SUCCESS) {
audio_dev_warn(adev, "failure to resume codec");
}
ac97_reset(statep->ac97);
audio_dev_resume(statep->adev);
return (DDI_SUCCESS);
}
static int
audio810_detach(dev_info_t *dip)
{
audio810_state_t *statep;
statep = ddi_get_driver_private(dip);
ASSERT(statep != NULL);
if (audio_dev_unregister(statep->adev) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
audio810_destroy(statep);
return (DDI_SUCCESS);
}
static int
audio810_suspend(dev_info_t *dip)
{
audio810_state_t *statep;
statep = ddi_get_driver_private(dip);
ASSERT(statep != NULL);
audio_dev_suspend(statep->adev);
audio810_stop_dma(statep);
return (DDI_SUCCESS);
}
static int
audio810_alloc_port(audio810_state_t *statep, int num, uint8_t nchan)
{
ddi_dma_cookie_t cookie;
uint_t count;
int dir;
unsigned caps;
audio_dev_t *adev;
audio810_port_t *port;
int rc;
dev_info_t *dip;
i810_bd_entry_t *bdentry;
adev = statep->adev;
dip = statep->dip;
port = kmem_zalloc(sizeof (*port), KM_SLEEP);
statep->ports[num] = port;
port->statep = statep;
port->nchan = nchan;
port->num = num;
switch (num) {
case I810_PCM_IN:
dir = DDI_DMA_READ;
caps = ENGINE_INPUT_CAP;
port->sync_dir = DDI_DMA_SYNC_FORKERNEL;
port->regoff = I810_BASE_PCM_IN;
break;
case I810_PCM_OUT:
dir = DDI_DMA_WRITE;
caps = ENGINE_OUTPUT_CAP;
port->sync_dir = DDI_DMA_SYNC_FORDEV;
port->regoff = I810_BASE_PCM_OUT;
break;
default:
audio_dev_warn(adev, "bad port number (%d)!", num);
return (DDI_FAILURE);
}
if (statep->quirk == QUIRK_SIS7012) {
port->stsoff = port->regoff + I810_OFFSET_PICB;
port->picboff = port->regoff + I810_OFFSET_SR;
} else {
port->stsoff = port->regoff + I810_OFFSET_SR;
port->picboff = port->regoff + I810_OFFSET_PICB;
}
port->samp_frames = 4096;
port->samp_size = port->samp_frames * port->nchan * sizeof (int16_t);
rc = ddi_dma_alloc_handle(dip, &sample_buf_dma_attr, DDI_DMA_SLEEP,
NULL, &port->samp_dmah);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev, "ddi_dma_alloc_handle failed: %d", rc);
return (DDI_FAILURE);
}
rc = ddi_dma_mem_alloc(port->samp_dmah, port->samp_size, &buf_attr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->samp_kaddr,
&port->samp_size, &port->samp_acch);
if (rc == DDI_FAILURE) {
audio_dev_warn(adev, "dma_mem_alloc (%d) failed: %d",
port->samp_size, rc);
return (DDI_FAILURE);
}
rc = ddi_dma_addr_bind_handle(port->samp_dmah, NULL,
port->samp_kaddr, port->samp_size, dir|DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, NULL, &cookie, &count);
if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
audio_dev_warn(adev,
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
port->samp_paddr = cookie.dmac_address;
rc = ddi_dma_alloc_handle(dip, &bdlist_dma_attr, DDI_DMA_SLEEP,
NULL, &port->bdl_dmah);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev, "ddi_dma_alloc_handle(bdlist) failed");
return (DDI_FAILURE);
}
port->bdl_size = sizeof (i810_bd_entry_t) * I810_BD_NUMS;
rc = ddi_dma_mem_alloc(port->bdl_dmah, port->bdl_size,
&dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&port->bdl_kaddr, &port->bdl_size, &port->bdl_acch);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev, "ddi_dma_mem_alloc(bdlist) failed");
return (DDI_FAILURE);
}
rc = ddi_dma_addr_bind_handle(port->bdl_dmah, NULL, port->bdl_kaddr,
port->bdl_size, DDI_DMA_WRITE|DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
NULL, &cookie, &count);
if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
audio_dev_warn(adev, "addr_bind_handle failed");
return (DDI_FAILURE);
}
port->bdl_paddr = cookie.dmac_address;
bdentry = (void *)port->bdl_kaddr;
for (int i = 0; i < I810_BD_NUMS; i++) {
ddi_put32(port->bdl_acch, &bdentry->buf_base,
port->samp_paddr);
ddi_put16(port->bdl_acch, &bdentry->buf_len,
statep->quirk == QUIRK_SIS7012 ? port->samp_size :
port->samp_size / 2);
ddi_put16(port->bdl_acch, &bdentry->buf_cmd, BUF_CMD_BUP);
bdentry++;
}
(void) ddi_dma_sync(port->bdl_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
port->engine = audio_engine_alloc(&audio810_engine_ops, caps);
if (port->engine == NULL) {
audio_dev_warn(adev, "audio_engine_alloc failed");
return (DDI_FAILURE);
}
audio_engine_set_private(port->engine, port);
audio_dev_add_engine(adev, port->engine);
return (DDI_SUCCESS);
}
static void
audio810_free_port(audio810_port_t *port)
{
if (port == NULL)
return;
if (port->engine) {
audio_dev_remove_engine(port->statep->adev, port->engine);
audio_engine_free(port->engine);
}
if (port->bdl_paddr) {
(void) ddi_dma_unbind_handle(port->bdl_dmah);
}
if (port->bdl_acch) {
ddi_dma_mem_free(&port->bdl_acch);
}
if (port->bdl_dmah) {
ddi_dma_free_handle(&port->bdl_dmah);
}
if (port->samp_paddr) {
(void) ddi_dma_unbind_handle(port->samp_dmah);
}
if (port->samp_acch) {
ddi_dma_mem_free(&port->samp_acch);
}
if (port->samp_dmah) {
ddi_dma_free_handle(&port->samp_dmah);
}
kmem_free(port, sizeof (*port));
}
static int
audio810_map_regs(dev_info_t *dip, audio810_state_t *statep)
{
uint_t nregs = 0;
int *regs_list;
int i;
int pciBar1 = 0;
int pciBar2 = 0;
int pciBar3 = 0;
int pciBar4 = 0;
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", (int **)®s_list, &nregs) != DDI_PROP_SUCCESS) {
audio_dev_warn(statep->adev, "inquire regs property failed");
goto error;
}
for (i = 1; i < nregs/I810_INTS_PER_REG_PROP; i++) {
switch (regs_list[I810_INTS_PER_REG_PROP * i] & 0x000000ff) {
case 0x10:
pciBar1 = i;
break;
case 0x14:
pciBar2 = i;
break;
case 0x18:
pciBar3 = i;
break;
case 0x1c:
pciBar4 = i;
break;
default:
break;
}
}
if ((pciBar3 != 0) && (pciBar4 != 0)) {
if ((ddi_regs_map_setup(dip, pciBar3, &statep->am_regs_base, 0,
0, &dev_attr, &statep->am_regs_handle)) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"memory am mapping failed");
goto error;
}
if ((ddi_regs_map_setup(dip, pciBar4, &statep->bm_regs_base, 0,
0, &dev_attr, &statep->bm_regs_handle)) != DDI_SUCCESS) {
audio_dev_warn(statep->adev,
"memory bm mapping failed");
goto error;
}
} else if ((pciBar1 != 0) && (pciBar2 != 0)) {
if ((ddi_regs_map_setup(dip, pciBar1, &statep->am_regs_base, 0,
0, &dev_attr, &statep->am_regs_handle)) != DDI_SUCCESS) {
audio_dev_warn(statep->adev, "I/O am mapping failed");
goto error;
}
if ((ddi_regs_map_setup(dip, pciBar2, &statep->bm_regs_base, 0,
0, &dev_attr, &statep->bm_regs_handle)) != DDI_SUCCESS) {
audio_dev_warn(statep->adev, "I/O bm mapping failed");
goto error;
}
} else {
audio_dev_warn(statep->adev, "map_regs() pci BAR error");
goto error;
}
ddi_prop_free(regs_list);
return (DDI_SUCCESS);
error:
if (nregs > 0) {
ddi_prop_free(regs_list);
}
audio810_unmap_regs(statep);
return (DDI_FAILURE);
}
static void
audio810_unmap_regs(audio810_state_t *statep)
{
if (statep->bm_regs_handle) {
ddi_regs_map_free(&statep->bm_regs_handle);
}
if (statep->am_regs_handle) {
ddi_regs_map_free(&statep->am_regs_handle);
}
}
static int
audio810_chip_init(audio810_state_t *statep)
{
uint32_t gcr;
uint32_t gsr;
uint32_t codec_ready;
int loop;
clock_t ticks;
gcr = I810_BM_GET32(I810_REG_GCR);
ticks = drv_usectohz(100);
if (statep->quirk == QUIRK_SIS7012) {
gcr &= ~(I810_GCR_ACLINK_OFF | I810_GCR_SIS_CHANNELS_MASK);
} else {
gcr &= ~(I810_GCR_ACLINK_OFF | I810_GCR_CHANNELS_MASK);
}
gcr |= (gcr & I810_GCR_COLD_RST) == 0 ?
I810_GCR_COLD_RST:I810_GCR_WARM_RST;
I810_BM_PUT32(I810_REG_GCR, gcr);
for (loop = 6000; --loop >= 0; ) {
delay(ticks);
gcr = I810_BM_GET32(I810_REG_GCR);
if ((gcr & I810_GCR_WARM_RST) == 0) {
break;
}
}
if (loop < 0) {
audio_dev_warn(statep->adev, "Failed to reset codec");
return (DDI_FAILURE);
}
codec_ready =
I810_GSR_PRI_READY | I810_GSR_SEC_READY | I810_GSR_TRI_READY;
for (loop = 7000; --loop >= 0; ) {
delay(ticks);
gsr = I810_BM_GET32(I810_REG_GSR);
if ((gsr & codec_ready) != 0) {
break;
}
}
if (loop < 0) {
audio_dev_warn(statep->adev, "No codec ready signal received");
return (DDI_FAILURE);
}
audio810_stop_dma(statep);
return (DDI_SUCCESS);
}
static void
audio810_set_channels(audio810_state_t *statep)
{
uint32_t gcr;
if (statep->quirk == QUIRK_SIS7012) {
gcr = I810_BM_GET32(I810_REG_GCR);
gcr &= ~I810_GCR_SIS_CHANNELS_MASK;
I810_BM_PUT32(I810_REG_GCR, gcr);
delay(drv_usectohz(50000));
switch (statep->maxch) {
case 2:
gcr |= I810_GCR_SIS_2_CHANNELS;
break;
case 4:
gcr |= I810_GCR_SIS_4_CHANNELS;
break;
case 6:
gcr |= I810_GCR_SIS_6_CHANNELS;
break;
}
I810_BM_PUT32(I810_REG_GCR, gcr);
delay(drv_usectohz(50000));
} else {
gcr = I810_BM_GET32(I810_REG_GCR);
gcr &= ~I810_GCR_CHANNELS_MASK;
I810_BM_PUT32(I810_REG_GCR, gcr);
delay(drv_usectohz(50000));
switch (statep->maxch) {
case 2:
gcr |= I810_GCR_2_CHANNELS;
break;
case 4:
gcr |= I810_GCR_4_CHANNELS;
break;
case 6:
gcr |= I810_GCR_6_CHANNELS;
break;
}
I810_BM_PUT32(I810_REG_GCR, gcr);
delay(drv_usectohz(50000));
}
}
static void
audio810_stop_dma(audio810_state_t *statep)
{
if (statep->bm_regs_handle == NULL) {
return;
}
I810_BM_PUT8(I810_BASE_PCM_IN + I810_OFFSET_CR, 0x0);
I810_BM_PUT8(I810_BASE_PCM_OUT + I810_OFFSET_CR, 0x0);
I810_BM_PUT8(I810_BASE_MIC + I810_OFFSET_CR, 0x0);
I810_BM_PUT8(I810_BASE_PCM_IN + I810_OFFSET_CR, I810_BM_CR_RST);
I810_BM_PUT8(I810_BASE_PCM_OUT + I810_OFFSET_CR, I810_BM_CR_RST);
I810_BM_PUT8(I810_BASE_MIC + I810_OFFSET_CR, I810_BM_CR_RST);
}
static int
audio810_codec_sync(audio810_state_t *statep)
{
int i;
uint16_t casr;
for (i = 0; i < 300; i++) {
casr = I810_BM_GET8(I810_REG_CASR);
if ((casr & 1) == 0) {
return (DDI_SUCCESS);
}
drv_usecwait(10);
}
return (DDI_FAILURE);
}
static void
audio810_write_ac97(void *arg, uint8_t reg, uint16_t data)
{
audio810_state_t *statep = arg;
if (audio810_codec_sync(statep) == DDI_SUCCESS) {
I810_AM_PUT16(reg, data);
}
(void) audio810_read_ac97(statep, reg);
}
static uint16_t
audio810_read_ac97(void *arg, uint8_t reg)
{
audio810_state_t *statep = arg;
uint16_t val = 0xffff;
if (audio810_codec_sync(statep) == DDI_SUCCESS) {
val = I810_AM_GET16(reg);
}
return (val);
}
void
audio810_destroy(audio810_state_t *statep)
{
audio810_stop_dma(statep);
for (int i = 0; i < I810_NUM_PORTS; i++) {
audio810_free_port(statep->ports[i]);
}
audio810_unmap_regs(statep);
if (statep->ac97)
ac97_free(statep->ac97);
if (statep->adev)
audio_dev_free(statep->adev);
kmem_free(statep, sizeof (*statep));
}