/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * audiocs Audio Driver * * This Audio Driver controls the Crystal CS4231 Codec used on many SPARC * platforms. It does not support the CS4231 on Power PCs or x86 PCs. It * does support two different DMA engines, the APC and EB2. The code for * those DMA engines is split out and a well defined, but private, interface * is used to control those DMA engines. * * For some reason setting the CS4231's registers doesn't always * succeed. Therefore every time we set a register we always read it * back to make sure it was set. If not we wait a little while and * then try again. This is all taken care of in the routines * audiocs_put_index() and audiocs_sel_index() and the macros ORIDX() * and ANDIDX(). We don't worry about the status register because it * is cleared by writing anything to it. So it doesn't matter what * the value written is. * * This driver supports suspending and resuming. A suspend just stops playing * and recording. The play DMA buffers end up getting thrown away, but when * you shut down the machine there is a break in the audio anyway, so they * won't be missed and it isn't worth the effort to save them. When we resume * we always start playing and recording. If they aren't needed they get * shut off by the mixer. * * Power management is supported by this driver. * * NOTE: This module depends on drv/audio being loaded first. */ #include #include #include #include #include #include #include #include "audio_4231.h" /* * Module linkage routines for the kernel */ 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); /* * Entry point routine prototypes */ 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); /* * Control callbacks. */ 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); /* Local Routines */ 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 }; /* * Global variables, but viewable only by this file. */ /* play gain array, converts linear gain to 64 steps of log10 gain */ static uint8_t cs4231_atten[] = { 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, /* [000] -> [004] */ 0x3a, 0x39, 0x38, 0x37, 0x36, /* [005] -> [009] */ 0x35, 0x34, 0x33, 0x32, 0x31, /* [010] -> [014] */ 0x30, 0x2f, 0x2e, 0x2d, 0x2c, /* [015] -> [019] */ 0x2b, 0x2a, 0x29, 0x29, 0x28, /* [020] -> [024] */ 0x28, 0x27, 0x27, 0x26, 0x26, /* [025] -> [029] */ 0x25, 0x25, 0x24, 0x24, 0x23, /* [030] -> [034] */ 0x23, 0x22, 0x22, 0x21, 0x21, /* [035] -> [039] */ 0x20, 0x20, 0x1f, 0x1f, 0x1f, /* [040] -> [044] */ 0x1e, 0x1e, 0x1e, 0x1d, 0x1d, /* [045] -> [049] */ 0x1d, 0x1c, 0x1c, 0x1c, 0x1b, /* [050] -> [054] */ 0x1b, 0x1b, 0x1a, 0x1a, 0x1a, /* [055] -> [059] */ 0x1a, 0x19, 0x19, 0x19, 0x19, /* [060] -> [064] */ 0x18, 0x18, 0x18, 0x18, 0x17, /* [065] -> [069] */ 0x17, 0x17, 0x17, 0x16, 0x16, /* [070] -> [074] */ 0x16, 0x16, 0x16, 0x15, 0x15, /* [075] -> [079] */ 0x15, 0x15, 0x15, 0x14, 0x14, /* [080] -> [084] */ 0x14, 0x14, 0x14, 0x13, 0x13, /* [085] -> [089] */ 0x13, 0x13, 0x13, 0x12, 0x12, /* [090] -> [094] */ 0x12, 0x12, 0x12, 0x12, 0x11, /* [095] -> [099] */ 0x11, 0x11, 0x11, 0x11, 0x11, /* [100] -> [104] */ 0x10, 0x10, 0x10, 0x10, 0x10, /* [105] -> [109] */ 0x10, 0x0f, 0x0f, 0x0f, 0x0f, /* [110] -> [114] */ 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, /* [114] -> [119] */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, /* [120] -> [124] */ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, /* [125] -> [129] */ 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, /* [130] -> [134] */ 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, /* [135] -> [139] */ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, /* [140] -> [144] */ 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, /* [145] -> [149] */ 0x0a, 0x0a, 0x0a, 0x0a, 0x09, /* [150] -> [154] */ 0x09, 0x09, 0x09, 0x09, 0x09, /* [155] -> [159] */ 0x09, 0x09, 0x08, 0x08, 0x08, /* [160] -> [164] */ 0x08, 0x08, 0x08, 0x08, 0x08, /* [165] -> [169] */ 0x08, 0x07, 0x07, 0x07, 0x07, /* [170] -> [174] */ 0x07, 0x07, 0x07, 0x07, 0x07, /* [175] -> [179] */ 0x06, 0x06, 0x06, 0x06, 0x06, /* [180] -> [184] */ 0x06, 0x06, 0x06, 0x06, 0x05, /* [185] -> [189] */ 0x05, 0x05, 0x05, 0x05, 0x05, /* [190] -> [194] */ 0x05, 0x05, 0x05, 0x05, 0x04, /* [195] -> [199] */ 0x04, 0x04, 0x04, 0x04, 0x04, /* [200] -> [204] */ 0x04, 0x04, 0x04, 0x04, 0x03, /* [205] -> [209] */ 0x03, 0x03, 0x03, 0x03, 0x03, /* [210] -> [214] */ 0x03, 0x03, 0x03, 0x03, 0x03, /* [215] -> [219] */ 0x02, 0x02, 0x02, 0x02, 0x02, /* [220] -> [224] */ 0x02, 0x02, 0x02, 0x02, 0x02, /* [225] -> [229] */ 0x02, 0x01, 0x01, 0x01, 0x01, /* [230] -> [234] */ 0x01, 0x01, 0x01, 0x01, 0x01, /* [235] -> [239] */ 0x01, 0x01, 0x01, 0x00, 0x00, /* [240] -> [244] */ 0x00, 0x00, 0x00, 0x00, 0x00, /* [245] -> [249] */ 0x00, 0x00, 0x00, 0x00, 0x00, /* [250] -> [254] */ 0x00 /* [255] */ }; /* * STREAMS Structures */ /* * DDI Structures */ /* Device operations structure */ static struct dev_ops audiocs_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ NULL, /* devo_getinfo */ nulldev, /* devo_identify - obsolete */ nulldev, /* devo_probe - not needed */ audiocs_ddi_attach, /* devo_attach */ audiocs_ddi_detach, /* devo_detach */ nodev, /* devo_reset */ NULL, /* devi_cb_ops */ NULL, /* devo_bus_ops */ audiocs_ddi_power, /* devo_power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; /* Linkage structure for loadable drivers */ static struct modldrv audiocs_modldrv = { &mod_driverops, /* drv_modops */ CS4231_MOD_NAME, /* drv_linkinfo */ &audiocs_dev_ops /* drv_dev_ops */ }; /* Module linkage structure */ static struct modlinkage audiocs_modlinkage = { MODREV_1, /* ml_rev */ (void *)&audiocs_modldrv, /* ml_linkage */ NULL /* NULL terminates the list */ }; /* ******* Loadable Module Configuration Entry Points ********************* */ /* * _init() * * Description: * Implements _init(9E). * * Returns: * mod_install() status, see mod_install(9f) */ 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); } /* * _fini() * * Description: * Implements _fini(9E). * * Returns: * mod_remove() status, see mod_remove(9f) */ int _fini(void) { int rv; if ((rv = mod_remove(&audiocs_modlinkage)) == 0) { audio_fini_ops(&audiocs_dev_ops); } return (rv); } /* * _info() * * Description: * Implements _info(9E). * * Arguments: * modinfo *modinfop Pointer to the opaque modinfo structure * * Returns: * mod_info() status, see mod_info(9f) */ int _info(struct modinfo *modinfop) { return (mod_info(&audiocs_modlinkage, modinfop)); } /* ******* Driver Entry Points ******************************************** */ /* * audiocs_ddi_attach() * * Description: * Implement attach(9e). * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * ddi_attach_cmd_t cmd Attach command * * Returns: * DDI_SUCCESS The driver was initialized properly * DDI_FAILURE The driver couldn't be initialized properly */ 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); } } /* * audiocs_ddi_detach() * * Description: * Implement detach(9e). * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * ddi_detach_cmd_t cmd Detach command * * Returns: * DDI_SUCCESS Success. * DDI_FAILURE 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); } } /* * audiocs_ddi_power() * * Description: * Implements power(9E). * * Arguments: * def_info_t *dip Ptr to the device's dev_info structure * int component Which component to power up/down * int level The power level for the component * * Returns: * DDI_SUCCESS Power level changed, we always succeed */ static int audiocs_ddi_power(dev_info_t *dip, int component, int level) { CS_state_t *state; if (component != CS4231_COMPONENT) return (DDI_FAILURE); /* get the state structure */ state = ddi_get_driver_private(dip); ASSERT(!mutex_owned(&state->cs_lock)); /* make sure we have some work to do */ mutex_enter(&state->cs_lock); /* * We don't do anything if we're suspended. Suspend/resume diddles * with power anyway. */ if (!state->cs_suspended) { /* check the level change to see what we need to do */ if (level == CS4231_PWR_OFF && state->cs_powered) { /* power down and save the state */ audiocs_power_down(state); state->cs_powered = B_FALSE; } else if (level == CS4231_PWR_ON && !state->cs_powered) { /* power up */ audiocs_power_up(state); state->cs_powered = B_TRUE; } } mutex_exit(&state->cs_lock); ASSERT(!mutex_owned(&state->cs_lock)); return (DDI_SUCCESS); } /* ******* Local Routines *************************************************** */ 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); } /* unmap the registers */ CS4231_DMA_UNMAP_REGS(state); /* destroy the state mutex */ mutex_destroy(&state->cs_lock); kmem_free(state, sizeof (*state)); } /* * audiocs_attach() * * Description: * Attach an instance of the CS4231 driver. This routine does the device * dependent attach tasks. When it is complete it calls * audio_dev_register() to register with the framework. * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * * Returns: * DDI_SUCCESS The driver was initialized properly * DDI_FAILURE The driver couldn't be initialized properly */ static int audiocs_attach(dev_info_t *dip) { CS_state_t *state; audio_dev_t *adev; /* allocate the state structure */ state = kmem_zalloc(sizeof (*state), KM_SLEEP); state->cs_dip = dip; ddi_set_driver_private(dip, state); /* now fill it in, initialize the state mutexs first */ mutex_init(&state->cs_lock, NULL, MUTEX_DRIVER, NULL); /* * audio state initialization... should always succeed, * framework will message failure. */ 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"); /* initialize the audio state structures */ if ((audiocs_init_state(state)) == DDI_FAILURE) { audio_dev_warn(adev, "init_state() failed"); goto error; } mutex_enter(&state->cs_lock); /* initialize the audio chip */ if ((audiocs_chip_init(state)) == DDI_FAILURE) { mutex_exit(&state->cs_lock); audio_dev_warn(adev, "chip_init() failed"); goto error; } /* chip init will have powered us up */ state->cs_powered = B_TRUE; mutex_exit(&state->cs_lock); /* finally register with framework to kick everything off */ if (audio_dev_register(state->cs_adev) != DDI_SUCCESS) { audio_dev_warn(state->cs_adev, "unable to register audio dev"); } /* everything worked out, so report the device */ ddi_report_dev(dip); return (DDI_SUCCESS); error: audiocs_destroy(state); return (DDI_FAILURE); } /* * audiocs_resume() * * Description: * Resume a suspended device instance. * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * * Returns: * DDI_SUCCESS The driver was initialized properly * DDI_FAILURE The driver couldn't be initialized properly */ static int audiocs_resume(dev_info_t *dip) { CS_state_t *state; audio_dev_t *adev; /* we've already allocated the state structure so get ptr */ state = ddi_get_driver_private(dip); adev = state->cs_adev; ASSERT(dip == state->cs_dip); ASSERT(!mutex_owned(&state->cs_lock)); /* mark the Codec busy -- this should keep power(9e) away */ (void) pm_busy_component(state->cs_dip, CS4231_COMPONENT); /* power it up */ audiocs_power_up(state); state->cs_powered = B_TRUE; mutex_enter(&state->cs_lock); /* initialize the audio chip */ 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); /* * We have already powered up the chip, but this alerts the * framework to the fact. */ (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); } /* * audiocs_detach() * * Description: * Detach an instance of the CS4231 driver. * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * * Returns: * DDI_SUCCESS The driver was detached * DDI_FAILURE The driver couldn't be detached (busy) */ static int audiocs_detach(dev_info_t *dip) { CS_state_t *state; audio_dev_t *adev; ddi_acc_handle_t handle; /* get the state structure */ state = ddi_get_driver_private(dip); handle = CODEC_HANDLE; adev = state->cs_adev; /* don't detach if still in use */ if (audio_dev_unregister(adev) != DDI_SUCCESS) { return (DDI_FAILURE); } if (state->cs_powered) { /* * Make sure the Codec and DMA engine are off. */ SELIDX(state, INTC_REG); ANDIDX(state, ~(INTC_PEN|INTC_CEN), INTC_VALID_MASK); /* make sure the DMA engine isn't going to do anything */ CS4231_DMA_RESET(state); /* * power down the device, no reason to waste power without * a driver */ (void) pm_lower_power(dip, CS4231_COMPONENT, CS4231_PWR_OFF); } audiocs_destroy(state); return (DDI_SUCCESS); } /* * audiocs_suspend() * * Description: * Suspend an instance of the CS4231 driver. * * Arguments: * dev_info_t *dip Pointer to the device's dev_info struct * * Returns: * DDI_SUCCESS The driver was detached * DDI_FAILURE The driver couldn't be detached */ static int audiocs_suspend(dev_info_t *dip) { CS_state_t *state; /* get the state structure */ 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) { /* now we can power down the Codec */ audiocs_power_down(state); state->cs_powered = B_FALSE; } state->cs_suspended = B_TRUE; /* stop new ops */ 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) /* * audiocs_alloc_ctrl * * Description: * Allocates a control structure for the audio mixer. * * Arguments: * CS_state_t *state Device soft state. * uint32_t num Control number to allocate. * uint64_t val Initial value. * * Returns: * Pointer to newly allocated CS_ctrl_t structure. */ 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); } /* * audiocs_free_ctrl * * Description: * Frees a control and all resources associated with it. * * Arguments: * CS_ctrl_t *cc Pointer to control structure. */ 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)); } /* * audiocs_add_controls * * Description: * Allocates and registers all controls for this device. * * Arguments: * CS_state_t *state Device soft state. * * Returns: * DDI_SUCCESS All controls added and registered * DDI_FAILURE At least one control was not added or registered. */ 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); } /* * audiocs_del_controls * * Description: * Unregisters and frees all controls for this device. * * Arguments: * CS_state_t *state Device soft state. */ 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); } /* * audiocs_chip_init() * * Description: * Power up the audio core, initialize the audio Codec, prepare the chip * for use. * * Arguments: * CS_state_t *state The device's state structure * * Returns: * DDI_SUCCESS Chip initialized and ready to use * DDI_FAILURE Chip not initialized and not ready */ static int audiocs_chip_init(CS_state_t *state) { ddi_acc_handle_t handle = CODEC_HANDLE; /* make sure we are powered up */ CS4231_DMA_POWER(state, CS4231_PWR_ON); CS4231_DMA_RESET(state); /* wait for the Codec before we continue */ if (audiocs_poll_ready(state) == DDI_FAILURE) { return (DDI_FAILURE); } /* activate registers 16 -> 31 */ SELIDX(state, MID_REG); ddi_put8(handle, &CS4231_IDR, MID_MODE2); /* now figure out what version we have */ SELIDX(state, VID_REG); if (ddi_get8(handle, &CS4231_IDR) & VID_A) { state->cs_revA = B_TRUE; } else { state->cs_revA = B_FALSE; } /* get rid of annoying popping by muting the output channels */ 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); /* initialize aux input channels to known gain values & muted */ 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); /* initialize aux input channels to known gain values & muted */ 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); /* program the sample rate, play and capture must be the same */ 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); } /* * Set up the Codec for playback and capture disabled, dual DMA, and * playback and capture DMA. */ 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); } /* * Turn on the output level bit to be 2.8 Vpp. Also, don't go to 0 on * underflow. */ SELIDX(state, AFE1_REG); PUTIDX(state, AFE1_OLB, AFE1_VALID_MASK); /* turn on the high pass filter if Rev A */ SELIDX(state, AFE2_REG); if (state->cs_revA) { PUTIDX(state, AFE2_HPF, AFE2_VALID_MASK); } else { PUTIDX(state, 0, AFE2_VALID_MASK); } /* clear the play and capture interrupt flags */ SELIDX(state, AFS_REG); ddi_put8(handle, &CS4231_STATUS, (AFS_RESET_STATUS)); /* the play and record gains will be set by the audio mixer */ /* unmute the output */ SELIDX(state, LDACO_REG); ANDIDX(state, ~LDACO_LDM, LDAC0_VALID_MASK); SELIDX(state, RDACO_REG); ANDIDX(state, ~RDACO_RDM, RDAC0_VALID_MASK); /* unmute the mono speaker and mute mono in */ SELIDX(state, MIOC_REG); PUTIDX(state, MIOC_MIM, MIOC_VALID_MASK); audiocs_configure_output(state); audiocs_configure_input(state); return (DDI_SUCCESS); } /* * audiocs_init_state() * * Description: * This routine initializes the audio driver's state structure and * maps in the registers. This also includes reading the properties. * * CAUTION: This routine maps the registers and initializes a mutex. * Failure cleanup is handled by cs4231_attach(). It is not * handled locally by this routine. * * Arguments: * CS_state_t *state The device's state structure * * Returns: * DDI_SUCCESS State structure initialized * DDI_FAILURE State structure not initialized */ 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" }; /* set up the pm-components */ 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); } /* figure out which DMA engine hardware we have */ 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; } /* cs_regs, cs_eb2_regs and cs_handles filled in later */ /* most of what's left is filled in when the registers are mapped */ audiocs_get_ports(state); /* Allocate engines, must be done before register mapping called */ if ((audiocs_alloc_engine(state, CS4231_PLAY) != DDI_SUCCESS) || (audiocs_alloc_engine(state, CS4231_REC) != DDI_SUCCESS)) { return (DDI_FAILURE); } /* Map in the registers */ if (CS4231_DMA_MAP_REGS(state) == DDI_FAILURE) { return (DDI_FAILURE); } /* Allocate and add controls, must be done *after* registers mapped */ if (audiocs_add_controls(state) != DDI_SUCCESS) { return (DDI_FAILURE); } state->cs_suspended = B_FALSE; state->cs_powered = B_FALSE; return (DDI_SUCCESS); } /* * audiocs_get_ports() * * Description: * Get which audiocs h/w version we have and use this to * determine the input and output ports as well whether or not * the hardware has internal loopbacks or not. We also have three * different ways for the properties to be specified, which we * also need to worry about. * * Vers Platform(s) DMA eng. audio-module** loopback * a SS-4+/SS-5+ apcdma no no * b Ultra-1&2 apcdma no yes * c positron apcdma no yes * d PPC - retired * e x86 - retired * f tazmo eb2dma Perigee no * g tazmo eb2dma Quark yes * h darwin+ eb2dma no N/A * * Vers model~ aux1* aux2* * a N/A N/A N/A * b N/A N/A N/A * c N/A N/A N/A * d retired * e retired * f SUNW,CS4231f N/A N/A * g SUNW,CS4231g N/A N/A * h SUNW,CS4231h cdrom none * * * = Replaces internal-loopback for latest property type, can be * set to "cdrom", "loopback", or "none". * * ** = For plugin audio modules only. Starting with darwin, this * property is replaces by the model property. * * ~ = Replaces audio-module. * * + = Has the capability of having a cable run from the internal * CD-ROM to the audio device. * * N/A = Not applicable, the property wasn't created for early * platforms, or the property has been retired. * * NOTE: Older tazmo and quark machines don't have the model property. * * Arguments: * CS_state_t *state The device's state structure */ 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; /* First we set the common ports, etc. */ 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); /* now we try the new "model" property */ 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) { /* darwin */ 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) { /* quark audio module */ audio_dev_set_version(adev, CS_DEV_VERSION_G); /* * NB: This could do SUNVTS LOOPBACK, but we * don't support it for now... owing to no * support in framework. */ } else if (strcmp(prop_str, "SUNW,CS4231f") == 0) { /* tazmo */ 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); /* done with the property */ } else { /* now try the older "audio-module" property */ 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': /* quark audio module */ audio_dev_set_version(adev, CS_DEV_VERSION_G); /* See quark comment above about SunVTS */ break; case 'P': /* tazmo */ 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); /* done with the prop */ } else { /* now try heuristics, ;-( */ 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); } /* * Again, we don't support SunVTS for these * boards, although we potentially could. */ } else { audio_dev_set_version(adev, CS_DEV_VERSION_A); state->cs_imask |= (1U << INPUT_CD); } } } } /* * audiocs_power_up() * * Description: * Power up the Codec and restore the codec's registers. * * NOTE: We don't worry about locking since the only routines * that may call us are attach() and power() Both of * which should be the only threads in the driver. * * Arguments: * CS_state_t *state The device's state structure */ static void audiocs_power_up(CS_state_t *state) { ddi_acc_handle_t handle = CODEC_HANDLE; int i; /* turn on the Codec */ CS4231_DMA_POWER(state, CS4231_PWR_ON); /* reset the DMA engine(s) */ CS4231_DMA_RESET(state); (void) audiocs_poll_ready(state); /* * Reload the Codec's registers, the DMA engines will be * taken care of when play and record start up again. But * first enable registers 16 -> 31. */ SELIDX(state, MID_REG); PUTIDX(state, state->cs_save[MID_REG], MID_VALID_MASK); for (i = 0; i < CS4231_REGS; i++) { /* restore Codec registers */ SELIDX(state, (i | IAR_MCE)); ddi_put8(handle, &CS4231_IDR, state->cs_save[i]); (void) audiocs_poll_ready(state); } /* clear MCE bit */ SELIDX(state, 0); } /* * audiocs_power_down() * * Description: * Power down the Codec and save the codec's registers. * * NOTE: See the note in cs4231_power_up() about locking. * * Arguments: * CS_state_t *state The device's state structure */ static void audiocs_power_down(CS_state_t *state) { ddi_acc_handle_t handle; int i; handle = state->cs_handles.cs_codec_hndl; /* * We are powering down, so we don't need to do a thing with * the DMA engines. However, we do need to save the Codec * registers. */ for (i = 0; i < CS4231_REGS; i++) { /* save Codec regs */ SELIDX(state, i); state->cs_save[i] = ddi_get8(handle, &CS4231_IDR); } /* turn off the Codec */ CS4231_DMA_POWER(state, CS4231_PWR_OFF); } /* cs4231_power_down() */ /* * audiocs_configure_input() * * Description: * Configure input properties of the mixer (e.g. igain, ports). * * Arguments: * CS_state_t *state The device's state structure */ 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); /* rescale these for our atten array */ l = (((uint32_t)l * 255) / 100) & 0xff; r = (((uint32_t)r * 255) / 100) & 0xff; /* we downshift by 4 bits -- igain only has 16 possible values */ /* NB: that we do not scale here! The SADA driver didn't do so. */ 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)) { /* note that SunVTS also uses this */ 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); } /* * audiocs_configure_output() * * Description: * Configure output properties of the mixer (e.g. ogain, mgain). * * Arguments: * CS_state_t *state The device's state structure */ 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; /* port selection */ 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); } /* monitor gain */ mgain = cs4231_atten[((state->cs_mgain->cc_val * 255) / 100) & 0xff]; SELIDX(state, LC_REG); if (mgain == 0) { /* disable loopbacks when gain == 0 */ PUTIDX(state, LC_OFF, LC_VALID_MASK); } else { /* we use cs4231_atten[] to linearize attenuation */ PUTIDX(state, (mgain << 2) | LC_LBE, LC_VALID_MASK); } /* output gain */ 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; } /* rescale these for our atten array */ 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); } /* * audiocs_get_value() * * Description: * Get a control value * * Arguments: * void *arg The device's state structure * uint64_t *valp Pointer to store value. * * Returns: * 0 The Codec parameter has been retrieved. */ 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); } /* * audiocs_set_ogain() * * Description: * Set the play gain. * * Arguments: * void *arg The device's state structure * uint64_t val The gain to set (both left and right) * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_set_micboost() * * Description: * Set the 20 dB microphone boost. * * Arguments: * void *arg The device's state structure * uint64_t val The 1 to enable, 0 to disable. * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_set_igain() * * Description: * Set the record gain. * * Arguments: * void *arg The device's state structure * uint64_t val The gain to set (both left and right) * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_set_inputs() * * Description: * Set the input ports. * * Arguments: * void *arg The device's state structure * uint64_t val The mask of output ports. * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_set_outputs() * * Description: * Set the output ports. * * Arguments: * void *arg The device's state structure * uint64_t val The mask of input ports. * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_set_mgain() * * Description: * Set the monitor gain. * * Arguments: * void *arg The device's state structure * uint64_t val The gain to set (monoaural).) * * Returns: * 0 The Codec parameter has been set */ 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); } /* * audiocs_open() * * Description: * Opens a DMA engine for use. * * Arguments: * void *arg The DMA engine to set up * int flag Open flags * unsigned *nframesp Receives number of frames * caddr_t *bufp Receives kernel data buffer * * Returns: * 0 on success * errno on failure */ 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) { /* match the busy call above */ (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); } /* * audiocs_close() * * Description: * Closes an audio DMA engine that was previously opened. Since * nobody is using it, we take this opportunity to possibly power * down the entire device. * * Arguments: * void *arg The DMA engine to shut down */ 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); } /* * audiocs_stop() * * Description: * This is called by the framework to stop an engine that is * transferring data. * * Arguments: * void *arg The DMA engine to stop */ 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); /* * Stop the DMA engine. */ CS4231_DMA_STOP(state, eng); /* * Stop the codec. */ SELIDX(state, INTC_REG); ANDIDX(state, ~(eng->ce_codec_en), INTC_VALID_MASK); mutex_exit(&state->cs_lock); } /* * audiocs_start() * * Description: * This is called by the framework to start an engine transferring data. * * Arguments: * void *arg The DMA engine to start * * Returns: * 0 on success, an errno otherwise */ 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) { /* sample rate only set on play side */ 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 { /* * Start the codec. */ SELIDX(state, INTC_REG); ORIDX(state, eng->ce_codec_en, INTC_VALID_MASK); rv = 0; } mutex_exit(&state->cs_lock); return (rv); } /* * audiocs_format() * * Description: * Called by the framework to query the format of the device. * * Arguments: * void *arg The DMA engine to query * * Returns: * AUDIO_FORMAT_S16_NE */ static int audiocs_format(void *arg) { _NOTE(ARGUNUSED(arg)); return (AUDIO_FORMAT_S16_NE); } /* * audiocs_channels() * * Description: * Called by the framework to query the channels of the device. * * Arguments: * void *arg The DMA engine to query * * Returns: * 2 (stereo) */ static int audiocs_channels(void *arg) { _NOTE(ARGUNUSED(arg)); return (2); } /* * audiocs_rates() * * Description: * Called by the framework to query the sample rate of the device. * * Arguments: * void *arg The DMA engine to query * * Returns: * 48000 */ static int audiocs_rate(void *arg) { _NOTE(ARGUNUSED(arg)); return (48000); } /* * audiocs_count() * * Description: * This is called by the framework to get the engine's frame counter * * Arguments: * void *arg The DMA engine to query * * Returns: * frame count for current engine */ 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; /* * Every now and then, we get a value that is just a wee bit * too large. This seems to be a small value related to * prefetch. Rather than believe it, we just assume the last * offset in the buffer. This should allow us to handle * wraps, but without inserting bogus sample counts. */ 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; /* while here, possibly reload the next address */ CS4231_DMA_RELOAD(state, eng); mutex_exit(&state->cs_lock); return (val); } /* * audiocs_sync() * * Description: * This is called by the framework to synchronize DMA caches. * * Arguments: * void *arg The DMA engine to sync */ 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); } /* * audiocs_alloc_engine() * * Description: * Allocates the DMA handles and the memory for the DMA engine. * * Arguments: * CS_state_t *dip Pointer to the device's soft state * int num Engine number, CS4231_PLAY or CS4231_REC. * * Returns: * DDI_SUCCESS Engine initialized. * DDI_FAILURE Engine not initialized. */ 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; /* allocate dma handle */ 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); } /* allocate DMA buffer */ 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); } /* bind DMA buffer */ 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); } /* * audiocs_free_engine() * * Description: * This routine fress the engine and all associated resources. * * Arguments: * CS_engine_t *eng Engine to free. */ 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)); } /* * audiocs_poll_ready() * * Description: * This routine waits for the Codec to complete its initialization * sequence and is done with its autocalibration. * * Early versions of the Codec have a bug that can take as long as * 15 seconds to complete its initialization. For these cases we * use a timeout mechanism so we don't keep the machine locked up. * * Arguments: * CS_state_t *state The device's state structure * * Returns: * DDI_SUCCESS The Codec is ready to continue * DDI_FAILURE The Codec isn't ready to continue */ 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); /* wait for the chip to initialize itself */ 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; /* * Now wait for the chip to complete its autocalibration. * Set the test register. */ 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); } /* * audiocs_sel_index() * * Description: * Select a cs4231 register. The cs4231 has a hardware bug where a * register is not always selected the first time. We try and try * again until the proper register is selected or we time out and * print an error message. * * Arguments: * audiohdl_t ahandle Handle to this device * ddi_acc_handle_t handle A handle to the device's registers * uint8_t addr The register address to program * int reg The register to select */ 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"); } } /* * audiocs_put_index() * * Description: * Program a cs4231 register. The cs4231 has a hardware bug where a * register is not programmed properly the first time. We program a value, * then immediately read back the value and reprogram if nescessary. * We do this until the register is properly programmed or we time out and * print an error message. * * Arguments: * CS_state_t state Handle to this device * uint8_t mask Mask to not set reserved register bits * int val The value to program */ 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"); } }