#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/stropts.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include "audio_4231.h"
static int audiocs_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int audiocs_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static int audiocs_ddi_power(dev_info_t *, int, int);
static int audiocs_open(void *, int, unsigned *, caddr_t *);
static void audiocs_close(void *);
static int audiocs_start(void *);
static void audiocs_stop(void *);
static int audiocs_format(void *);
static int audiocs_channels(void *);
static int audiocs_rate(void *);
static uint64_t audiocs_count(void *);
static void audiocs_sync(void *, unsigned);
static int audiocs_get_value(void *, uint64_t *);
static int audiocs_set_ogain(void *, uint64_t);
static int audiocs_set_igain(void *, uint64_t);
static int audiocs_set_mgain(void *, uint64_t);
static int audiocs_set_inputs(void *, uint64_t);
static int audiocs_set_outputs(void *, uint64_t);
static int audiocs_set_micboost(void *, uint64_t);
static int audiocs_resume(dev_info_t *);
static int audiocs_attach(dev_info_t *);
static int audiocs_detach(dev_info_t *);
static int audiocs_suspend(dev_info_t *);
static void audiocs_destroy(CS_state_t *);
static int audiocs_init_state(CS_state_t *);
static int audiocs_chip_init(CS_state_t *);
static int audiocs_alloc_engine(CS_state_t *, int);
static void audiocs_free_engine(CS_engine_t *);
static void audiocs_get_ports(CS_state_t *);
static void audiocs_configure_input(CS_state_t *);
static void audiocs_configure_output(CS_state_t *);
static CS_ctrl_t *audiocs_alloc_ctrl(CS_state_t *, uint32_t, uint64_t);
static void audiocs_free_ctrl(CS_ctrl_t *);
static int audiocs_add_controls(CS_state_t *);
static void audiocs_del_controls(CS_state_t *);
static void audiocs_power_up(CS_state_t *);
static void audiocs_power_down(CS_state_t *);
static int audiocs_poll_ready(CS_state_t *);
#ifdef DEBUG
static void audiocs_put_index(CS_state_t *, uint8_t, uint8_t, int);
static void audiocs_sel_index(CS_state_t *, uint8_t, int);
#define SELIDX(s, idx) audiocs_sel_index(s, idx, __LINE__)
#define PUTIDX(s, val, mask) audiocs_put_index(s, val, mask, __LINE__)
#else
static void audiocs_put_index(CS_state_t *, uint8_t, uint8_t);
static void audiocs_sel_index(CS_state_t *, uint8_t);
#define SELIDX(s, idx) audiocs_sel_index(s, idx)
#define PUTIDX(s, val, mask) audiocs_put_index(s, val, mask)
#endif
#define GETIDX(s) ddi_get8((handle), &CS4231_IDR)
#define ORIDX(s, val, mask) \
PUTIDX(s, \
(ddi_get8((handle), &CS4231_IDR) | (uint8_t)(val)), \
(uint8_t)(mask))
#define ANDIDX(s, val, mask) \
PUTIDX(s, (ddi_get8((handle), &CS4231_IDR) & (uint8_t)(val)), \
(uint8_t)(mask))
static audio_engine_ops_t audiocs_engine_ops = {
AUDIO_ENGINE_VERSION,
audiocs_open,
audiocs_close,
audiocs_start,
audiocs_stop,
audiocs_count,
audiocs_format,
audiocs_channels,
audiocs_rate,
audiocs_sync,
NULL,
NULL,
NULL,
};
#define OUTPUT_SPEAKER 0
#define OUTPUT_HEADPHONES 1
#define OUTPUT_LINEOUT 2
static const char *audiocs_outputs[] = {
AUDIO_PORT_SPEAKER,
AUDIO_PORT_HEADPHONES,
AUDIO_PORT_LINEOUT,
NULL
};
#define INPUT_MIC 0
#define INPUT_LINEIN 1
#define INPUT_STEREOMIX 2
#define INPUT_CD 3
static const char *audiocs_inputs[] = {
AUDIO_PORT_MIC,
AUDIO_PORT_LINEIN,
AUDIO_PORT_STEREOMIX,
AUDIO_PORT_CD,
NULL
};
static uint8_t cs4231_atten[] = {
0x3f, 0x3e, 0x3d, 0x3c, 0x3b,
0x3a, 0x39, 0x38, 0x37, 0x36,
0x35, 0x34, 0x33, 0x32, 0x31,
0x30, 0x2f, 0x2e, 0x2d, 0x2c,
0x2b, 0x2a, 0x29, 0x29, 0x28,
0x28, 0x27, 0x27, 0x26, 0x26,
0x25, 0x25, 0x24, 0x24, 0x23,
0x23, 0x22, 0x22, 0x21, 0x21,
0x20, 0x20, 0x1f, 0x1f, 0x1f,
0x1e, 0x1e, 0x1e, 0x1d, 0x1d,
0x1d, 0x1c, 0x1c, 0x1c, 0x1b,
0x1b, 0x1b, 0x1a, 0x1a, 0x1a,
0x1a, 0x19, 0x19, 0x19, 0x19,
0x18, 0x18, 0x18, 0x18, 0x17,
0x17, 0x17, 0x17, 0x16, 0x16,
0x16, 0x16, 0x16, 0x15, 0x15,
0x15, 0x15, 0x15, 0x14, 0x14,
0x14, 0x14, 0x14, 0x13, 0x13,
0x13, 0x13, 0x13, 0x12, 0x12,
0x12, 0x12, 0x12, 0x12, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11,
0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0e, 0x0e, 0x0e,
0x0e, 0x0e, 0x0e, 0x0e, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x0c, 0x0c, 0x0c, 0x0c,
0x0c, 0x0c, 0x0c, 0x0b, 0x0b,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
0x0b, 0x0a, 0x0a, 0x0a, 0x0a,
0x0a, 0x0a, 0x0a, 0x0a, 0x09,
0x09, 0x09, 0x09, 0x09, 0x09,
0x09, 0x09, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07,
0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03,
0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00
};
static struct dev_ops audiocs_dev_ops = {
DEVO_REV,
0,
NULL,
nulldev,
nulldev,
audiocs_ddi_attach,
audiocs_ddi_detach,
nodev,
NULL,
NULL,
audiocs_ddi_power,
ddi_quiesce_not_supported,
};
static struct modldrv audiocs_modldrv = {
&mod_driverops,
CS4231_MOD_NAME,
&audiocs_dev_ops
};
static struct modlinkage audiocs_modlinkage = {
MODREV_1,
(void *)&audiocs_modldrv,
NULL
};
int
_init(void)
{
int rv;
audio_init_ops(&audiocs_dev_ops, CS4231_NAME);
if ((rv = mod_install(&audiocs_modlinkage)) != 0) {
audio_fini_ops(&audiocs_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&audiocs_modlinkage)) == 0) {
audio_fini_ops(&audiocs_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&audiocs_modlinkage, modinfop));
}
static int
audiocs_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
return (audiocs_attach(dip));
case DDI_RESUME:
return (audiocs_resume(dip));
default:
return (DDI_FAILURE);
}
}
static int
audiocs_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
return (audiocs_detach(dip));
case DDI_SUSPEND:
return (audiocs_suspend(dip));
default:
return (DDI_FAILURE);
}
}
static int
audiocs_ddi_power(dev_info_t *dip, int component, int level)
{
CS_state_t *state;
if (component != CS4231_COMPONENT)
return (DDI_FAILURE);
state = ddi_get_driver_private(dip);
ASSERT(!mutex_owned(&state->cs_lock));
mutex_enter(&state->cs_lock);
if (!state->cs_suspended) {
if (level == CS4231_PWR_OFF && state->cs_powered) {
audiocs_power_down(state);
state->cs_powered = B_FALSE;
} else if (level == CS4231_PWR_ON && !state->cs_powered) {
audiocs_power_up(state);
state->cs_powered = B_TRUE;
}
}
mutex_exit(&state->cs_lock);
ASSERT(!mutex_owned(&state->cs_lock));
return (DDI_SUCCESS);
}
static void
audiocs_destroy(CS_state_t *state)
{
if (state == NULL)
return;
for (int i = CS4231_PLAY; i <= CS4231_REC; i++) {
audiocs_free_engine(state->cs_engines[i]);
}
audiocs_del_controls(state);
if (state->cs_adev) {
audio_dev_free(state->cs_adev);
}
CS4231_DMA_UNMAP_REGS(state);
mutex_destroy(&state->cs_lock);
kmem_free(state, sizeof (*state));
}
static int
audiocs_attach(dev_info_t *dip)
{
CS_state_t *state;
audio_dev_t *adev;
state = kmem_zalloc(sizeof (*state), KM_SLEEP);
state->cs_dip = dip;
ddi_set_driver_private(dip, state);
mutex_init(&state->cs_lock, NULL, MUTEX_DRIVER, NULL);
if ((state->cs_adev = audio_dev_alloc(dip, 0)) == NULL) {
goto error;
}
adev = state->cs_adev;
audio_dev_set_description(adev, CS_DEV_CONFIG_ONBRD1);
audio_dev_add_info(adev, "Legacy codec: Crystal Semiconductor CS4231");
if ((audiocs_init_state(state)) == DDI_FAILURE) {
audio_dev_warn(adev, "init_state() failed");
goto error;
}
mutex_enter(&state->cs_lock);
if ((audiocs_chip_init(state)) == DDI_FAILURE) {
mutex_exit(&state->cs_lock);
audio_dev_warn(adev, "chip_init() failed");
goto error;
}
state->cs_powered = B_TRUE;
mutex_exit(&state->cs_lock);
if (audio_dev_register(state->cs_adev) != DDI_SUCCESS) {
audio_dev_warn(state->cs_adev, "unable to register audio dev");
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
audiocs_destroy(state);
return (DDI_FAILURE);
}
static int
audiocs_resume(dev_info_t *dip)
{
CS_state_t *state;
audio_dev_t *adev;
state = ddi_get_driver_private(dip);
adev = state->cs_adev;
ASSERT(dip == state->cs_dip);
ASSERT(!mutex_owned(&state->cs_lock));
(void) pm_busy_component(state->cs_dip, CS4231_COMPONENT);
audiocs_power_up(state);
state->cs_powered = B_TRUE;
mutex_enter(&state->cs_lock);
if ((audiocs_chip_init(state)) == DDI_FAILURE) {
mutex_exit(&state->cs_lock);
audio_dev_warn(adev, "chip_init() failed");
(void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);
return (DDI_FAILURE);
}
state->cs_suspended = B_FALSE;
mutex_exit(&state->cs_lock);
(void) pm_raise_power(dip, CS4231_COMPONENT, CS4231_PWR_ON);
(void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);
audio_dev_resume(state->cs_adev);
return (DDI_SUCCESS);
}
static int
audiocs_detach(dev_info_t *dip)
{
CS_state_t *state;
audio_dev_t *adev;
ddi_acc_handle_t handle;
state = ddi_get_driver_private(dip);
handle = CODEC_HANDLE;
adev = state->cs_adev;
if (audio_dev_unregister(adev) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if (state->cs_powered) {
SELIDX(state, INTC_REG);
ANDIDX(state, ~(INTC_PEN|INTC_CEN), INTC_VALID_MASK);
CS4231_DMA_RESET(state);
(void) pm_lower_power(dip, CS4231_COMPONENT, CS4231_PWR_OFF);
}
audiocs_destroy(state);
return (DDI_SUCCESS);
}
static int
audiocs_suspend(dev_info_t *dip)
{
CS_state_t *state;
state = ddi_get_driver_private(dip);
mutex_enter(&state->cs_lock);
ASSERT(!state->cs_suspended);
audio_dev_suspend(state->cs_adev);
if (state->cs_powered) {
audiocs_power_down(state);
state->cs_powered = B_FALSE;
}
state->cs_suspended = B_TRUE;
mutex_exit(&state->cs_lock);
return (DDI_SUCCESS);
}
#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
#define MONVOL (MONCTL | AUDIO_CTRL_FLAG_MONVOL)
static CS_ctrl_t *
audiocs_alloc_ctrl(CS_state_t *state, uint32_t num, uint64_t val)
{
audio_ctrl_desc_t desc;
audio_ctrl_wr_t fn;
CS_ctrl_t *cc;
cc = kmem_zalloc(sizeof (*cc), KM_SLEEP);
cc->cc_state = state;
cc->cc_num = num;
bzero(&desc, sizeof (desc));
switch (num) {
case CTL_VOLUME:
desc.acd_name = AUDIO_CTRL_ID_VOLUME;
desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = PCMVOL;
fn = audiocs_set_ogain;
break;
case CTL_IGAIN:
desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = RECVOL;
fn = audiocs_set_igain;
break;
case CTL_MGAIN:
desc.acd_name = AUDIO_CTRL_ID_MONGAIN;
desc.acd_type = AUDIO_CTRL_TYPE_MONO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = MONVOL;
fn = audiocs_set_mgain;
break;
case CTL_INPUTS:
desc.acd_name = AUDIO_CTRL_ID_RECSRC;
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_minvalue = state->cs_imask;
desc.acd_maxvalue = state->cs_imask;
desc.acd_flags = RECCTL;
for (int i = 0; audiocs_inputs[i]; i++) {
desc.acd_enum[i] = audiocs_inputs[i];
}
fn = audiocs_set_inputs;
break;
case CTL_OUTPUTS:
desc.acd_name = AUDIO_CTRL_ID_OUTPUTS;
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_minvalue = state->cs_omod;
desc.acd_maxvalue = state->cs_omask;
desc.acd_flags = PLAYCTL | AUDIO_CTRL_FLAG_MULTI;
for (int i = 0; audiocs_outputs[i]; i++) {
desc.acd_enum[i] = audiocs_outputs[i];
}
fn = audiocs_set_outputs;
break;
case CTL_MICBOOST:
desc.acd_name = AUDIO_CTRL_ID_MICBOOST;
desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 1;
desc.acd_flags = RECCTL;
fn = audiocs_set_micboost;
break;
}
cc->cc_val = val;
cc->cc_ctrl = audio_dev_add_control(state->cs_adev, &desc,
audiocs_get_value, fn, cc);
return (cc);
}
static void
audiocs_free_ctrl(CS_ctrl_t *cc)
{
if (cc == NULL)
return;
if (cc->cc_ctrl)
audio_dev_del_control(cc->cc_ctrl);
kmem_free(cc, sizeof (*cc));
}
static int
audiocs_add_controls(CS_state_t *state)
{
#define ADD_CTRL(CTL, ID, VAL) \
state->cs_##CTL = audiocs_alloc_ctrl(state, ID, VAL); \
if (state->cs_##CTL == NULL) { \
audio_dev_warn(state->cs_adev, \
"unable to allocate %s control", #ID); \
return (DDI_FAILURE); \
}
ADD_CTRL(ogain, CTL_VOLUME, 0x4b4b);
ADD_CTRL(igain, CTL_IGAIN, 0x3232);
ADD_CTRL(mgain, CTL_MGAIN, 0);
ADD_CTRL(micboost, CTL_MICBOOST, 0);
ADD_CTRL(outputs, CTL_OUTPUTS, (state->cs_omask & ~state->cs_omod) |
(1U << OUTPUT_SPEAKER));
ADD_CTRL(inputs, CTL_INPUTS, (1U << INPUT_MIC));
return (DDI_SUCCESS);
}
void
audiocs_del_controls(CS_state_t *state)
{
audiocs_free_ctrl(state->cs_ogain);
audiocs_free_ctrl(state->cs_igain);
audiocs_free_ctrl(state->cs_mgain);
audiocs_free_ctrl(state->cs_micboost);
audiocs_free_ctrl(state->cs_inputs);
audiocs_free_ctrl(state->cs_outputs);
}
static int
audiocs_chip_init(CS_state_t *state)
{
ddi_acc_handle_t handle = CODEC_HANDLE;
CS4231_DMA_POWER(state, CS4231_PWR_ON);
CS4231_DMA_RESET(state);
if (audiocs_poll_ready(state) == DDI_FAILURE) {
return (DDI_FAILURE);
}
SELIDX(state, MID_REG);
ddi_put8(handle, &CS4231_IDR, MID_MODE2);
SELIDX(state, VID_REG);
if (ddi_get8(handle, &CS4231_IDR) & VID_A) {
state->cs_revA = B_TRUE;
} else {
state->cs_revA = B_FALSE;
}
SELIDX(state, LDACO_REG);
PUTIDX(state, LDACO_LDM | LDACO_MID_GAIN, LDAC0_VALID_MASK);
SELIDX(state, RDACO_REG);
PUTIDX(state, RDACO_RDM | RDACO_MID_GAIN, RDAC0_VALID_MASK);
SELIDX(state, LAUX1_REG);
PUTIDX(state, LAUX1_LX1M | LAUX1_UNITY_GAIN, LAUX1_VALID_MASK);
SELIDX(state, RAUX1_REG);
PUTIDX(state, RAUX1_RX1M | RAUX1_UNITY_GAIN, RAUX1_VALID_MASK);
SELIDX(state, LAUX2_REG);
PUTIDX(state, LAUX2_LX2M | LAUX2_UNITY_GAIN, LAUX2_VALID_MASK);
SELIDX(state, RAUX2_REG);
PUTIDX(state, RAUX2_RX2M | RAUX2_UNITY_GAIN, RAUX2_VALID_MASK);
SELIDX(state, LLIC_REG);
PUTIDX(state, LLIC_LLM | LLIC_UNITY_GAIN, LLIC_VALID_MASK);
SELIDX(state, RLIC_REG);
PUTIDX(state, RLIC_RLM | RLIC_UNITY_GAIN, RLIC_VALID_MASK);
SELIDX(state, FSDF_REG | IAR_MCE);
PUTIDX(state, FS_48000 | PDF_LINEAR16NE | PDF_STEREO, FSDF_VALID_MASK);
if (audiocs_poll_ready(state) == DDI_FAILURE) {
return (DDI_FAILURE);
}
SELIDX(state, CDF_REG | IAR_MCE);
PUTIDX(state, CDF_LINEAR16NE | CDF_STEREO, CDF_VALID_MASK);
if (audiocs_poll_ready(state) == DDI_FAILURE) {
return (DDI_FAILURE);
}
SELIDX(state, (INTC_REG | IAR_MCE));
PUTIDX(state, INTC_DDC | INTC_PDMA | INTC_CDMA, INTC_VALID_MASK);
if (audiocs_poll_ready(state) == DDI_FAILURE) {
return (DDI_FAILURE);
}
SELIDX(state, AFE1_REG);
PUTIDX(state, AFE1_OLB, AFE1_VALID_MASK);
SELIDX(state, AFE2_REG);
if (state->cs_revA) {
PUTIDX(state, AFE2_HPF, AFE2_VALID_MASK);
} else {
PUTIDX(state, 0, AFE2_VALID_MASK);
}
SELIDX(state, AFS_REG);
ddi_put8(handle, &CS4231_STATUS, (AFS_RESET_STATUS));
SELIDX(state, LDACO_REG);
ANDIDX(state, ~LDACO_LDM, LDAC0_VALID_MASK);
SELIDX(state, RDACO_REG);
ANDIDX(state, ~RDACO_RDM, RDAC0_VALID_MASK);
SELIDX(state, MIOC_REG);
PUTIDX(state, MIOC_MIM, MIOC_VALID_MASK);
audiocs_configure_output(state);
audiocs_configure_input(state);
return (DDI_SUCCESS);
}
static int
audiocs_init_state(CS_state_t *state)
{
audio_dev_t *adev = state->cs_adev;
dev_info_t *dip = state->cs_dip;
char *prop_str;
char *pm_comp[] = {
"NAME=audiocs audio device",
"0=off",
"1=on" };
if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip,
"pm-components", pm_comp, 3) != DDI_PROP_SUCCESS) {
audio_dev_warn(adev, "couldn't create pm-components property");
return (DDI_FAILURE);
}
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"dma-model", &prop_str) == DDI_PROP_SUCCESS) {
if (strcmp(prop_str, "eb2dma") == 0) {
state->cs_dma_engine = EB2_DMA;
state->cs_dma_ops = &cs4231_eb2dma_ops;
} else {
state->cs_dma_engine = APC_DMA;
state->cs_dma_ops = &cs4231_apcdma_ops;
}
ddi_prop_free(prop_str);
} else {
state->cs_dma_engine = APC_DMA;
state->cs_dma_ops = &cs4231_apcdma_ops;
}
audiocs_get_ports(state);
if ((audiocs_alloc_engine(state, CS4231_PLAY) != DDI_SUCCESS) ||
(audiocs_alloc_engine(state, CS4231_REC) != DDI_SUCCESS)) {
return (DDI_FAILURE);
}
if (CS4231_DMA_MAP_REGS(state) == DDI_FAILURE) {
return (DDI_FAILURE);
}
if (audiocs_add_controls(state) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
state->cs_suspended = B_FALSE;
state->cs_powered = B_FALSE;
return (DDI_SUCCESS);
}
static void
audiocs_get_ports(CS_state_t *state)
{
dev_info_t *dip = state->cs_dip;
audio_dev_t *adev = state->cs_adev;
char *prop_str;
state->cs_omask = state->cs_omod =
(1U << OUTPUT_SPEAKER) |
(1U << OUTPUT_HEADPHONES) |
(1U << OUTPUT_LINEOUT);
state->cs_imask =
(1U << INPUT_MIC) |
(1U << INPUT_LINEIN) |
(1U << INPUT_STEREOMIX);
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"model", &prop_str) == DDI_PROP_SUCCESS) {
if (strcmp(prop_str, "SUNW,CS4231h") == 0) {
audio_dev_set_version(adev, CS_DEV_VERSION_H);
state->cs_imask |= (1U << INPUT_CD);
state->cs_omod = (1U << OUTPUT_SPEAKER);
} else if (strcmp(prop_str, "SUNW,CS4231g") == 0) {
audio_dev_set_version(adev, CS_DEV_VERSION_G);
} else if (strcmp(prop_str, "SUNW,CS4231f") == 0) {
audio_dev_set_version(adev, CS_DEV_VERSION_F);
} else {
audio_dev_set_version(adev, prop_str);
audio_dev_warn(adev,
"unknown audio model: %s, some parts of "
"audio may not work correctly", prop_str);
}
ddi_prop_free(prop_str);
} else {
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "audio-module", &prop_str) ==
DDI_PROP_SUCCESS) {
switch (*prop_str) {
case 'Q':
audio_dev_set_version(adev, CS_DEV_VERSION_G);
break;
case 'P':
audio_dev_set_version(adev, CS_DEV_VERSION_F);
break;
default:
audio_dev_set_version(adev, prop_str);
audio_dev_warn(adev,
"unknown audio module: %s, some "
"parts of audio may not work correctly",
prop_str);
break;
}
ddi_prop_free(prop_str);
} else {
if (ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "internal-loopback", B_FALSE)) {
if (state->cs_dma_engine == EB2_DMA) {
audio_dev_set_version(adev,
CS_DEV_VERSION_C);
} else {
audio_dev_set_version(adev,
CS_DEV_VERSION_B);
}
} else {
audio_dev_set_version(adev, CS_DEV_VERSION_A);
state->cs_imask |= (1U << INPUT_CD);
}
}
}
}
static void
audiocs_power_up(CS_state_t *state)
{
ddi_acc_handle_t handle = CODEC_HANDLE;
int i;
CS4231_DMA_POWER(state, CS4231_PWR_ON);
CS4231_DMA_RESET(state);
(void) audiocs_poll_ready(state);
SELIDX(state, MID_REG);
PUTIDX(state, state->cs_save[MID_REG], MID_VALID_MASK);
for (i = 0; i < CS4231_REGS; i++) {
SELIDX(state, (i | IAR_MCE));
ddi_put8(handle, &CS4231_IDR, state->cs_save[i]);
(void) audiocs_poll_ready(state);
}
SELIDX(state, 0);
}
static void
audiocs_power_down(CS_state_t *state)
{
ddi_acc_handle_t handle;
int i;
handle = state->cs_handles.cs_codec_hndl;
for (i = 0; i < CS4231_REGS; i++) {
SELIDX(state, i);
state->cs_save[i] = ddi_get8(handle, &CS4231_IDR);
}
CS4231_DMA_POWER(state, CS4231_PWR_OFF);
}
static void
audiocs_configure_input(CS_state_t *state)
{
uint8_t l, r;
uint64_t inputs;
uint64_t micboost;
ASSERT(mutex_owned(&state->cs_lock));
inputs = state->cs_inputs->cc_val;
micboost = state->cs_micboost->cc_val;
r = (state->cs_igain->cc_val & 0xff);
l = ((state->cs_igain->cc_val & 0xff00) >> 8);
l = (((uint32_t)l * 255) / 100) & 0xff;
r = (((uint32_t)r * 255) / 100) & 0xff;
l = l >> 4;
r = r >> 4;
if (inputs & (1U << INPUT_MIC)) {
l |= LADCI_LMIC;
r |= RADCI_RMIC;
}
if (inputs & (1U << INPUT_LINEIN)) {
l |= LADCI_LLINE;
r |= RADCI_RLINE;
}
if (inputs & (1U << INPUT_CD)) {
l |= LADCI_LAUX1;
r |= RADCI_RAUX1;
}
if (inputs & (1U << INPUT_STEREOMIX)) {
l |= LADCI_LLOOP;
r |= RADCI_RLOOP;
}
if (micboost) {
l |= LADCI_LMGE;
r |= RADCI_RMGE;
}
SELIDX(state, LADCI_REG);
PUTIDX(state, l, LADCI_VALID_MASK);
SELIDX(state, RADCI_REG);
PUTIDX(state, r, RADCI_VALID_MASK);
}
static void
audiocs_configure_output(CS_state_t *state)
{
uint64_t outputs;
uint8_t l, r;
uint8_t rmute, lmute;
uint8_t mgain;
ddi_acc_handle_t handle = CODEC_HANDLE;
rmute = lmute = 0;
ASSERT(mutex_owned(&state->cs_lock));
outputs = state->cs_outputs->cc_val;
SELIDX(state, MIOC_REG);
if (outputs & (1U << OUTPUT_SPEAKER)) {
ANDIDX(state, ~MIOC_MONO_SPKR_MUTE, MIOC_VALID_MASK);
} else {
ORIDX(state, MIOC_MONO_SPKR_MUTE, MIOC_VALID_MASK);
}
SELIDX(state, PC_REG);
if (outputs & (1U << OUTPUT_HEADPHONES)) {
ANDIDX(state, ~PC_HEADPHONE_MUTE, PC_VALID_MASK);
} else {
ORIDX(state, PC_HEADPHONE_MUTE, PC_VALID_MASK);
}
SELIDX(state, PC_REG);
if (outputs & (1U << OUTPUT_LINEOUT)) {
ANDIDX(state, ~PC_LINE_OUT_MUTE, PC_VALID_MASK);
} else {
ORIDX(state, PC_LINE_OUT_MUTE, PC_VALID_MASK);
}
mgain = cs4231_atten[((state->cs_mgain->cc_val * 255) / 100) & 0xff];
SELIDX(state, LC_REG);
if (mgain == 0) {
PUTIDX(state, LC_OFF, LC_VALID_MASK);
} else {
PUTIDX(state, (mgain << 2) | LC_LBE, LC_VALID_MASK);
}
l = ((state->cs_ogain->cc_val >> 8) & 0xff);
r = (state->cs_ogain->cc_val & 0xff);
if (l == 0) {
lmute = LDACO_LDM;
}
if (r == 0) {
rmute = RDACO_RDM;
}
l = cs4231_atten[(((uint32_t)l * 255) / 100) & 0xff] | lmute;
r = cs4231_atten[(((uint32_t)r * 255) / 100) & 0xff] | rmute;
SELIDX(state, LDACO_REG);
PUTIDX(state, l, LDAC0_VALID_MASK);
SELIDX(state, RDACO_REG);
PUTIDX(state, r, RDAC0_VALID_MASK);
}
static int
audiocs_get_value(void *arg, uint64_t *valp)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
mutex_enter(&state->cs_lock);
*valp = cc->cc_val;
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_ogain(void *arg, uint64_t val)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
if ((val & ~0xffff) ||
((val & 0xff) > 100) ||
(((val & 0xff00) >> 8) > 100))
return (EINVAL);
mutex_enter(&state->cs_lock);
cc->cc_val = val;
audiocs_configure_output(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_micboost(void *arg, uint64_t val)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
mutex_enter(&state->cs_lock);
cc->cc_val = val ? B_TRUE : B_FALSE;
audiocs_configure_input(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_igain(void *arg, uint64_t val)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
if ((val & ~0xffff) ||
((val & 0xff) > 100) ||
(((val & 0xff00) >> 8) > 100))
return (EINVAL);
mutex_enter(&state->cs_lock);
cc->cc_val = val;
audiocs_configure_input(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_inputs(void *arg, uint64_t val)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
if (val & ~(state->cs_imask))
return (EINVAL);
mutex_enter(&state->cs_lock);
cc->cc_val = val;
audiocs_configure_input(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_outputs(void *arg, uint64_t val)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
if ((val & ~(state->cs_omod)) !=
(state->cs_omask & ~state->cs_omod))
return (EINVAL);
mutex_enter(&state->cs_lock);
cc->cc_val = val;
audiocs_configure_output(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_set_mgain(void *arg, uint64_t gain)
{
CS_ctrl_t *cc = arg;
CS_state_t *state = cc->cc_state;
if (gain > 100)
return (EINVAL);
mutex_enter(&state->cs_lock);
cc->cc_val = gain;
audiocs_configure_output(state);
mutex_exit(&state->cs_lock);
return (0);
}
static int
audiocs_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
{
CS_engine_t *eng = arg;
CS_state_t *state = eng->ce_state;
dev_info_t *dip = state->cs_dip;
_NOTE(ARGUNUSED(flag));
(void) pm_busy_component(dip, CS4231_COMPONENT);
if (pm_raise_power(dip, CS4231_COMPONENT, CS4231_PWR_ON) ==
DDI_FAILURE) {
(void) pm_idle_component(dip, CS4231_COMPONENT);
audio_dev_warn(state->cs_adev, "power up failed");
}
eng->ce_count = 0;
*nframesp = CS4231_NFRAMES;
*bufp = eng->ce_kaddr;
return (0);
}
static void
audiocs_close(void *arg)
{
CS_engine_t *eng = arg;
CS_state_t *state = eng->ce_state;
(void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);
}
static void
audiocs_stop(void *arg)
{
CS_engine_t *eng = arg;
CS_state_t *state = eng->ce_state;
ddi_acc_handle_t handle = CODEC_HANDLE;
mutex_enter(&state->cs_lock);
CS4231_DMA_STOP(state, eng);
SELIDX(state, INTC_REG);
ANDIDX(state, ~(eng->ce_codec_en), INTC_VALID_MASK);
mutex_exit(&state->cs_lock);
}
static int
audiocs_start(void *arg)
{
CS_engine_t *eng = arg;
CS_state_t *state = eng->ce_state;
ddi_acc_handle_t handle = CODEC_HANDLE;
uint8_t mask;
uint8_t value;
uint8_t reg;
int rv;
mutex_enter(&state->cs_lock);
if (eng->ce_num == CS4231_PLAY) {
value = FS_48000 | PDF_STEREO | PDF_LINEAR16NE;
reg = FSDF_REG;
mask = FSDF_VALID_MASK;
} else {
value = CDF_STEREO | CDF_LINEAR16NE;
reg = CDF_REG;
mask = CDF_VALID_MASK;
}
eng->ce_curoff = 0;
eng->ce_curidx = 0;
SELIDX(state, reg | IAR_MCE);
PUTIDX(state, value, mask);
if (audiocs_poll_ready(state) != DDI_SUCCESS) {
rv = EIO;
} else if (CS4231_DMA_START(state, eng) != DDI_SUCCESS) {
rv = EIO;
} else {
SELIDX(state, INTC_REG);
ORIDX(state, eng->ce_codec_en, INTC_VALID_MASK);
rv = 0;
}
mutex_exit(&state->cs_lock);
return (rv);
}
static int
audiocs_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (AUDIO_FORMAT_S16_NE);
}
static int
audiocs_channels(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (2);
}
static int
audiocs_rate(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (48000);
}
static uint64_t
audiocs_count(void *arg)
{
CS_engine_t *eng = arg;
CS_state_t *state = eng->ce_state;
uint64_t val;
uint32_t off;
mutex_enter(&state->cs_lock);
off = CS4231_DMA_ADDR(state, eng);
ASSERT(off >= eng->ce_paddr);
off -= eng->ce_paddr;
if (off >= CS4231_BUFSZ) {
off = CS4231_BUFSZ - 4;
}
off /= 4;
val = (off >= eng->ce_curoff) ?
off - eng->ce_curoff :
off + CS4231_NFRAMES - eng->ce_curoff;
eng->ce_count += val;
eng->ce_curoff = off;
val = eng->ce_count;
CS4231_DMA_RELOAD(state, eng);
mutex_exit(&state->cs_lock);
return (val);
}
static void
audiocs_sync(void *arg, unsigned nframes)
{
CS_engine_t *eng = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(eng->ce_dmah, 0, 0, eng->ce_syncdir);
}
int
audiocs_alloc_engine(CS_state_t *state, int num)
{
unsigned caps;
int dir;
int rc;
audio_dev_t *adev;
dev_info_t *dip;
CS_engine_t *eng;
uint_t ccnt;
ddi_dma_cookie_t dmac;
size_t bufsz;
static ddi_device_acc_attr_t buf_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
adev = state->cs_adev;
dip = state->cs_dip;
eng = kmem_zalloc(sizeof (*eng), KM_SLEEP);
eng->ce_state = state;
eng->ce_started = B_FALSE;
eng->ce_num = num;
switch (num) {
case CS4231_REC:
dir = DDI_DMA_READ;
caps = ENGINE_INPUT_CAP;
eng->ce_syncdir = DDI_DMA_SYNC_FORKERNEL;
eng->ce_codec_en = INTC_CEN;
break;
case CS4231_PLAY:
dir = DDI_DMA_WRITE;
caps = ENGINE_OUTPUT_CAP;
eng->ce_syncdir = DDI_DMA_SYNC_FORDEV;
eng->ce_codec_en = INTC_PEN;
break;
default:
kmem_free(eng, sizeof (*eng));
audio_dev_warn(adev, "bad engine number (%d)!", num);
return (DDI_FAILURE);
}
state->cs_engines[num] = eng;
rc = ddi_dma_alloc_handle(dip, CS4231_DMA_ATTR(state), DDI_DMA_SLEEP,
NULL, &eng->ce_dmah);
if (rc != DDI_SUCCESS) {
audio_dev_warn(adev, "ddi_dma_alloc_handle failed: %d", rc);
return (DDI_FAILURE);
}
rc = ddi_dma_mem_alloc(eng->ce_dmah, CS4231_BUFSZ, &buf_attr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &eng->ce_kaddr,
&bufsz, &eng->ce_acch);
if (rc == DDI_FAILURE) {
audio_dev_warn(adev, "dma_mem_alloc failed");
return (DDI_FAILURE);
}
rc = ddi_dma_addr_bind_handle(eng->ce_dmah, NULL,
eng->ce_kaddr, CS4231_BUFSZ, dir | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, NULL, &dmac, &ccnt);
if ((rc != DDI_DMA_MAPPED) || (ccnt != 1)) {
audio_dev_warn(adev,
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
eng->ce_paddr = dmac.dmac_address;
eng->ce_engine = audio_engine_alloc(&audiocs_engine_ops, caps);
if (eng->ce_engine == NULL) {
audio_dev_warn(adev, "audio_engine_alloc failed");
return (DDI_FAILURE);
}
audio_engine_set_private(eng->ce_engine, eng);
audio_dev_add_engine(adev, eng->ce_engine);
return (DDI_SUCCESS);
}
void
audiocs_free_engine(CS_engine_t *eng)
{
CS_state_t *state = eng->ce_state;
audio_dev_t *adev = state->cs_adev;
if (eng == NULL)
return;
if (eng->ce_engine) {
audio_dev_remove_engine(adev, eng->ce_engine);
audio_engine_free(eng->ce_engine);
}
if (eng->ce_paddr) {
(void) ddi_dma_unbind_handle(eng->ce_dmah);
}
if (eng->ce_acch) {
ddi_dma_mem_free(&eng->ce_acch);
}
if (eng->ce_dmah) {
ddi_dma_free_handle(&eng->ce_dmah);
}
kmem_free(eng, sizeof (*eng));
}
int
audiocs_poll_ready(CS_state_t *state)
{
ddi_acc_handle_t handle = CODEC_HANDLE;
int x = 0;
uint8_t iar;
uint8_t idr;
ASSERT(state->cs_regs != NULL);
ASSERT(handle != NULL);
iar = ddi_get8(handle, &CS4231_IAR);
while ((iar & IAR_INIT) && x++ < CS4231_TIMEOUT) {
drv_usecwait(50);
iar = ddi_get8(handle, &CS4231_IAR);
}
if (x >= CS4231_TIMEOUT) {
return (DDI_FAILURE);
}
x = 0;
SELIDX(state, ESI_REG);
idr = ddi_get8(handle, &CS4231_IDR);
while ((idr & ESI_ACI) && x++ < CS4231_TIMEOUT) {
drv_usecwait(50);
idr = ddi_get8(handle, &CS4231_IDR);
}
if (x >= CS4231_TIMEOUT) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
#ifdef DEBUG
audiocs_sel_index(CS_state_t *state, uint8_t reg, int n)
#else
audiocs_sel_index(CS_state_t *state, uint8_t reg)
#endif
{
int x;
uint8_t T;
ddi_acc_handle_t handle = CODEC_HANDLE;
uint8_t *addr = &CS4231_IAR;
for (x = 0; x < CS4231_RETRIES; x++) {
ddi_put8(handle, addr, reg);
T = ddi_get8(handle, addr);
if (T == reg) {
break;
}
drv_usecwait(1000);
}
if (x == CS4231_RETRIES) {
audio_dev_warn(state->cs_adev,
#ifdef DEBUG
"line %d: Couldn't select index (0x%02x 0x%02x)", n,
#else
"Couldn't select index (0x%02x 0x%02x)",
#endif
T, reg);
audio_dev_warn(state->cs_adev,
"audio may not work correctly until it is stopped and "
"restarted");
}
}
void
#ifdef DEBUG
audiocs_put_index(CS_state_t *state, uint8_t val, uint8_t mask, int n)
#else
audiocs_put_index(CS_state_t *state, uint8_t val, uint8_t mask)
#endif
{
int x;
uint8_t T;
ddi_acc_handle_t handle = CODEC_HANDLE;
uint8_t *addr = &CS4231_IDR;
val &= mask;
for (x = 0; x < CS4231_RETRIES; x++) {
ddi_put8(handle, addr, val);
T = ddi_get8(handle, addr);
if (T == val) {
break;
}
drv_usecwait(1000);
}
if (x == CS4231_RETRIES) {
#ifdef DEBUG
audio_dev_warn(state->cs_adev,
"line %d: Couldn't set value (0x%02x 0x%02x)", n, T, val);
#else
audio_dev_warn(state->cs_adev,
"Couldn't set value (0x%02x 0x%02x)", T, val);
#endif
audio_dev_warn(state->cs_adev,
"audio may not work correctly until it is stopped and "
"restarted");
}
}