#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/pci.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/debug.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include "audiots.h"
static int audiots_attach(dev_info_t *, ddi_attach_cmd_t);
static int audiots_detach(dev_info_t *, ddi_detach_cmd_t);
static int audiots_quiesce(dev_info_t *);
static int audiots_open(void *, int, unsigned *, caddr_t *);
static void audiots_close(void *);
static int audiots_start(void *);
static void audiots_stop(void *);
static int audiots_format(void *);
static int audiots_channels(void *);
static int audiots_rate(void *);
static void audiots_chinfo(void *, int, unsigned *, unsigned *);
static uint64_t audiots_count(void *);
static void audiots_sync(void *, unsigned);
static audio_engine_ops_t audiots_engine_ops = {
AUDIO_ENGINE_VERSION,
audiots_open,
audiots_close,
audiots_start,
audiots_stop,
audiots_count,
audiots_format,
audiots_channels,
audiots_rate,
audiots_sync,
NULL,
audiots_chinfo,
NULL,
};
static void audiots_power_up(audiots_state_t *);
static void audiots_chip_init(audiots_state_t *);
static uint16_t audiots_get_ac97(void *, uint8_t);
static void audiots_set_ac97(void *, uint8_t, uint16_t);
static int audiots_init_state(audiots_state_t *, dev_info_t *);
static int audiots_map_regs(dev_info_t *, audiots_state_t *);
static uint16_t audiots_read_ac97(audiots_state_t *, int);
static void audiots_stop_everything(audiots_state_t *);
static void audiots_destroy(audiots_state_t *);
static int audiots_alloc_port(audiots_state_t *, int);
static void *audiots_statep;
static struct dev_ops audiots_dev_ops = {
DEVO_REV,
0,
NULL,
nulldev,
nulldev,
audiots_attach,
audiots_detach,
nodev,
NULL,
NULL,
NULL,
audiots_quiesce,
};
static struct modldrv audiots_modldrv = {
&mod_driverops,
TS_MOD_NAME,
&audiots_dev_ops
};
static struct modlinkage audiots_modlinkage = {
MODREV_1,
(void *)&audiots_modldrv,
NULL
};
static ddi_dma_attr_t audiots_attr = {
DMA_ATTR_VERSION,
0x0000000000000000LL,
0x00000000ffffffffLL,
0x0000000000003fffLL,
0x0000000000000008LL,
0x0000007f,
0x00000001,
0x0000000000003fffLL,
0x000000000000ffffLL,
0x00000001,
0x00000001,
0
};
static ddi_device_acc_attr_t ts_acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
static ddi_device_acc_attr_t ts_regs_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
int
_init(void)
{
int error;
audio_init_ops(&audiots_dev_ops, TS_NAME);
if ((error = ddi_soft_state_init(&audiots_statep,
sizeof (audiots_state_t), 1)) != 0) {
audio_fini_ops(&audiots_dev_ops);
return (error);
}
if ((error = mod_install(&audiots_modlinkage)) != 0) {
audio_fini_ops(&audiots_dev_ops);
ddi_soft_state_fini(&audiots_statep);
}
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&audiots_modlinkage)) != 0) {
return (error);
}
ddi_soft_state_fini(&audiots_statep);
audio_fini_ops(&audiots_dev_ops);
return (0);
}
int
_info(struct modinfo *modinfop)
{
int error;
error = mod_info(&audiots_modlinkage, modinfop);
return (error);
}
static int
audiots_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
state = ddi_get_soft_state(audiots_statep, instance);
ASSERT(dip == state->ts_dip);
if (state->ts_flags & TS_AUDIO_READ_FAILED) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_RESTORED,
DDI_DEVICE_FAULT,
"check port, gain, balance, and mute settings");
state->ts_flags &=
~(TS_AUDIO_READ_FAILED|TS_READ_FAILURE_PRINTED);
}
audiots_power_up(state);
audiots_chip_init(state);
ac97_reset(state->ts_ac97);
audio_dev_resume(state->ts_adev);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (ddi_get_devstate(dip) == DDI_DEVSTATE_DOWN) {
cmn_err(CE_WARN, "%s%d: The audio hardware has "
"been disabled.", ddi_driver_name(dip), instance);
cmn_err(CE_CONT, "Please reboot to restore audio.");
return (DDI_FAILURE);
}
if (ddi_soft_state_zalloc(audiots_statep, instance) == DDI_FAILURE) {
cmn_err(CE_WARN, "!%s%d: soft state allocate failed",
ddi_driver_name(dip), instance);
return (DDI_FAILURE);
}
state = ddi_get_soft_state(audiots_statep, instance);
ASSERT(state != NULL);
if ((state->ts_adev = audio_dev_alloc(dip, 0)) == NULL) {
cmn_err(CE_WARN, "unable to allocate audio dev");
goto error;
}
if (audiots_map_regs(dip, state) == DDI_FAILURE) {
audio_dev_warn(state->ts_adev, "unable to map registers");
goto error;
}
if (audiots_init_state(state, dip) == DDI_FAILURE) {
audio_dev_warn(state->ts_adev, "init state structure failed");
goto error;
}
audiots_power_up(state);
audiots_chip_init(state);
if (ac97_init(state->ts_ac97, state->ts_adev) != 0) {
goto error;
}
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_ainten,
TS_ALL_DMA_OFF);
if (audio_dev_register(state->ts_adev) != DDI_SUCCESS) {
audio_dev_warn(state->ts_adev, "unable to register audio");
goto error;
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
audiots_destroy(state);
return (DDI_FAILURE);
}
static int
audiots_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
if ((state = ddi_get_soft_state(audiots_statep, instance)) == NULL) {
cmn_err(CE_WARN, "!%s%d: detach get soft state failed",
ddi_driver_name(dip), instance);
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
audio_dev_suspend(state->ts_adev);
(void) audiots_stop_everything(state);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (audio_dev_unregister(state->ts_adev) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
audiots_destroy(state);
return (DDI_SUCCESS);
}
static int
audiots_quiesce(dev_info_t *dip)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
if ((state = ddi_get_soft_state(audiots_statep, instance)) == NULL) {
return (DDI_FAILURE);
}
audiots_stop_everything(state);
return (DDI_SUCCESS);
}
static void
audiots_power_up(audiots_state_t *state)
{
ddi_acc_handle_t pcih = state->ts_pcih;
uint8_t ptr;
uint16_t pmcsr;
if ((pci_config_get16(pcih, PCI_CONF_STAT) & PCI_STAT_CAP) == 0) {
return;
}
ptr = pci_config_get8(pcih, PCI_CONF_CAP_PTR);
for (;;) {
if (ptr == PCI_CAP_NEXT_PTR_NULL) {
return;
}
if (pci_config_get8(pcih, ptr + PCI_CAP_ID) == PCI_CAP_ID_PM) {
break;
}
ptr = pci_config_get8(pcih, ptr + PCI_CAP_NEXT_PTR);
}
ptr += PCI_PMCSR;
pmcsr = pci_config_get16(pcih, ptr);
if ((pmcsr & PCI_PMCSR_STATE_MASK) != PCI_PMCSR_D0) {
pmcsr &= ~PCI_PMCSR_STATE_MASK;
pmcsr |= PCI_PMCSR_D0;
pci_config_put16(pcih, ptr, pmcsr);
}
delay(drv_usectohz(TS_20MS));
pmcsr = pci_config_get16(pcih, ptr);
pci_config_put16(pcih, ptr, pmcsr | PCI_PMCSR_PME_STAT);
}
static void
audiots_chip_init(audiots_state_t *state)
{
ddi_acc_handle_t handle = state->ts_acch;
audiots_regs_t *regs = state->ts_regs;
int str;
ddi_put32(handle, ®s->aud_regs.ap_stop, TS_ALL_DMA_ENGINES);
ddi_put32(handle, ®s->aud_regs.ap_ainten, TS_ALL_DMA_OFF);
ddi_put32(handle, ®s->aud_regs.ap_volume, 0x0);
ddi_put32(handle, ®s->aud_regs.ap_cir_gc, AP_CIR_GC_ENDLP_IE);
for (str = 0; str < TS_MAX_HW_CHANNELS; str++) {
ddi_put16(handle,
®s->aud_ram[str].eram.eram_gvsel_pan_vol,
(ERAM_WAVE_VOL|ERAM_PAN_LEFT|ERAM_PAN_0dB|
ERAM_VOL_MAX_ATTEN));
ddi_put16(handle,
®s->aud_ram[str].eram.eram_gvsel_pan_vol,
(ERAM_WAVE_VOL|ERAM_PAN_RIGHT|ERAM_PAN_0dB|
ERAM_VOL_MAX_ATTEN));
ddi_put32(handle, ®s->aud_ram[str].eram.eram_ebuf1,
ERAM_EBUF_STILL);
ddi_put32(handle, ®s->aud_ram[str].eram.eram_ebuf2,
ERAM_EBUF_STILL);
ddi_put16(handle, ®s->aud_ram[str].aram.aram_delta,
1 << TS_SRC_SHIFT);
ddi_put16(handle, ®s->aud_ram[str].eram.eram_ctrl_ec,
ERAM_16_BITS | ERAM_STEREO | ERAM_LOOP_MODE |
ERAM_SIGNED_PCM);
}
OR_SET_WORD(handle, &state->ts_regs->aud_regs.ap_global_control,
(AP_CLOGAL_CTRL_E_PCMIN_CH31|AP_CLOGAL_CTRL_PCM_OUT_AC97|
AP_CLOGAL_CTRL_MMC_FROM_MIXER|AP_CLOGAL_CTRL_PCM_OUT_TO_AC97));
OR_SET_WORD(handle, &state->ts_regs->aud_regs.ap_sctrl,
AP_SCTRL_WRST_CODEC);
drv_usecwait(2);
AND_SET_WORD(handle, &state->ts_regs->aud_regs.ap_sctrl,
~AP_SCTRL_WRST_CODEC);
audiots_set_ac97(state, AC97_RESET_REGISTER, 0);
int i = TS_WAIT_CNT;
while ((audiots_get_ac97(state, AC97_POWERDOWN_CTRL_STAT_REGISTER) &
PCSR_POWERD_UP) != PCSR_POWERD_UP && i--) {
drv_usecwait(1);
}
}
static uint16_t
audiots_get_ac97(void *arg, uint8_t reg)
{
audiots_state_t *state = arg;
ddi_acc_handle_t handle = state->ts_acch;
uint16_t *data;
int count;
int delay;
uint16_t first;
uint16_t next;
if (state->ts_revid == AC_REV_ID1) {
data = &state->ts_regs->aud_regs.ap_acrd_35D_data;
} else {
data = &state->ts_regs->aud_regs.ap_acrdwr_data;
}
reg &= AP_ACRD_INDEX_MASK;
for (count = TS_LOOP_CNT; count--; ) {
if ((first = audiots_read_ac97(state, reg)) != 0) {
next = first;
break;
}
delay = TS_DELAY_CNT;
while (delay--) {
(void) ddi_get16(handle, data);
}
if ((next = audiots_read_ac97(state, reg)) != 0) {
break;
}
}
drv_usecwait(TS_20US);
return (next);
}
static int
audiots_init_state(audiots_state_t *state, dev_info_t *dip)
{
state->ts_ac97 = ac97_alloc(dip, audiots_get_ac97,
audiots_set_ac97, state);
if (state->ts_ac97 == NULL) {
return (DDI_FAILURE);
}
state->ts_dip = dip;
for (int i = 0; i < TS_NUM_PORTS; i++) {
if (audiots_alloc_port(state, i) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static int
audiots_map_regs(dev_info_t *dip, audiots_state_t *state)
{
char rev[16];
char *name;
if (pci_config_setup(dip, &state->ts_pcih) != DDI_SUCCESS) {
audio_dev_warn(state->ts_adev,
"unable to map PCI configuration space");
return (DDI_FAILURE);
}
state->ts_devid =
(pci_config_get16(state->ts_pcih, PCI_CONF_VENID) << 16) |
pci_config_get16(state->ts_pcih, PCI_CONF_DEVID);
state->ts_revid = pci_config_get8(state->ts_pcih, PCI_CONF_REVID);
if (ddi_regs_map_setup(dip, TS_MEM_MAPPED_REGS,
(caddr_t *)&state->ts_regs, 0, 0, &ts_regs_attr, &state->ts_acch) !=
DDI_SUCCESS) {
audio_dev_warn(state->ts_adev,
"unable to map PCI device registers");
return (DDI_FAILURE);
}
switch (state->ts_devid) {
case 0x10b95451:
name = "ALI M5451";
break;
default:
name = "audiots";
break;
}
(void) snprintf(rev, sizeof (rev), "Rev %x", state->ts_revid);
audio_dev_set_description(state->ts_adev, name);
audio_dev_set_version(state->ts_adev, rev);
return (DDI_SUCCESS);
}
int
audiots_alloc_port(audiots_state_t *state, int num)
{
audiots_port_t *port;
dev_info_t *dip = state->ts_dip;
audio_dev_t *adev = state->ts_adev;
int dir;
unsigned caps;
ddi_dma_cookie_t cookie;
unsigned count;
int rc;
ddi_acc_handle_t regsh = state->ts_acch;
uint32_t *gcptr = &state->ts_regs->aud_regs.ap_cir_gc;
port = kmem_zalloc(sizeof (*port), KM_SLEEP);
state->ts_ports[num] = port;
port->tp_num = num;
port->tp_state = state;
port->tp_rate = TS_RATE;
if (num == TS_INPUT_PORT) {
dir = DDI_DMA_READ;
caps = ENGINE_INPUT_CAP;
port->tp_dma_stream = 31;
port->tp_sync_dir = DDI_DMA_SYNC_FORKERNEL;
} else {
dir = DDI_DMA_WRITE;
caps = ENGINE_OUTPUT_CAP;
port->tp_dma_stream = 0;
port->tp_sync_dir = DDI_DMA_SYNC_FORDEV;
}
port->tp_dma_mask = (1U << port->tp_dma_stream);
port->tp_nframes = 4096;
port->tp_size = port->tp_nframes * TS_FRAMESZ;
rc = ddi_dma_alloc_handle(dip, &audiots_attr, DDI_DMA_SLEEP,
NULL, &port->tp_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->tp_dmah, port->tp_size, &ts_acc_attr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->tp_kaddr,
&port->tp_size, &port->tp_acch);
if (rc == DDI_FAILURE) {
audio_dev_warn(adev, "dma_mem_alloc failed");
return (DDI_FAILURE);
}
rc = ddi_dma_addr_bind_handle(port->tp_dmah, NULL,
port->tp_kaddr, port->tp_size, dir|DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, NULL, &cookie, &count);
if (rc != DDI_DMA_MAPPED) {
audio_dev_warn(adev,
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
ASSERT(count == 1);
port->tp_paddr = cookie.dmac_address;
if ((unsigned)port->tp_paddr & 0x80000000U) {
ddi_put32(regsh, gcptr,
ddi_get32(regsh, gcptr) | AP_CIR_GC_SYS_MEM_4G_ENABLE);
} else {
ddi_put32(regsh, gcptr,
ddi_get32(regsh, gcptr) & ~(AP_CIR_GC_SYS_MEM_4G_ENABLE));
}
port->tp_engine = audio_engine_alloc(&audiots_engine_ops, caps);
if (port->tp_engine == NULL) {
audio_dev_warn(adev, "audio_engine_alloc failed");
return (DDI_FAILURE);
}
audio_engine_set_private(port->tp_engine, port);
audio_dev_add_engine(adev, port->tp_engine);
return (DDI_SUCCESS);
}
static uint16_t
audiots_read_ac97(audiots_state_t *state, int reg)
{
ddi_acc_handle_t acch = state->ts_acch;
uint16_t *addr;
uint16_t *data;
uint32_t *stimer = &state->ts_regs->aud_regs.ap_stimer;
uint32_t chk1;
uint32_t chk2;
int resets = 0;
int i;
if (state->ts_revid == AC_REV_ID1) {
addr = &state->ts_regs->aud_regs.ap_acrd_35D_reg;
data = &state->ts_regs->aud_regs.ap_acrd_35D_data;
} else {
addr = &state->ts_regs->aud_regs.ap_acrdwr_reg;
data = &state->ts_regs->aud_regs.ap_acrdwr_data;
}
first_read:
for (i = 0; i < TS_READ_TRIES; i++) {
if (!(ddi_get16(acch, addr) & AP_ACRD_R_READ_BUSY)) {
break;
}
drv_usecwait(1);
}
if (i >= TS_READ_TRIES) {
if (resets < TS_RESET_TRIES) {
drv_usecwait(TS_20US);
ddi_put16(acch, addr, TS_SB_RESET);
resets++;
goto first_read;
} else {
state->ts_flags |= TS_AUDIO_READ_FAILED;
if (!(state->ts_flags & TS_READ_FAILURE_PRINTED)) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_LOST, DDI_DEVICE_FAULT,
"Unable to communicate with AC97 CODEC");
audio_dev_warn(state->ts_adev,
"The audio AC97 register has timed out.");
audio_dev_warn(state->ts_adev,
"Audio is now disabled.");
audio_dev_warn(state->ts_adev,
"Please reboot to restore audio.");
state->ts_flags |= TS_READ_FAILURE_PRINTED;
}
}
return (0);
}
ddi_put16(acch, addr, (reg|AP_ACRD_W_PRIMARY_CODEC|
AP_ACRD_W_READ_MIXER_REG|AP_ACRD_W_AUDIO_READ_REQ&
(~AP_ACWR_W_SELECT_WRITE)));
chk1 = ddi_get32(acch, stimer);
chk2 = ddi_get32(acch, stimer);
i = TS_WAIT_CNT;
while (chk1 == chk2 && i) {
chk2 = ddi_get32(acch, stimer);
i--;
}
OR_SET_SHORT(acch, addr, AP_ACRD_W_READ_MIXER_REG);
resets = 0;
second_read:
for (i = 0; i < TS_READ_TRIES; i++) {
if (!(ddi_get16(acch, addr) & AP_ACRD_R_READ_BUSY)) {
break;
}
drv_usecwait(1);
}
if (i >= TS_READ_TRIES) {
if (resets < TS_RESET_TRIES) {
drv_usecwait(TS_20US);
ddi_put16(acch, addr, TS_SB_RESET);
resets++;
goto second_read;
} else {
state->ts_flags |= TS_AUDIO_READ_FAILED;
if (!(state->ts_flags & TS_READ_FAILURE_PRINTED)) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_LOST, DDI_DEVICE_FAULT,
"Unable to communicate with AC97 CODEC");
audio_dev_warn(state->ts_adev,
"The audio AC97 register has timed out.");
audio_dev_warn(state->ts_adev,
"Audio is now disabled.");
audio_dev_warn(state->ts_adev,
"Please reboot to restore audio.");
state->ts_flags |= TS_READ_FAILURE_PRINTED;
}
}
return (0);
}
return (ddi_get16(acch, data));
}
static void
audiots_set_ac97(void *arg, uint8_t reg8, uint16_t data)
{
audiots_state_t *state = arg;
ddi_acc_handle_t handle = state->ts_acch;
uint16_t *data_addr = &state->ts_regs->aud_regs.ap_acrdwr_data;
uint16_t *reg_addr = &state->ts_regs->aud_regs.ap_acrdwr_reg;
int count;
int i;
uint16_t tmp_short;
uint16_t reg = reg8;
reg &= AP_ACWR_INDEX_MASK;
if (state->ts_revid == AC_REV_ID1) {
reg |= AP_ACWR_W_PRIMARY_CODEC|AP_ACWR_W_WRITE_MIXER_REG;
} else {
reg |= AP_ACWR_W_PRIMARY_CODEC|AP_ACWR_W_WRITE_MIXER_REG|
AP_ACWR_W_SELECT_WRITE;
}
for (count = TS_LOOP_CNT; count--; ) {
for (i = 0; i < TS_WAIT_CNT; i++) {
if (!(ddi_get16(handle, reg_addr) &
AP_ACWR_R_WRITE_BUSY)) {
ddi_put16(handle, reg_addr, reg);
ddi_put16(handle, data_addr, data);
break;
}
}
if (i >= TS_WAIT_CNT) {
continue;
}
for (i = 0; i < TS_WAIT_CNT; i++) {
if (!(ddi_get16(handle, reg_addr) &
AP_ACWR_R_WRITE_BUSY)) {
break;
}
}
tmp_short = audiots_get_ac97(state, reg8);
if (data == tmp_short) {
return;
}
}
}
static int
audiots_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
{
audiots_port_t *port = arg;
_NOTE(ARGUNUSED(flag));
port->tp_count = 0;
port->tp_cso = 0;
*nframesp = port->tp_nframes;
*bufp = port->tp_kaddr;
return (0);
}
static void
audiots_close(void *arg)
{
_NOTE(ARGUNUSED(arg));
}
static void
audiots_stop(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_stop,
port->tp_dma_mask);
}
static int
audiots_start(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
ddi_acc_handle_t handle = state->ts_acch;
audiots_regs_t *regs = state->ts_regs;
audiots_aram_t *aram;
audiots_eram_t *eram;
unsigned delta;
uint16_t ctrl;
uint16_t gvsel;
uint16_t eso;
aram = ®s->aud_ram[port->tp_dma_stream].aram;
eram = ®s->aud_ram[port->tp_dma_stream].eram;
port->tp_cso = 0;
gvsel = ERAM_WAVE_VOL | ERAM_PAN_0dB | ERAM_VOL_DEFAULT;
ctrl = ERAM_16_BITS | ERAM_STEREO | ERAM_LOOP_MODE | ERAM_SIGNED_PCM;
delta = (port->tp_rate << TS_SRC_SHIFT) / TS_RATE;
if (port->tp_num == TS_INPUT_PORT) {
delta = (TS_RATE << TS_SRC_SHIFT) / port->tp_rate;
}
eso = port->tp_nframes - 1;
ddi_put16(handle, &aram->aram_delta, (uint16_t)delta);
ddi_put16(handle, &eram->eram_ctrl_ec, ctrl);
ddi_put16(handle, &eram->eram_gvsel_pan_vol, gvsel);
ddi_put16(handle, &aram->aram_alpha_fms, 0x0);
ddi_put16(handle, &aram->aram_cso, 0x0);
ddi_put32(handle, &aram->aram_cptr_lba,
port->tp_paddr & ARAM_LBA_MASK);
ddi_put16(handle, &aram->aram_eso, eso);
ddi_put32(handle, ®s->aud_regs.ap_stop, port->tp_dma_mask);
ddi_put32(handle, ®s->aud_regs.ap_start, port->tp_dma_mask);
return (0);
}
static void
audiots_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
{
_NOTE(ARGUNUSED(arg));
*offset = chan;
*incr = 2;
}
static int
audiots_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (AUDIO_FORMAT_S16_LE);
}
static int
audiots_channels(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (2);
}
static int
audiots_rate(void *arg)
{
audiots_port_t *port = arg;
return (port->tp_rate);
}
static uint64_t
audiots_count(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
uint64_t val;
uint16_t cso;
unsigned n;
cso = ddi_get16(state->ts_acch,
&state->ts_regs->aud_ram[port->tp_dma_stream].aram.aram_cso);
n = (cso >= port->tp_cso) ?
cso - port->tp_cso :
cso + port->tp_nframes - port->tp_cso;
port->tp_cso = cso;
port->tp_count += n;
val = port->tp_count;
return (val);
}
static void
audiots_sync(void *arg, unsigned nframes)
{
audiots_port_t *port = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(port->tp_dmah, 0, 0, port->tp_sync_dir);
}
static void
audiots_stop_everything(audiots_state_t *state)
{
if (state->ts_acch == NULL)
return;
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_ainten,
TS_ALL_DMA_OFF);
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_stop,
TS_ALL_DMA_ENGINES);
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_aint,
TS_ALL_DMA_ENGINES);
}
void
audiots_free_port(audiots_port_t *port)
{
if (port == NULL)
return;
if (port->tp_engine) {
audio_dev_remove_engine(port->tp_state->ts_adev,
port->tp_engine);
audio_engine_free(port->tp_engine);
}
if (port->tp_paddr) {
(void) ddi_dma_unbind_handle(port->tp_dmah);
}
if (port->tp_acch) {
ddi_dma_mem_free(&port->tp_acch);
}
if (port->tp_dmah) {
ddi_dma_free_handle(&port->tp_dmah);
}
kmem_free(port, sizeof (*port));
}
void
audiots_destroy(audiots_state_t *state)
{
audiots_stop_everything(state);
for (int i = 0; i < TS_NUM_PORTS; i++)
audiots_free_port(state->ts_ports[i]);
if (state->ts_acch)
ddi_regs_map_free(&state->ts_acch);
if (state->ts_pcih)
pci_config_teardown(&state->ts_pcih);
if (state->ts_ac97)
ac97_free(state->ts_ac97);
if (state->ts_adev)
audio_dev_free(state->ts_adev);
ddi_soft_state_free(audiots_statep, ddi_get_instance(state->ts_dip));
}