/* * 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 (C) 4Front Technologies 1996-2008. * * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include "ac97_impl.h" /* * This is the initial value for many controls. This is * a 75% level. */ #define INIT_VAL_MAIN 75 #define INIT_VAL_ST ((75 << 8) | 75) #define INIT_VAL_MN 75 #define INIT_IGAIN_ST ((50 << 8) | 50) #define INIT_IGAIN_MN 50 /* * In AC'97 v2.3, the registers are carved up as follows: * * Audio Base Registers: 0x00 - 0x26 * Audio Extended Registers: 0x28 - 0x3A * Modem Extended Registers: 0x3C - 0x58 * Vendor Reserved Registers: 0x5A - 0x5F * Page Registers: 0x60 - 0x6F * Vendor Reserved Registers: 0x70 - 0x7A * Vendor ID Registers: 0x7C - 0x7F * * We only need to shadow the normal audio registers by default. * TBD: Handling of codec-specific registers in vendor reserved space. * We cannot necessarily meaningfully shadow them. */ #define LAST_SHADOW_REG 0x3A #define NUM_SHADOW ((LAST_SHADOW_REG / sizeof (uint16_t)) + 1) #define SHADOW(ac, reg) ((ac)->shadow[((reg) / sizeof (uint16_t))]) /* * Record source selection. */ #define INPUT_MIC 0 #define INPUT_CD 1 #define INPUT_VIDEO 2 #define INPUT_AUXIN 3 #define INPUT_LINEIN 4 #define INPUT_STEREOMIX 5 #define INPUT_MONOMIX 6 #define INPUT_PHONE 7 static const char *ac_insrcs[] = { AUDIO_PORT_MIC, AUDIO_PORT_CD, AUDIO_PORT_VIDEO, AUDIO_PORT_AUX1IN, AUDIO_PORT_LINEIN, AUDIO_PORT_STEREOMIX, AUDIO_PORT_MONOMIX, AUDIO_PORT_PHONE, NULL, }; /* * Per audio device state structure */ struct ac97 { dev_info_t *dip; /* DDI device instance */ audio_dev_t *d; void *private; /* drivers devc */ ac97_rd_t rd; /* drivers port read routine */ ac97_wr_t wr; /* drivers port write routine */ char name[128]; /* driver instance name */ uint8_t nchan; uint16_t shadow[NUM_SHADOW]; uint32_t flags; #define AC97_FLAG_AMPLIFIER (1 << 0) /* ext. amp on by default */ #define AC97_FLAG_MICBOOST (1 << 1) /* micboost on by default */ #define AC97_FLAG_SPEAKER (1 << 2) /* mono out on by default */ #define AC97_FLAG_AUX_HP (1 << 4) /* possible uses for AUX_OUT */ #define AC97_FLAG_AUX_4CH (1 << 5) #define AC97_FLAG_AUX_LVL (1 << 6) #define AC97_FLAG_SPEAKER_OK (1 << 7) /* expose mono out */ #define AC97_FLAG_NO_HEADPHONE (1 << 8) /* do not expose headphone */ #define AC97_FLAG_NO_CDROM (1 << 9) /* do not expose CDROM */ #define AC97_FLAG_NO_PHONE (1 << 10) /* do not expose phone in */ #define AC97_FLAG_NO_VIDEO (1 << 11) /* do not expose video in */ #define AC97_FLAG_NO_AUXIN (1 << 12) /* do not expose aux in */ #define AC97_FLAG_NO_AUXOUT (1 << 13) /* do not expose aux out */ #define AC97_FLAG_NO_LINEIN (1 << 14) /* do not expose linein */ #define AC97_FLAG_NO_MIC (1 << 15) /* do not expose mic */ uint32_t vid; /* Vendor ID for CODEC */ uint16_t caps; void (*codec_init)(ac97_t *); void (*codec_reset)(ac97_t *); list_t ctrls; uint64_t inputs; }; struct modlmisc ac97_modlmisc = { &mod_miscops, "Audio Codec '97 Support" }; struct modlinkage ac97_modlinkage = { MODREV_1, { &ac97_modlmisc, NULL } }; int _init(void) { return (mod_install(&ac97_modlinkage)); } int _fini(void) { return (mod_install(&ac97_modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&ac97_modlinkage, modinfop)); } #if 0 /* * The following table, and the code to scale it, works in percentages. * This may be convenient for humans, but it would be faster if the table * entries were rescaled to 256. (Division by 100 is painful. Divison by * 256 is trivial.) */ static const char ac97_val_cvt[101] = { 0, 0, 3, 7, 10, 13, 16, 19, 21, 23, 26, 28, 30, 32, 34, 35, 37, 39, 40, 42, 43, 45, 46, 47, 49, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 70, 71, 72, 73, 73, 74, 75, 75, 76, 77, 77, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 96, 97, 97, 98, 98, 98, 99, 99, 100 }; #endif /* * This code has three main functions. All related to converting * a standard controls value to hardware specific values. All * Standard passed in values are 0-100 as in percent. * * First it takes a value passed in as volume or gain and * converts to attenuation or gain correspondingly. Since this is * what the hardware needs. * * Second it adjusts the value passed in to compensate for the none * linear nature of human hearing, sound loudness, sensitivity. It * converts the linear value to a logarithmic value. This gives users * the perception that the controls are linear. * * Third it converts the value to the number of bits that a hardware * register needs to be. * * On input the following are supplied: * left - The gain or volume in percent for left channel. * right - The gain or volume in percent for right channel. * bits - The number of bits the hardware needs. If this value * is negetive then right and left are gain else they * are volume. * * On return the following is returned: * * bit: 15 8 7 0 * ---------------------------------- * | left channel | right channel | * ---------------------------------- * ( each channel is "bits" wide ) */ uint16_t ac_val_scale(int left, int right, int bits) { ASSERT(left <= 100); ASSERT(right <= 100); if (bits < 0) { /* This is gain not ATTN */ left = 100 - left; right = 100 - right; bits = -bits; } #if 0 /* * 4Front's code used a table to smooth the transitions * somewhat. Without this change, the volume levels adjusted * near the top of the table seem to have less effect. Its * hard to notice a volume change from 100 to 95, without the * val_cvt table, for example. However, the scaling has an * ugly side effect, which is at the default volumes (75%), we * wind up having the level set too high for some * codec/amplifier combinations. * * Legacy Sun code didn't have this table, and some * qualitative testing shows that it isn't really necessary. */ left = 100 - ac97_val_cvt[left]; right = 100 - ac97_val_cvt[right]; #else left = 100 - left; right = 100 - right; #endif return (((left * ((1 << bits) - 1) / 100) << 8) | (right * ((1 << bits) - 1) / 100)); } uint16_t ac_mono_scale(int val, int bits) { ASSERT(val <= 100); if (bits < 0) { /* This is gain not ATTN */ bits = -bits; } else { val = 100 - val; /* convert to attenuation */ } return (val * ((1 << bits) - 1) / 100); } audio_dev_t * ac_get_dev(ac97_t *ac) { return (ac->d); } int ac_get_prop(ac97_t *ac, char *prop, int defval) { int rv; rv = ddi_prop_get_int(DDI_DEV_T_ANY, ac->dip, DDI_PROP_DONTPASS, prop, defval); return (rv); } /* * This calls the Hardware drivers access write routine * to write to a device register. */ #define WR(r, v) (ac)->wr((ac)->private, (r), (v)) #define RD(r) (ac)->rd((ac)->private, (r)) /* * Probe routines for optional controls * * These routines each probe one aspect of hardware * for controls presents. * If the control is present these routines should * return none zero. */ /* * Is the named register implemented? This routine saves and * restores the original value, and relies on the fact that the * registers (if implemented) will have at least one bit that acts * as a mute (0x8000, 0x8080), so we can probe "silently". * * The probe logic is suggested by the AC'97 2.3 spec. (Unimplemented * registers are required to return zero to facilitate this sort of * detection.) */ static int ac_probe_reg(ac97_t *ac, uint8_t reg) { uint16_t val; int rv = 0; /* get the original value */ val = RD(reg); WR(reg, 0xffff); if (RD(reg) != 0) { rv = 1; } /* restore the original value */ WR(reg, val); return (rv); } /* * Does this device have bass/treble controls? */ static int ac_probe_tone(ac97_t *ac) { /* Bass/Treble contols present */ if (ac->caps & RR_BASS_TREBLE) return (1); else return (0); } /* * If there is a loudness switch? */ static int ac_probe_loud(ac97_t *ac) { /* loudness contol present */ if (ac->caps & RR_LOUDNESS_SUPPORT) return (1); else return (0); } /* * Does this device have a mono-mic input volume control? */ static int ac_probe_mmic(ac97_t *ac) { /* mono mic present */ if (ac->caps & RR_DEDICATED_MIC) return (1); else return (0); } /* * Does this device have a simulated stereo switch? */ static int ac_probe_stsim(ac97_t *ac) { /* simulated stereocontol present */ if (ac->caps & RR_PSEUDO_STEREO) return (1); else return (0); } /* * Does this device have a PC beeper input volume control? */ static int ac_probe_pcbeep(ac97_t *ac) { return (ac_probe_reg(ac, AC97_PC_BEEP_REGISTER)); } /* * Does this device have AUX output port volume control? */ static int ac_probe_rear(ac97_t *ac) { if (ac->flags & AC97_FLAG_AUX_4CH) return (1); else return (0); } /* * Does this device have a mic? */ static int ac_probe_mic(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_MIC)) && (ac_probe_reg(ac, AC97_MIC_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_MIC); return (1); } return (0); } /* * If this device has an AUX output port is it used for headphones? */ static int ac_probe_headphone(ac97_t *ac) { /* headphone control present */ if ((ac->flags & AC97_FLAG_AUX_HP) && !(ac->flags & AC97_FLAG_NO_HEADPHONE)) { return (1); } return (0); } /* * Does this device have AUX output port volume control? */ static int ac_probe_auxout(ac97_t *ac) { /* ALT PCM control present */ if ((ac->flags & AC97_FLAG_AUX_LVL) && !(ac->flags & AC97_FLAG_NO_AUXOUT)) { return (1); } return (0); } /* * Does this device have an AUX input port volume control? */ static int ac_probe_auxin(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_AUXIN)) && (ac_probe_reg(ac, AC97_AUX_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_AUXIN); return (1); } return (0); } /* * Does this device have a phone input port with a volume control? */ static int ac_probe_phone(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_PHONE)) && (ac_probe_reg(ac, AC97_PHONE_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_PHONE); return (1); } return (0); } /* * Does this device have a mono output port with volume control? */ static int ac_probe_mono(ac97_t *ac) { if (!(ac->flags & AC97_FLAG_SPEAKER_OK)) { return (0); } if (ac_probe_reg(ac, AC97_MONO_MASTER_VOLUME_REGISTER)) { return (1); } return (0); } /* * Does this device have a line input port with volume control? */ static int ac_probe_linein(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_LINEIN)) && (ac_probe_reg(ac, AC97_LINE_IN_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_LINEIN); return (1); } return (0); } /* * Does this device have a cdrom input port with volume control? */ static int ac_probe_cdrom(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_CDROM)) && (ac_probe_reg(ac, AC97_CD_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_CD); return (1); } return (0); } /* * Does this device have a video input port with volume control? */ static int ac_probe_video(ac97_t *ac) { if ((!(ac->flags & AC97_FLAG_NO_VIDEO)) && (ac_probe_reg(ac, AC97_VIDEO_VOLUME_REGISTER))) { ac->inputs |= (1U << INPUT_VIDEO); return (1); } return (0); } /* * Does this device have a 3D sound enhancement? */ static int ac_probe_3d(ac97_t *ac) { /* 3D control present */ if (ac->caps & RR_3D_STEREO_ENHANCE_MASK) return (1); else return (0); } static int ac_probe_3d_impl(ac97_t *ac, uint16_t mask) { int rv = 0; uint16_t val; if ((ac->caps & RR_3D_STEREO_ENHANCE_MASK) == 0) return (0); /* get the original value */ val = RD(AC97_THREE_D_CONTROL_REGISTER); WR(AC97_THREE_D_CONTROL_REGISTER, mask); if ((RD(AC97_THREE_D_CONTROL_REGISTER) & mask) != 0) { rv = 1; } /* restore the original value */ WR(AC97_THREE_D_CONTROL_REGISTER, val); return (rv); } static int ac_probe_3d_depth(ac97_t *ac) { return (ac_probe_3d_impl(ac, TDCR_DEPTH_MASK)); } static int ac_probe_3d_center(ac97_t *ac) { return (ac_probe_3d_impl(ac, TDCR_CENTER_MASK)); } /* * Does this device have a center output port with volume control? */ static int ac_probe_center(ac97_t *ac) { uint16_t val; val = RD(AC97_EXTENDED_AUDIO_REGISTER); /* center volume present */ if (val & EAR_CDAC) return (1); else return (0); } /* * Does this device have a LFE (Sub-woofer) output port with * a volume control? */ static int ac_probe_lfe(ac97_t *ac) { uint16_t val; val = RD(AC97_EXTENDED_AUDIO_REGISTER); /* We have LFE control */ if (val & EAR_LDAC) return (1); else return (0); } /* * Are we a multichannel codec? */ static int ac_probe_front(ac97_t *ac) { uint16_t val; val = RD(AC97_EXTENDED_AUDIO_REGISTER); /* Are any of the Surround, Center, or LFE dacs present? */ if (val & (EAR_SDAC | EAR_CDAC | EAR_LDAC)) return (1); else return (0); } static int ac_probe_lineout(ac97_t *ac) { /* if not multichannel, then use "lineout" instead of "front" label */ return (!ac_probe_front(ac)); } static const char *ac_mics[] = { AUDIO_PORT_MIC1, AUDIO_PORT_MIC2, NULL, }; static const char *ac_monos[] = { AUDIO_PORT_MONOMIX, AUDIO_PORT_MIC, NULL }; /* * This calls the Hardware drivers access write routine * to write to a device register. */ void ac_wr(ac97_t *ac, uint8_t reg, uint16_t val) { if ((reg < LAST_SHADOW_REG) && (reg > 0)) { SHADOW(ac, reg) = val; } ac->wr(ac->private, reg, val); } /* * This obtains the shadowed value of a register. If the register is * out of range, zero is returned. * * To read a hardware register, use the RD() macro above. */ uint16_t ac_rd(ac97_t *ac, uint8_t reg) { if ((reg < LAST_SHADOW_REG) && (reg > 0)) { return (SHADOW(ac, reg)); } return (ac->rd(ac->private, reg)); } /* * This calls the hardware driver's access read/write routine * to set bits in a device register. */ void ac_set(ac97_t *ac, uint8_t reg, uint16_t val) { ac_wr(ac, reg, ac->rd(ac->private, reg) | val); } /* * This calls the hardware driver's access read/write routine * to clear bits in a device register. */ void ac_clr(ac97_t *ac, uint8_t reg, uint16_t val) { ac_wr(ac, reg, ac->rd(ac->private, reg) & ~val); } /* * Look for a control attached to this device based * on its control number. * * If this control number is found the per controls state * structure is returned. */ ac97_ctrl_t * ac97_control_find(ac97_t *ac, const char *name) { ac97_ctrl_t *ctrl; list_t *l = &ac->ctrls; /* Validate that ctrlnum is real and usable */ for (ctrl = list_head(l); ctrl; ctrl = list_next(l, ctrl)) { if (strcmp(ctrl->actrl_name, name) == 0) { return (ctrl); } } return (NULL); } /* * This will update all the codec registers from the shadow table. */ static void ac_restore(ac97_t *ac) { /* * If we are restoring previous settings, just reload from the * shadowed settings. */ for (int i = 2; i < LAST_SHADOW_REG; i += sizeof (uint16_t)) { ac->wr(ac->private, i, SHADOW(ac, i)); } } /* * This will update all the hardware controls to the initial values at * start of day. */ static void ac_init_values(ac97_t *ac) { ac97_ctrl_t *ctrl; for (ctrl = list_head(&ac->ctrls); ctrl; ctrl = list_next(&ac->ctrls, ctrl)) { ctrl->actrl_value = ctrl->actrl_initval; ctrl->actrl_write_fn(ctrl, ctrl->actrl_initval); } } /* * Select the input source for recording. This is the set routine * for the control AUDIO_CONTROL_INPUTS. */ static void ac_insrc_set(ac97_ctrl_t *ctrl, uint64_t value) { ac97_t *ac = ctrl->actrl_ac97; uint16_t set_val; set_val = ddi_ffs(value & 0xffff); if ((set_val > 0) && (set_val <= 8)) { set_val--; ac_wr(ac, AC97_RECORD_SELECT_CTRL_REGISTER, set_val | (set_val << 8)); } } static void ac_gpr_toggle(ac97_ctrl_t *ctrl, int bit, uint64_t onoff) { ac97_t *ac = ctrl->actrl_ac97; uint16_t v; v = SHADOW(ac, AC97_GENERAL_PURPOSE_REGISTER); if (onoff) { v |= bit; } else { v &= ~bit; } ac_wr(ac, AC97_GENERAL_PURPOSE_REGISTER, v); } static void ac_3donoff_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_3D_STEREO_ENHANCE, value); } static void ac_loudness_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_BASS_BOOST, value); } static void ac_loopback_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_LPBK, value); } /* * This will set simulated stereo control to on or off. */ static void ac_stsim_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_ST, value); } /* * This will set mic select control to mic1=0 or mic2=1. */ static void ac_selmic_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_MS_MIC2, value & 2); } /* * This will set mono source select control to mix=0 or mic=1. */ static void ac_monosrc_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_gpr_toggle(ctrl, GPR_MONO_MIC_IN, value & 2); } static void ac_stereo_set(ac97_ctrl_t *ctrl, uint64_t value, uint8_t reg) { ac97_t *ac = ctrl->actrl_ac97; uint8_t left, right; uint16_t mute; left = (value >> 8) & 0xff; right = value & 0xff; mute = value ? 0 : ctrl->actrl_muteable; ac_wr(ac, reg, ac_val_scale(left, right, ctrl->actrl_bits) | mute); } static void ac_mono_set(ac97_ctrl_t *ctrl, uint64_t value, uint8_t reg, int shift) { ac97_t *ac = ctrl->actrl_ac97; uint8_t val; uint16_t mute, v; uint16_t mask; val = value & 0xff; mute = val ? 0 : ctrl->actrl_muteable; mask = ctrl->actrl_muteable | (((1 << ABS(ctrl->actrl_bits)) - 1) << shift); v = SHADOW(ac, reg); v &= ~mask; /* clear all of our bits, preserve others */ /* now set the mute bit, and volume bits */ v |= mute; v |= (ac_mono_scale(val, ctrl->actrl_bits) << shift); ac_wr(ac, reg, v); } static void ac97_master_set(ac97_ctrl_t *ctrl, uint64_t value) { value = value | (value << 8); ac_stereo_set(ctrl, value, AC97_PCM_OUT_VOLUME_REGISTER); } static void ac97_lineout_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_MASTER_VOLUME_REGISTER); } static void ac97_surround_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_EXTENDED_LRS_VOLUME_REGISTER); } static void ac97_aux1out_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_HEADPHONE_VOLUME_REGISTER); } static void ac97_headphone_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_HEADPHONE_VOLUME_REGISTER); } static void ac_cd_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_CD_VOLUME_REGISTER); } static void ac_video_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_VIDEO_VOLUME_REGISTER); } static void ac_auxin_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_AUX_VOLUME_REGISTER); } static void ac_linein_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_LINE_IN_VOLUME_REGISTER); } /* * This will set mono mic gain control. */ static void ac_monomic_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_RECORD_GAIN_MIC_REGISTER, 0); } static void ac_phone_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_PHONE_VOLUME_REGISTER, 0); } static void ac_mic_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_MIC_VOLUME_REGISTER, 0); } static void ac_speaker_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_MONO_MASTER_VOLUME_REGISTER, 0); } static void ac_pcbeep_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_PC_BEEP_REGISTER, 1); } static void ac_recgain_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_stereo_set(ctrl, value, AC97_RECORD_GAIN_REGISTER); } static void ac_center_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_EXTENDED_C_LFE_VOLUME_REGISTER, 0); } static void ac_lfe_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_EXTENDED_C_LFE_VOLUME_REGISTER, 8); } static void ac_bass_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_MASTER_TONE_CONTROL_REGISTER, 8); } static void ac_treble_set(ac97_ctrl_t *ctrl, uint64_t value) { ac_mono_set(ctrl, value, AC97_MASTER_TONE_CONTROL_REGISTER, 0); } static void ac_3ddepth_set(ac97_ctrl_t *ctrl, uint64_t value) { /* * XXX: This is all wrong... 3D depth/center cannot necessarily * be scaled, because the technology in use may vary. We * need more information about each of the options available * to do the right thing. */ ac_mono_set(ctrl, value, AC97_THREE_D_CONTROL_REGISTER, 0); } static void ac_3dcent_set(ac97_ctrl_t *ctrl, uint64_t value) { /* * XXX: This is all wrong... 3D depth/center cannot necessarily * be scaled, because the technology in use may vary. We * need more information about each of the options available * to do the right thing. */ ac_mono_set(ctrl, value, AC97_THREE_D_CONTROL_REGISTER, 8); } static void ac97_micboost_set(ac97_ctrl_t *ctrl, uint64_t value) { ac97_t *ac = ctrl->actrl_ac97; uint16_t v; v = SHADOW(ac, AC97_MIC_VOLUME_REGISTER); if (value) { v |= MICVR_20dB_BOOST; } else { v &= ~MICVR_20dB_BOOST; } ac_wr(ac, AC97_MIC_VOLUME_REGISTER, v); } /* * This will return the stored value for any control that has been set. * Note this does not return the actual hardware value from a port. But * instead returns the cached value from the last write to the hardware * port. * * arg - This control structure for this control. * value - This is a pointer to the location to put the * controls value. * * On success zero is returned. */ int ac97_control_get(ac97_ctrl_t *ctrl, uint64_t *value) { *value = ctrl->actrl_value; return (0); } int ac97_control_set(ac97_ctrl_t *ctrl, uint64_t value) { uint8_t v1, v2; /* a bit of quick checking */ switch (ctrl->actrl_type) { case AUDIO_CTRL_TYPE_STEREO: v1 = (value >> 8) & 0xff; v2 = value & 0xff; if ((v1 < ctrl->actrl_minval) || (v1 > ctrl->actrl_maxval) || (v2 < ctrl->actrl_minval) || (v2 > ctrl->actrl_maxval) || (value > 0xffff)) { return (EINVAL); } break; case AUDIO_CTRL_TYPE_ENUM: if ((value & ~ctrl->actrl_minval) != (ctrl->actrl_maxval & ~ctrl->actrl_minval)) { return (EINVAL); } break; case AUDIO_CTRL_TYPE_MONO: case AUDIO_CTRL_TYPE_BOOLEAN: if ((value < ctrl->actrl_minval) || (value > ctrl->actrl_maxval)) { return (EINVAL); } break; } ctrl->actrl_value = value; ctrl->actrl_write_fn(ctrl, value); return (0); } static int ac_get_value(void *arg, uint64_t *value) { return (ac97_control_get(arg, value)); } static int ac_set_value(void *arg, uint64_t value) { return (ac97_control_set(arg, value)); } /* * Reset the analog codec hardware * * Reset all analog AC97 hardware, input ADC's, output DAC's and MIXER. * Wait a resonable amount of time for hardware to become ready. */ static void ac_analog_reset(ac97_t *ac) { uint16_t tmp; int wait = 1000; /* delay for up to 1s */ /* Clear stale data and resync register accesses */ tmp = RD(AC97_POWERDOWN_CTRL_STAT_REGISTER); /* reset the codec */ WR(AC97_RESET_REGISTER, 0); tmp = RD(AC97_RESET_REGISTER); /* power up */ WR(AC97_POWERDOWN_CTRL_STAT_REGISTER, 0); /* Wait for ADC/DAC/MIXER to become ready */ while (wait--) { /* 1 msec delay */ drv_usecwait(1000); /* If all ready - end delay */ tmp = RD(AC97_POWERDOWN_CTRL_STAT_REGISTER); SHADOW(ac, AC97_POWERDOWN_CTRL_STAT_REGISTER) = tmp; if ((tmp & PCSR_POWERD_UP) == PCSR_POWERD_UP) { return; } } audio_dev_warn(ac->d, "AC'97 analog power up timed out"); } /* * This is the internal hardware reset routine. * It has no locking and we must be locked before it is * called! * * This will reset and re-initialize the device. * It has two modes of operation that affect how it handles * all controls. * * It re-initializes the device and reloads values with * last updated versions. */ static void ac_hw_reset(ac97_t *ac) { /* * Fully Power up the device */ if (ac->flags & AC97_FLAG_AMPLIFIER) { /* power up - external amp powerd up */ ac_wr(ac, AC97_POWERDOWN_CTRL_STAT_REGISTER, 0); } else { /* power up - external amp powered down */ ac_wr(ac, AC97_POWERDOWN_CTRL_STAT_REGISTER, PCSR_EAPD); } ac_wr(ac, AC97_GENERAL_PURPOSE_REGISTER, 0); switch (ac->vid) { case AC97_CODEC_STAC9708: #if 0 /* non-inverted phase */ /* ac_rd(ac, AC97_VENDOR_REGISTER_11) & ~0x8); */ #endif WR(AC97_VENDOR_REGISTER_11, 8); break; case AC97_CODEC_AD1886: /* jack sense */ WR(AC97_VENDOR_REGISTER_13, (RD(AC97_VENDOR_REGISTER_13) & ~0xEF) | 0x10); break; case AC97_CODEC_AD1888: WR(AC97_VENDOR_REGISTER_15, 0xC420); #if 0 /* GED: This looks fishy to me, so I'm nuking it for now */ /* headphone/aux volume (?) */ ac_wr(ac, AC97_HEADPHONE_VOLUME_REGISTER, 0x0808); #endif break; case AC97_CODEC_AD1980: #if 0 /* set jacksense to mute line if headphone is plugged */ WR(AC97_VENDOR_REGISTER_13, (RD(AC97_VENDOR_REGISTER_13) & ~0xe00) | 0x400); #endif WR(AC97_VENDOR_REGISTER_15, 0xC420); break; case AC97_CODEC_AD1985: WR(AC97_VENDOR_REGISTER_15, 0xC420); break; case AC97_CODEC_WM9704: /* enable I2S */ WR(AC97_VENDOR_REGISTER_01, RD(AC97_VENDOR_REGISTER_01) | 0x80); break; case AC97_CODEC_VT1612A: case AC97_CODEC_VT1617A: case AC97_CODEC_VT1616: /* Turn on Center, Surround, and LFE DACs */ ac_clr(ac, AC97_EXTENDED_AUDIO_STAT_CTRL_REGISTER, EASCR_PRI | EASCR_PRJ | EASCR_PRK); WR(AC97_VENDOR_REGISTER_01, 0x0230); break; case AC97_CODEC_YMF753: /* set TX8 + 3AWE */ WR(AC97_VENDOR_REGISTER_07, RD(AC97_VENDOR_REGISTER_07) | 0x9); break; default: break; } /* call codec specific reset hook */ if (ac->codec_reset != NULL) { ac->codec_reset(ac); } /* Turn off variable sampling rate support */ ac_clr(ac, AC97_EXTENDED_AUDIO_STAT_CTRL_REGISTER, EASCR_VRA); } /* * This will reset and re-initialize the device. It is still incumbent * on the caller (or the audio framework) to replay control settings! */ void ac97_reset(ac97_t *ac) { ac_analog_reset(ac); ac_hw_reset(ac); ac_restore(ac); } /* * Return the number of channels supported by this codec. */ int ac97_num_channels(ac97_t *ac) { return (ac->nchan); } /* * Register a control -- if it fails, it will generate a message to * syslog, but the driver muddles on. (Failure to register a control * should never occur, but is generally benign if it happens.) */ void ac97_control_register(ac97_ctrl_t *ctrl) { ac97_t *ac = ctrl->actrl_ac97; ASSERT(ac->d != NULL); ctrl->actrl_suppress = B_FALSE; /* Register control with framework */ ctrl->actrl_ctrl = audio_dev_add_control(ac->d, &ctrl->actrl_desc, ac_get_value, ac_set_value, ctrl); if (ctrl->actrl_ctrl == NULL) { audio_dev_warn(ac->d, "AC97 %s alloc failed", ctrl->actrl_name); } } void ac97_control_unregister(ac97_ctrl_t *ctrl) { ctrl->actrl_suppress = B_TRUE; if (ctrl->actrl_ctrl != NULL) { audio_dev_del_control(ctrl->actrl_ctrl); ctrl->actrl_ctrl = NULL; } } const char * ac97_control_name(ac97_ctrl_t *ctrl) { return (ctrl->actrl_name); } const audio_ctrl_desc_t * ac97_control_desc(ac97_ctrl_t *ctrl) { return (&ctrl->actrl_desc); } void ac97_register_controls(ac97_t *ac) { ac97_ctrl_t *ctrl; for (ctrl = list_head(&ac->ctrls); ctrl; ctrl = list_next(&ac->ctrls, ctrl)) { if (ctrl->actrl_suppress) continue; ac97_control_register(ctrl); } } void ac97_walk_controls(ac97_t *ac, ac97_ctrl_walk_t walker, void *arg) { ac97_ctrl_t *ctrl; for (ctrl = list_head(&ac->ctrls); ctrl; ctrl = list_next(&ac->ctrls, ctrl)) { if (!(*walker)(ctrl, arg)) { break; } } } void ac_add_control(ac97_t *ac, ac97_ctrl_probe_t *cpt) { ac97_ctrl_t *ctrl; boolean_t is_new; ASSERT(ac); ASSERT(ac->d); ctrl = ac97_control_find(ac, cpt->cp_name); if (ctrl != NULL) { is_new = B_FALSE; } else { ctrl = kmem_zalloc(sizeof (ac97_ctrl_t), KM_SLEEP); is_new = B_TRUE; } ctrl->actrl_ac97 = ac; ctrl->actrl_minval = cpt->cp_minval; ctrl->actrl_maxval = cpt->cp_maxval; ctrl->actrl_type = cpt->cp_type; ctrl->actrl_name = cpt->cp_name; ctrl->actrl_flags = cpt->cp_flags; if (cpt->cp_enum) { for (int e = 0; e < 64; e++) { if (cpt->cp_enum[e] == NULL) break; ctrl->actrl_enum[e] = cpt->cp_enum[e]; } } /* * Warning for extended controls this field gets changed * by audio_dev_add_control() to be a unique value. */ ctrl->actrl_initval = cpt->cp_initval; ctrl->actrl_muteable = cpt->cp_muteable; ctrl->actrl_write_fn = cpt->cp_write_fn; ctrl->actrl_bits = cpt->cp_bits; /* * Not that it can not be referenced until it is in the * list. So again by adding to the list last we avoid the need * for locks. */ if (is_new) list_insert_tail(&ac->ctrls, ctrl); } /* * De-Register and free up a control */ void ac97_control_remove(ac97_ctrl_t *ctrl) { ac97_t *ac = ctrl->actrl_ac97; list_remove(&ac->ctrls, ctrl); if (ctrl->actrl_ctrl != NULL) audio_dev_del_control(ctrl->actrl_ctrl); kmem_free(ctrl, sizeof (ac97_ctrl_t)); } /* * This is the master list of all controls known and handled by * the AC97 framework. This is the list used to probe, allocate * and configure controls. If a control is not in this list it * will not be handled. If a control is in this list but does not * have a probe routine then it will always be included. If a * control in list has a probe routine then it must return true * for that control to be included. */ #define MONCTL (AC97_FLAGS | AUDIO_CTRL_FLAG_MONITOR) #define PLAYCTL (AC97_FLAGS | AUDIO_CTRL_FLAG_PLAY) #define RECCTL (AC97_FLAGS | AUDIO_CTRL_FLAG_REC) #define T3DCTL (AC97_FLAGS | AUDIO_CTRL_FLAG_3D) #define TONECTL (AC97_FLAGS | AUDIO_CTRL_FLAG_TONE) #define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL) #define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL) #define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL) #define MONVOL (MONCTL | AUDIO_CTRL_FLAG_MONVOL) ac97_ctrl_probe_t ctrl_probe_tbl[] = { /* Master PCM Volume */ {AUDIO_CTRL_ID_VOLUME, INIT_VAL_MAIN, 0, 100, AUDIO_CTRL_TYPE_MONO, PCMVOL, PCMOVR_MUTE, ac97_master_set, NULL, 5}, /* LINE out volume */ {AUDIO_CTRL_ID_LINEOUT, INIT_VAL_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, MAINVOL, 0x8080, ac97_lineout_set, ac_probe_lineout, 6}, /* Front volume */ {AUDIO_CTRL_ID_FRONT, INIT_VAL_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, MAINVOL, 0x8080, ac97_lineout_set, ac_probe_front, 6}, /* 4CH out volume (has one of three possible uses, first use) */ {AUDIO_CTRL_ID_SURROUND, INIT_VAL_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, MAINVOL, 0x8080, ac97_surround_set, ac_probe_rear, 6}, /* ALT out volume (has one of three possible uses, second use) */ {AUDIO_CTRL_ID_HEADPHONE, INIT_VAL_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, MAINVOL, 0x8080, ac97_headphone_set, ac_probe_headphone, 6}, /* ALT out volume (has one of three possible uses, third use) */ {AUDIO_CTRL_ID_AUX1OUT, INIT_VAL_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, MAINVOL, 0x8080, ac97_aux1out_set, ac_probe_auxout, 6}, /* center out volume */ {AUDIO_CTRL_ID_CENTER, INIT_VAL_MN, 0, 100, AUDIO_CTRL_TYPE_MONO, MAINVOL, EXLFEVR_CENTER_MUTE, ac_center_set, ac_probe_center, 6}, /* LFE out volume (sub-woofer) */ {AUDIO_CTRL_ID_LFE, INIT_VAL_MN, 0, 100, AUDIO_CTRL_TYPE_MONO, MAINVOL, EXLFEVR_LFE_MUTE, ac_lfe_set, ac_probe_lfe, 6}, /* MONO out volume */ {AUDIO_CTRL_ID_SPEAKER, INIT_VAL_MN, 0, 100, AUDIO_CTRL_TYPE_MONO, MAINVOL, MMVR_MUTE, ac_speaker_set, ac_probe_mono, 6}, /* Record in GAIN */ {AUDIO_CTRL_ID_RECGAIN, INIT_IGAIN_ST, 0, 100, AUDIO_CTRL_TYPE_STEREO, RECVOL, RGR_MUTE, ac_recgain_set, NULL, -4}, /* MIC in volume */ {AUDIO_CTRL_ID_MIC, 0, 0, 100, AUDIO_CTRL_TYPE_STEREO, MONVOL, MICVR_MUTE, ac_mic_set, ac_probe_mic, 5}, /* LINE in volume */ {AUDIO_CTRL_ID_LINEIN, 0, 0, 100, AUDIO_CTRL_TYPE_STEREO, MONVOL, LIVR_MUTE, ac_linein_set, ac_probe_linein, 5}, /* CD in volume */ {AUDIO_CTRL_ID_CD, 0, 0, 100, AUDIO_CTRL_TYPE_STEREO, MONVOL, CDVR_MUTE, ac_cd_set, ac_probe_cdrom, 5}, /* VIDEO in volume */ {AUDIO_CTRL_ID_VIDEO, 0, 0, 100, AUDIO_CTRL_TYPE_STEREO, MONVOL, VIDVR_MUTE, ac_video_set, ac_probe_video, 5}, /* AUX in volume */ {AUDIO_CTRL_ID_AUX1IN, 0, 0, 100, AUDIO_CTRL_TYPE_STEREO, MONVOL, AUXVR_MUTE, ac_auxin_set, ac_probe_auxin, 5}, /* PHONE in volume */ {AUDIO_CTRL_ID_PHONE, 0, 0, 100, AUDIO_CTRL_TYPE_MONO, MONVOL, PVR_MUTE, ac_phone_set, ac_probe_phone, 5}, /* PC BEEPER in volume (motherboard speaker pins) */ {AUDIO_CTRL_ID_BEEP, INIT_VAL_MN, 0, 100, AUDIO_CTRL_TYPE_MONO, AC97_RW, PCBR_MUTE, ac_pcbeep_set, ac_probe_pcbeep, 4}, /* BASS out level (note, zero is hardware bypass) */ {AUDIO_CTRL_ID_BASS, 0, 0, 100, AUDIO_CTRL_TYPE_MONO, TONECTL, 0, ac_bass_set, ac_probe_tone, 4}, /* TREBLE out level (note, zero is hardware bypass) */ {AUDIO_CTRL_ID_TREBLE, 0, 0, 100, AUDIO_CTRL_TYPE_MONO, TONECTL, 0, ac_treble_set, ac_probe_tone, 4}, /* Loudness on/off switch */ {AUDIO_CTRL_ID_LOUDNESS, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN, TONECTL, 0, ac_loudness_set, ac_probe_loud, 0}, /* 3D depth out level */ {AUDIO_CTRL_ID_3DDEPTH, 0, 0, 100, AUDIO_CTRL_TYPE_MONO, T3DCTL, 0, ac_3ddepth_set, ac_probe_3d_depth, 4}, /* 3D center out level */ {AUDIO_CTRL_ID_3DCENT, 0, 0, 100, AUDIO_CTRL_TYPE_MONO, T3DCTL, 0, ac_3dcent_set, ac_probe_3d_center, 4}, /* 3D enhance on/off switch */ {AUDIO_CTRL_ID_3DENHANCE, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN, T3DCTL, 0, ac_3donoff_set, ac_probe_3d, 0}, /* MIC BOOST switch */ {AUDIO_CTRL_ID_MICBOOST, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN, RECCTL, 0, ac97_micboost_set, ac_probe_mic, 0}, /* Loopback on/off switch */ {AUDIO_CTRL_ID_LOOPBACK, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN, AC97_RW, 0, ac_loopback_set, NULL, 0}, /* * The following selectors *must* come after the others, as they rely * on the probe results of other controls. */ /* record src select (only one port at a time) */ {AUDIO_CTRL_ID_RECSRC, (1U << INPUT_MIC), 0, 0, AUDIO_CTRL_TYPE_ENUM, RECCTL, 0, ac_insrc_set, NULL, 0, ac_insrcs}, /* Start of non-standard private controls */ /* Simulated stereo on/off switch */ {AUDIO_CTRL_ID_STEREOSIM, 0, 0, 1, AUDIO_CTRL_TYPE_BOOLEAN, AC97_RW, 0, ac_stsim_set, ac_probe_stsim, 0}, /* mono MIC GAIN */ {AUDIO_CTRL_ID_MICGAIN, INIT_IGAIN_MN, 0, 100, AUDIO_CTRL_TYPE_MONO, RECCTL, RGMR_MUTE, ac_monomic_set, ac_probe_mmic, -4}, /* MIC select switch 0=mic1 1=mic2 */ {AUDIO_CTRL_ID_MICSRC, 1, 3, 3, AUDIO_CTRL_TYPE_ENUM, RECCTL, 0, ac_selmic_set, ac_probe_mic, 0, ac_mics}, /* MONO out src select 0=mix 1=mic */ {AUDIO_CTRL_ID_SPKSRC, 1, 3, 3, AUDIO_CTRL_TYPE_ENUM, AC97_RW, 0, ac_monosrc_set, ac_probe_mono, 0, ac_monos}, {NULL} }; /* * Probe all possible controls and register existing * ones and set initial values * * Returns zero on success */ static void ac_probeinit_ctrls(ac97_t *ac, int vol_bits, int enh_bits) { ac97_ctrl_probe_t *cpt; ac97_ctrl_probe_t my_cpt; ASSERT(ac); /* * Set some ports which are always present. */ ac->inputs = (1U << INPUT_STEREOMIX) | (1U << INPUT_MONOMIX); for (cpt = &ctrl_probe_tbl[0]; cpt->cp_name != NULL; cpt++) { bcopy(cpt, &my_cpt, sizeof (my_cpt)); if (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_RECSRC) == 0) { my_cpt.cp_minval |= ac->inputs; my_cpt.cp_maxval |= ac->inputs; } if (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_MICBOOST) == 0) { if (ac->flags & AC97_FLAG_MICBOOST) my_cpt.cp_initval = 1; } if ((strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_FRONT) == 0) || (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_HEADPHONE) == 0) || (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_SURROUND) == 0) || (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_SPEAKER) == 0)) { my_cpt.cp_bits = vol_bits; } if ((strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_3DDEPTH) == 0) || (strcmp(my_cpt.cp_name, AUDIO_CTRL_ID_3DCENT) == 0)) { my_cpt.cp_bits = enh_bits; } if (!my_cpt.cp_probe || my_cpt.cp_probe(ac)) { ac_add_control(ac, &my_cpt); } } if (ac->codec_init != NULL) { ac->codec_init(ac); } } /* * Allocate an AC97 instance for use by a hardware driver. * * returns an allocated and initialize ac97 structure. */ ac97_t * ac97_alloc(dev_info_t *dip, ac97_rd_t rd, ac97_wr_t wr, void *priv) { ac97_t *ac; ac = kmem_zalloc(sizeof (ac97_t), KM_SLEEP); ac->dip = dip; ac->rd = rd; ac->wr = wr; ac->private = priv; list_create(&ac->ctrls, sizeof (struct ac97_ctrl), offsetof(struct ac97_ctrl, actrl_linkage)); #define PROP_FLAG(prop, flag, def) \ if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, \ (prop), (def))) { \ ac->flags |= (flag); \ } else { \ ac->flags &= ~(flag); \ } /* * Engage the external amplifier by default, suppress with * a property of the form "ac97-amplifier=0". */ PROP_FLAG(AC97_PROP_AMPLIFIER, AC97_FLAG_AMPLIFIER, 1); /* * We cannot necessarily know if the headphone jack is present * or not. There's a technique to probe the codec for * headphone support, but many vendors seem to simply hang the * headphone jack on the line out circuit, and have some kind * of jack sense detection to enable or disable it by default. * None of this is visible in the AC'97 registers. * * We cannot do much about it, but what we can do is offer users * a way to suppress the option for a headphone port. Users and * administrators can then set a flag in the driver.conf to suppress * the option from display. * * It turns out that this problem exists for other signals as * well. */ PROP_FLAG(AC97_PROP_NO_HEADPHONE, AC97_FLAG_NO_HEADPHONE, 0); PROP_FLAG(AC97_PROP_NO_AUXOUT, AC97_FLAG_NO_AUXOUT, 0); PROP_FLAG(AC97_PROP_NO_CDROM, AC97_FLAG_NO_CDROM, 0); PROP_FLAG(AC97_PROP_NO_AUXIN, AC97_FLAG_NO_AUXIN, 0); PROP_FLAG(AC97_PROP_NO_VIDEO, AC97_FLAG_NO_VIDEO, 0); PROP_FLAG(AC97_PROP_NO_LINEIN, AC97_FLAG_NO_LINEIN, 0); PROP_FLAG(AC97_PROP_NO_MIC, AC97_FLAG_NO_MIC, 0); /* * Most SPARC systems use the AC97 monoaural output for the * built-in speaker. On these systems, we want to expose and * enable the built-in speaker by default. * * On most x86 systems, the mono output is not connected to * anything -- the AC'97 spec makes it pretty clear that the * output was actually intended for use with speaker phones. * So on those systems, we really don't want to activate the * speaker -- we don't even want to expose it's presence * normally. * * However, there could be an exception to the rule here. To * facilitate this, we allow for the presence of the property * to indicate that the speaker should be exposed. Whether it * is enabled by default or not depends on the value of the * property. (Generally on SPARC, we enable by default. On * other systems we do not.) */ #ifdef __sparc ac->flags |= AC97_FLAG_SPEAKER_OK; PROP_FLAG(AC97_PROP_SPEAKER, AC97_FLAG_SPEAKER, 1); #else if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, AC97_PROP_SPEAKER)) { ac->flags |= AC97_FLAG_SPEAKER_OK; PROP_FLAG(AC97_PROP_SPEAKER, AC97_FLAG_SPEAKER, 0); } #endif /* * Enable microphone boost (20dB normally) by default? */ PROP_FLAG(AC97_PROP_MICBOOST, AC97_FLAG_MICBOOST, 0); return (ac); } /* * Allocate an AC97 instance for use by a hardware driver. * * returns an allocated and initialize ac97 structure. */ ac97_t * ac97_allocate(audio_dev_t *adev, dev_info_t *dip, ac97_rd_t rd, ac97_wr_t wr, void *priv) { ac97_t *ac; ac = ac97_alloc(dip, rd, wr, priv); if (ac != NULL) { ac->d = adev; } return (ac); } /* * Free an AC97 instance. */ void ac97_free(ac97_t *ac) { ac97_ctrl_t *ctrl; /* Clear out any controls that are still attached */ while ((ctrl = list_head(&ac->ctrls)) != NULL) { ac97_control_remove(ctrl); } list_destroy(&ac->ctrls); kmem_free(ac, sizeof (ac97_t)); } static struct vendor { unsigned id; const char *name; } vendors[] = { { AC97_VENDOR_ADS, "Analog Devices" }, { AC97_VENDOR_AKM, "Asahi Kasei" }, { AC97_VENDOR_ALC, "Realtek" }, { AC97_VENDOR_ALG, "Avance Logic" }, { AC97_VENDOR_CMI, "C-Media" }, { AC97_VENDOR_CRY, "Cirrus Logic" }, { AC97_VENDOR_CXT, "Conexant" }, { AC97_VENDOR_EMC, "eMicro" }, { AC97_VENDOR_ESS, "ESS Technology" }, { AC97_VENDOR_EV, "Ectiva" }, { AC97_VENDOR_HRS, "Intersil" }, { AC97_VENDOR_ICE, "ICEnsemble" }, { AC97_VENDOR_ITE, "ITE, Inc." }, { AC97_VENDOR_NSC, "National Semiconductor" }, { AC97_VENDOR_PSC, "Philips Semiconductor" }, { AC97_VENDOR_SIL, "Silicon Laboratories" }, { AC97_VENDOR_ST, "SigmaTel" }, { AC97_VENDOR_TRA, "TriTech", }, { AC97_VENDOR_TXN, "Texas Instruments", }, { AC97_VENDOR_VIA, "VIA Technologies" }, { AC97_VENDOR_WML, "Wolfson" }, { AC97_VENDOR_YMH, "Yamaha" }, { 0, NULL }, }; static struct codec { unsigned id; const char *name; int enh_bits; void (*init)(ac97_t *ac); void (*reset)(ac97_t *ac); } codecs[] = { { AC97_CODEC_AK4540, "AK4540" }, { AC97_CODEC_STAC9700, "STAC9700" }, { AC97_CODEC_STAC9701, "STAC9701" }, { AC97_CODEC_STAC9701_2, "STAC9701" }, { AC97_CODEC_STAC9704, "STAC9704" }, { AC97_CODEC_STAC9705, "STAC9705" }, { AC97_CODEC_STAC9721, "STAC9721" }, { AC97_CODEC_STAC9708, "STAC9708", 2 }, { AC97_CODEC_STAC9744, "STAC9744" }, { AC97_CODEC_STAC9750, "STAC9750", 3 }, { AC97_CODEC_STAC9752, "STAC9752", 3 }, { AC97_CODEC_STAC9756, "STAC9756", 3 }, { AC97_CODEC_STAC9758, "STAC9758", 3 }, { AC97_CODEC_STAC9766, "STAC9766", 3 }, { AC97_CODEC_TR28028, "TR28028" }, { AC97_CODEC_TR28028_2, "TR28028" }, { AC97_CODEC_TR28023, "TR28023" }, { AC97_CODEC_TR28023_2, "TR28023" }, { AC97_CODEC_EM28028, "EM28028" }, { AC97_CODEC_CX20468, "CX20468" }, { AC97_CODEC_CX20468_2, "CX20468" }, { AC97_CODEC_CX20468_21, "CX20468-21" }, { AC97_CODEC_CS4297, "CS4297" }, { AC97_CODEC_CS4297A, "CS4297A" }, { AC97_CODEC_CS4294, "CS4294" }, { AC97_CODEC_CS4299, "CS4299" }, { AC97_CODEC_CS4202, "CS4202" }, { AC97_CODEC_CS4205, "CS4205" }, { AC97_CODEC_AD1819B, "AD1819B" }, { AC97_CODEC_AD1881, "AD1881" }, { AC97_CODEC_AD1881A, "AD1881A" }, { AC97_CODEC_AD1885, "AD1885" }, { AC97_CODEC_AD1886, "AD1886" }, { AC97_CODEC_AD1887, "AD1887" }, { AC97_CODEC_AD1888, "AD1888" }, { AC97_CODEC_AD1980, "AD1980" }, { AC97_CODEC_AD1981, "AD1981" }, /* no data sheet */ { AC97_CODEC_AD1981A, "AD1981A", 0, ad1981a_init }, { AC97_CODEC_AD1981B, "AD1981B", 0, ad1981b_init }, { AC97_CODEC_AD1985, "AD1985" }, { AC97_CODEC_WM9701A, "WM9701A" }, { AC97_CODEC_WM9703, "WM9703" }, { AC97_CODEC_WM9704, "WM9704" }, { AC97_CODEC_ES1921, "ES1921" }, { AC97_CODEC_ICE1232, "ICE1232/VT1611A" }, { AC97_CODEC_LM4550, "LM4550" }, { AC97_CODEC_VT1612A, "VT1612A" }, { AC97_CODEC_VT1616, "VT1616" }, { AC97_CODEC_VT1616A, "VT1616A" }, { AC97_CODEC_VT1617A, "VT1617A" }, { AC97_CODEC_VT1618, "VT1618" }, { AC97_CODEC_ALC100, "ALC100", 2 }, { AC97_CODEC_ALC200P, "ALC200P", 2 }, { AC97_CODEC_ALC202, "ALC202", 2 }, { AC97_CODEC_ALC203, "ALC203", 2 }, { AC97_CODEC_ALC250, "ALC250", 2 }, { AC97_CODEC_ALC250_2, "ALC250", 2 }, { AC97_CODEC_ALC650, "ALC650", 2, alc650_init }, { AC97_CODEC_ALC655, "ALC655", 2, alc650_init }, { AC97_CODEC_ALC658, "ALC658", 2, alc650_init }, { AC97_CODEC_ALC850, "ALC850", 2, alc850_init }, { AC97_CODEC_EV1938, "EV1938" }, { AC97_CODEC_CMI9738, "CMI9738", 0, cmi9738_init }, { AC97_CODEC_CMI9739, "CMI9739", 0, cmi9739_init }, { AC97_CODEC_CMI9780, "CMI9780" }, { AC97_CODEC_CMI9761, "CMI9761A", 0, cmi9761_init }, { AC97_CODEC_CMI9761_2, "CMI9761B", 0, cmi9761_init }, { AC97_CODEC_CMI9761_3, "CMI9761A+", 0, cmi9761_init }, { AC97_CODEC_YMF743, "YMF743" }, { AC97_CODEC_YMF753, "YMF753" }, { 0, NULL } }; void ac97_probe_controls(ac97_t *ac) { uint32_t vid1, vid2; uint16_t ear; const char *name = NULL; const char *vendor = NULL; int enh_bits; int vol_bits; uint32_t flags; char nmbuf[128]; char buf[128]; /* This is only valid when used with new style ac97_allocate(). */ ASSERT(ac->d); ac_analog_reset(ac); vid1 = RD(AC97_VENDOR_ID1_REGISTER); vid2 = RD(AC97_VENDOR_ID2_REGISTER); if (vid1 == 0xffff) { audio_dev_warn(ac->d, "AC'97 codec unresponsive"); return; } ac->vid = (vid1 << 16) | vid2; /* * Find out kind of codec we have and set any special case * settings needed. */ for (int i = 0; codecs[i].id; i++) { if (ac->vid == codecs[i].id) { name = codecs[i].name; enh_bits = codecs[i].enh_bits; ac->codec_init = codecs[i].init; break; } } for (int i = 0; vendors[i].id; i++) { if ((ac->vid & 0xffffff00) == vendors[i].id) { vendor = vendors[i].name; break; } } if (name == NULL) { (void) snprintf(nmbuf, sizeof (nmbuf), "0x%04x%04x", vid1, vid2); name = nmbuf; } if (vendor == NULL) { vendor = "Unknown"; } /* * Populate the initial shadow table. */ for (int i = 0; i < LAST_SHADOW_REG; i += sizeof (uint16_t)) { SHADOW(ac, i) = RD(i); } ac->caps = RD(AC97_RESET_REGISTER); enh_bits = 4; vol_bits = 6; flags = 0; /* detect the bit width of the master volume controls */ WR(AC97_MASTER_VOLUME_REGISTER, 0x20); if ((RD(AC97_MASTER_VOLUME_REGISTER) & 0x1f) == 0x1f) { vol_bits = 5; } /* * AC'97 2.3 spec indicates three possible uses for AUX_OUT * (aka LNLVL_OUT aka HP_OUT). We have to figure out which one * is in use. */ if (ac->caps & RR_HEADPHONE_SUPPORT) { /* it looks like it is probably headphones */ if (ac_probe_reg(ac, AC97_HEADPHONE_VOLUME_REGISTER)) { /* it is implemented */ ac->flags |= AC97_FLAG_AUX_HP; } } /* Read EAR just once. */ ear = RD(AC97_EXTENDED_AUDIO_REGISTER); /* * If not a headphone, is it 4CH_OUT (surround?) */ if ((!(ac->flags & AC97_FLAG_AUX_HP)) && (ear & EAR_SDAC)) { if (ac_probe_reg(ac, AC97_EXTENDED_LRS_VOLUME_REGISTER)) { ac->flags |= AC97_FLAG_AUX_4CH; } } /* * If neither, then maybe its an auxiliary line level output? */ if (!(ac->flags & (AC97_FLAG_AUX_HP | AC97_FLAG_AUX_4CH))) { if (ac_probe_reg(ac, AC97_HEADPHONE_VOLUME_REGISTER)) { ac->flags |= AC97_FLAG_AUX_LVL; } } /* * How many channels? */ ac->nchan = 2; if (ear & EAR_SDAC) { ac->nchan += 2; } if (ear & EAR_CDAC) { ac->nchan++; } if (ear & EAR_LDAC) { ac->nchan++; } ac->flags |= flags; (void) snprintf(ac->name, sizeof (ac->name), "%s %s", vendor, name); (void) snprintf(buf, sizeof (buf), "AC'97 codec: %s", ac->name); audio_dev_add_info(ac->d, buf); cmn_err(CE_CONT, "?%s#%d: AC'97 codec id %s (%x, %d channels, caps %x)\n", ddi_driver_name(ac->dip), ddi_get_instance(ac->dip), ac->name, ac->vid, ac->nchan, ac->caps); /* * Probe and register all known controls with framework */ ac_probeinit_ctrls(ac, vol_bits, enh_bits); ac_hw_reset(ac); ac_init_values(ac); } /* * Init the actual hardware related to a previously allocated instance * of an AC97 device. This is a legacy function and should not be * used in new code. * * Return zero on success. */ int ac97_init(ac97_t *ac, struct audio_dev *d) { /* Make sure we aren't using this with new style ac97_allocate(). */ ASSERT(ac->d == NULL); /* Save audio framework instance structure */ ac->d = d; ac97_probe_controls(ac); ac97_register_controls(ac); return (0); }