From mboxrd@z Thu Jan 1 00:00:00 1970 From: lamikr Subject: [PATCH] Alsa modularisations and support for tsc2101 7/7 Date: Mon, 20 Feb 2006 20:28:56 +0200 Message-ID: <43FA0A68.4020000@cc.jyu.fi> Reply-To: lamikr@cc.jyu.fi Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="------------040208090207000909000404" Return-path: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-omap-open-source-bounces@linux.omap.com Errors-To: linux-omap-open-source-bounces@linux.omap.com To: OMAP-Linux List-Id: linux-omap@vger.kernel.org This is a multi-part message in MIME format. --------------040208090207000909000404 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit ALSA Omap Patch This patch creates the alsa mixer for tsc2101 codec. signed-off by Mika Laitio signed-off by Daniel Petrini ---------------- Mika Laitio --------------040208090207000909000404 Content-Type: text/x-patch; name="alsa7-20060214.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="alsa7-20060214.patch" ALSA Omap Patch This patch only created the alsa mixer for tsc2101 codec. signed-off by Mika Laitio signed-off by Daniel Petrini Index: linux-omap-2.6.git-q/sound/arm/omap/omap-alsa-tsc2101-mixer.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-omap-2.6.git-q/sound/arm/omap/omap-alsa-tsc2101-mixer.c 2006-02-16 09:07:02.000000000 -0400 @@ -0,0 +1,894 @@ +/* + * sound/arm/omap/omap-alsa-tsc2101-mixer.c + * + * Alsa Driver for TSC2101 codec for OMAP platform boards. + * + * Copyright (C) 2005 Mika Laitio and + * Everett Coleman II + * + * Board initialization code is based on the code in TSC2101 OSS driver. + * Copyright (C) 2004 Texas Instruments, Inc. + * Written by Nishanth Menon and Sriram Kannan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: + * + * 2006-02-10 Mika Laitio - Mixer for the tsc2101 driver used in omap boards. + * Can switch between headset and loudspeaker playback, + * mute and unmute dgc, set dgc volume. Record source switch, + * keyclick, buzzer and headset volume and handset volume control + * are still missing. + * + */ + +#include <../arch/arm/mach-omap1/omap-alsa-tsc2101.h> +#include "omap-alsa-tsc2101-mixer.h" + +#include +#include +#include + +//#define M_DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS) +#define M_DPRINTK(ARGS...) /* nop */ + +#define DGC_DALVL_EXTRACT(ARG) ((ARG & 0x7f00) >> 8) +#define DGC_DARVL_EXTRACT(ARG) ((ARG & 0x007f)) +#define GET_DGC_DALMU_BIT_VALUE(ARG) (((ARG) & TSC2101_BIT(15)) >> 15) +#define GET_DGC_DARMU_BIT_VALUE(ARG) (((ARG) & TSC2101_BIT(7)) >> 7) +#define IS_DGC_DALMU_UNMUTED(ARG) (((GET_DGC_DALMU_BIT_VALUE(ARG)) == 0)) +#define IS_DGC_DARMU_UNMUTED(ARG) (((GET_DGC_DARMU_BIT_VALUE(ARG)) == 0)) + +#define HGC_ADPGA_HED_EXTRACT(ARG) ((ARG & 0x7f00) >> 8) +#define GET_DGC_HGCMU_BIT_VALUE(ARG) (((ARG) & TSC2101_BIT(15)) >> 15) +#define IS_DGC_HGCMU_UNMUTED(ARG) (((GET_DGC_HGCMU_BIT_VALUE(ARG)) == 0)) + +#define HNGC_ADPGA_HND_EXTRACT(ARG) ((ARG & 0x7f00) >> 8) +#define GET_DGC_HNGCMU_BIT_VALUE(ARG) (((ARG) & TSC2101_BIT(15)) >> 15) +#define IS_DGC_HNGCMU_UNMUTED(ARG) (((GET_DGC_HNGCMU_BIT_VALUE(ARG)) == 0)) + +static int current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER; +static int current_rec_src = REC_SRC_SINGLE_ENDED_MICIN_HED; + +/* + * Used for switching between TSC2101 recourd sources. + * Logic is adjusted from the TSC2101 OSS code. + */ +static int set_record_source(int val) +{ + u16 data; + int maskedVal; + + FN_IN; + maskedVal = 0xe0 & val; + +/* + // If more than one recording device selected, disable the device that is currently in use. + // NOTE: This does not take account differential input selections! + if (hweight32(maskedVal) > 1) { + maskedVal &= ~current_rec_src; + } +*/ + data = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_MIXER_PGA_CTRL); + data &= ~MPC_MICSEL(7); /* clear all MICSEL bits */ + data |= maskedVal; + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_MIXER_PGA_CTRL, + data); + current_rec_src = val; + + FN_OUT(0); + return 0; +} + +/* + * Converts the Alsa mixer volume (0 - 100) to real + * Digital Gain Control (DGC) value that can be written + * or read from the TSC2101 registry. + * + * Note that the number "OUTPUT_VOLUME_MAX" is smaller than OUTPUT_VOLUME_MIN + * because DGC works as a volume decreaser. (The more bigger value is put + * to DGC, the more the volume of controlled channel is decreased) + * + * In addition the TCS2101 chip would allow the maximum volume reduction be 63.5 DB + * but according to some tests user can not hear anything with this chip + * when the volume is set to be less than 25 db. + * Therefore this function will return a value that means 38.5 db (63.5 db - 25 db) + * reduction in the channel volume, when mixer is set to 0. + * For mixer value 100, this will return a value that means 0 db volume reduction. + * ([mute_left_bit]0000000[mute_right_bit]0000000) +*/ +int get_mixer_volume_as_dac_gain_control_volume(int vol) +{ + u16 retVal; + + /* Convert 0 -> 100 volume to 0x7F(min) -> y(max) volume range */ + retVal = ((vol * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MAX; + /* invert the value for getting the proper range 0 min and 100 max */ + retVal = OUTPUT_VOLUME_MIN - retVal; + + return retVal; +} + +/* + * Converts the Alsa mixer volume (0 - 100) to TSC2101 + * Digital Gain Control (DGC) volume. Alsa mixer volume 0 + * is converted to value meaning the volume reduction of -38.5 db + * and Alsa mixer volume 100 is converted to value meaning the + * reduction of 0 db. + */ +int set_mixer_volume_as_dac_gain_control_volume(int mixerVolL, int mixerVolR) +{ + u16 val; + int retVal; + int volL; + int volR; + + if ((mixerVolL < 0) || + (mixerVolL > 100) || + (mixerVolR < 0) || + (mixerVolR > 100)) { + printk(KERN_ERR "Trying a bad mixer volume as dac gain control volume value, left (%d), right (%d)!\n", mixerVolL, mixerVolR); + return -EPERM; + } + M_DPRINTK("mixer volume left = %d, right = %d\n", mixerVolL, mixerVolR); + volL = get_mixer_volume_as_dac_gain_control_volume(mixerVolL); + volR = get_mixer_volume_as_dac_gain_control_volume(mixerVolR); + + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL); + // keep the old mute bit settings + val &= ~(DGC_DALVL(OUTPUT_VOLUME_MIN) | DGC_DARVL(OUTPUT_VOLUME_MIN)); + val |= DGC_DALVL(volL) | DGC_DARVL(volR); + retVal = 2; + if (retVal) { + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_DAC_GAIN_CTRL, + val); + } + M_DPRINTK("to registry: left = %d, right = %d, total = %d\n", DGC_DALVL_EXTRACT(val), DGC_DARVL_EXTRACT(val), val); + return retVal; +} + +int dac_gain_control_unmute_control(int muteLeft, int muteRight) +{ + u16 val; + int count; + + count = 0; + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL); + // in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off, 0 --> on + // so if values are same, it's time to change the registry value. + if (muteLeft == GET_DGC_DALMU_BIT_VALUE(val)) { + if (muteLeft == 0) { + val = val | DGC_DALMU; // mute --> turn bit on + } + else { + val = val & ~DGC_DALMU; // unmute --> turn bit off + } + count++; + } /* L */ + if (muteRight == GET_DGC_DARMU_BIT_VALUE(val)) { + if (muteRight == 0) { + val = val | DGC_DARMU; // mute --> turn bit on + } + else { + val = val & ~DGC_DARMU; // unmute --> turn bit off + } + count++; + } /* R */ + if (count) { + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL, val); + M_DPRINTK("changed value, is_unmuted left = %d, right = %d\n", + IS_DGC_DALMU_UNMUTED(val), + IS_DGC_DARMU_UNMUTED(val)); + } + return count; +} + +/* + * Converts the DGC registry value read from the TSC2101 registry to + * Alsa mixer volume format (0 - 100). + */ +int get_dac_gain_control_volume_as_mixer_volume(u16 vol) +{ + u16 retVal; + + retVal = OUTPUT_VOLUME_MIN - vol; + retVal = ((retVal - OUTPUT_VOLUME_MAX) * 100) / OUTPUT_VOLUME_RANGE; + /* fix scaling error */ + if ((retVal > 0) && (retVal < 100)) { + retVal++; + } + return retVal; +} + +/* + * Converts the headset gain control volume (0 - 63.5 db) + * to Alsa mixer volume (0 - 100) + */ +int get_headset_gain_control_volume_as_mixer_volume(u16 registerVal) +{ + u16 retVal; + + retVal = ((registerVal * 100) / INPUT_VOLUME_RANGE); + return retVal; +} + +/* + * Converts the handset gain control volume (0 - 63.5 db) + * to Alsa mixer volume (0 - 100) + */ +int get_handset_gain_control_volume_as_mixer_volume(u16 registerVal) +{ + return get_headset_gain_control_volume_as_mixer_volume(registerVal); +} + +/* + * Converts the Alsa mixer volume (0 - 100) to + * headset gain control volume (0 - 63.5 db) + */ +int get_mixer_volume_as_headset_gain_control_volume(u16 mixerVal) +{ + u16 retVal; + + retVal = ((mixerVal * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + return retVal; +} + +/* + * Writes Alsa mixer volume (0 - 100) to TSC2101 headset volume registry in + * a TSC2101 format. (0 - 63.5 db) + * In TSC2101 OSS driver this functionality was controlled with "SET_LINE" parameter. + */ +int set_mixer_volume_as_headset_gain_control_volume(int mixerVol) +{ + int volume; + int retVal; + u16 val; + + if (mixerVol < 0 || mixerVol > 100) { + M_DPRINTK("Trying a bad headset mixer volume value(%d)!\n", mixerVol); + return -EPERM; + } + M_DPRINTK("mixer volume = %d\n", mixerVol); + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = get_mixer_volume_as_headset_gain_control_volume(mixerVol); + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL); + // preserve the old mute settings + val &= ~(HGC_ADPGA_HED(INPUT_VOLUME_MAX)); + val |= HGC_ADPGA_HED(volume); + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL, + val); + retVal = 1; + + M_DPRINTK("to registry = %d\n", val); + return retVal; +} + +/* + * Writes Alsa mixer volume (0 - 100) to TSC2101 handset volume registry in + * a TSC2101 format. (0 - 63.5 db) + * In TSC2101 OSS driver this functionality was controlled with "SET_MIC" parameter. + */ +int set_mixer_volume_as_handset_gain_control_volume(int mixerVol) +{ + int volume; + int retVal; + u16 val; + + if (mixerVol < 0 || mixerVol > 100) { + M_DPRINTK("Trying a bad mic mixer volume value(%d)!\n", mixerVol); + return -EPERM; + } + M_DPRINTK("mixer volume = %d\n", mixerVol); + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = get_mixer_volume_as_headset_gain_control_volume(mixerVol); + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_HANDSET_GAIN_CTRL); + // preserve the old mute settigns + val &= ~(HNGC_ADPGA_HND(INPUT_VOLUME_MAX)); + val |= HNGC_ADPGA_HND(volume); + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HANDSET_GAIN_CTRL, + val); + retVal = 1; + + M_DPRINTK("to registry = %d\n", val); + return retVal; +} + +void init_record_sources(void) +{ + // Mute Analog Sidetone + // analog sidetone gain db? + // Cell Phone In not connected to ADC + // Input selected by MICSEL connected to ADC + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_MIXER_PGA_CTRL, + MPC_ASTMU | MPC_ASTG(0x40) | ~MPC_CPADC | MPC_MICADC); + // Set record source, Select MIC_INHED input for headset + set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HED); +} + +void set_loudspeaker_to_playback_target(void) +{ + u16 val; + + // power down sp1, sp2 and loudspeaker + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_CODEC_POWER_CTRL, + CPC_SP1PWDN | CPC_SP2PWDN | CPC_LDAPWDF); + // ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled + // 1dB AGC hysteresis + // MICes bias 2V + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_4, + AC4_MB_HED(0)); + + // Set codec output volume +/* + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_DAC_GAIN_CTRL, + 0x0000); +*/ + // DAC left and right routed to SPK1/SPK2 + // SPK1/SPK2 unmuted + // keyclicks routed to SPK1/SPK2 + val = AC5_DIFFIN | + AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 | + AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 | + AC5_HDSCPTC; + val = val & ~AC5_HDSCPTC; + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_5, + val); + + // powerdown spk1/out32n and spk2 + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_POWERDOWN_STS); + val = val & ~(~PS_SPK1FL | ~PS_HNDFL | PS_LSPKFL); + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_POWERDOWN_STS, + val); + + // routing selected to SPK1 goes to OUT8P/OUT84 alsa. (loudspeaker) + // analog sidetone routed to loudspeaker + // buzzer pga routed to loudspeaker + // keyclick routing to loudspeaker + // cellphone input routed to loudspeaker + // mic selection (control register 04h/page2) routed to cell phone output (CP_OUT) + // routing selected for SPK1 goes also to cellphone output (CP_OUT) + // OUT8P/OUT8N (loudspeakers) unmuted (0 = unmuted) + // Cellphone output is not muted (0 = unmuted) + // Enable loudspeaker short protection control (0 = enable protection) + // VGND short protection control (0 = enable protection) + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_6, + AC6_SPL2LSK | AC6_AST2LSK | AC6_BUZ2LSK | AC6_KCL2LSK | + AC6_CPI2LSK | AC6_MIC2CPO | AC6_SPL2CPO | + ~AC6_MUTLSPK | ~AC6_MUTSPK2 | ~AC6_LDSCPTC | ~AC6_VGNDSCPTC); + current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER; +} + +void set_headphone_to_playback_target(void) +{ + //power down sp1, sp2 and loudspeaker + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_CODEC_POWER_CTRL, + CPC_SP1PWDN | CPC_SP2PWDN | CPC_LDAPWDF); + /* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */ + /* 1dB AGC hysteresis */ + /* MICes bias 2V */ + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_4, + AC4_MB_HED(0)); + + /* Set codec output volume */ +/* + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_DAC_GAIN_CTRL, + 0x0000); +*/ + /* DAC left and right routed to SPK2 */ + /* SPK1/2 unmuted */ + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_5, + AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 | + AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 | + AC5_HDSCPTC); + + /* OUT8P/N muted, CPOUT muted */ + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_6, + AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC | + AC6_VGNDSCPTC); + current_playback_target = PLAYBACK_TARGET_HEADPHONE; +} + +/* + * Checks whether the headset is detected. + * If headset is detected, the type is returned. Type can be + * 0x01 = stereo headset detected + * 0x02 = cellurar headset detected + * 0x03 = stereo + cellurar headset detected + * If headset is not detected 0 is returned. + */ +u16 get_headset_detected(void) +{ + u16 curDetected; + u16 curType; + u16 curVal; + + curType = 0; // not detected; + curVal = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_7); + curDetected = curVal & AC7_HDDETFL; + if (curDetected) { + printk("headset detected, checking type from %d \n", curVal); + curType = ((curVal & 0x6000) >> 13); + printk("headset type detected = %d \n", curType); + } + else { + printk("headset not detected\n"); + } + return curType; +} + +void init_playback_targets(void) +{ + u16 val; + + set_loudspeaker_to_playback_target(); + /* Left line input volume control */ + // = SET_LINE in the OSS driver + set_mixer_volume_as_headset_gain_control_volume(DEFAULT_INPUT_VOLUME); + + // Set headset to be controllable by handset mixer + // AGC enable for handset input + // Handset input not muted + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HANDSET_GAIN_CTRL); + val = val | HNGC_AGCEN_HND; + val = val & ~HNGC_ADMUT_HND; + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HANDSET_GAIN_CTRL, + val); + + /* mic input volume control + SET_MIC in the OSS driver + */ + set_mixer_volume_as_handset_gain_control_volume(DEFAULT_INPUT_VOLUME); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + set_mixer_volume_as_dac_gain_control_volume(DEFAULT_OUTPUT_VOLUME, DEFAULT_OUTPUT_VOLUME); + dac_gain_control_unmute_control(1, 1); // unmute +} + +/* + * Initializes tsc2101 recourd source (to line) and playback target (to loudspeaker) + */ +void snd_omap_init_mixer(void) +{ + FN_IN; + + /* Headset/Hook switch detect enabled */ + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_AUDIO_CTRL_7, + AC7_DETECT); + + init_record_sources(); + init_playback_targets(); + + FN_OUT(0); +} + +static int __pcm_playback_target_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + static char *texts[PLAYBACK_TARGET_COUNT] = { + "Loudspeaker", "Headphone" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = PLAYBACK_TARGET_COUNT; + if (uinfo->value.enumerated.item > PLAYBACK_TARGET_COUNT - 1) { + uinfo->value.enumerated.item = PLAYBACK_TARGET_COUNT - 1; + } + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int __pcm_playback_target_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + ucontrol->value.integer.value[0] = current_playback_target; + return 0; +} + +static int __pcm_playback_target_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + int retVal; + int curVal; + + retVal = 0; + curVal = ucontrol->value.integer.value[0]; + if ((curVal >= 0) && + (curVal < PLAYBACK_TARGET_COUNT) && + (curVal != current_playback_target)) { + if (curVal == 0) { + set_loudspeaker_to_playback_target(); + } + else { + set_headphone_to_playback_target(); + } + retVal = 1; + } + return retVal; +} + +static int __pcm_playback_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +/* + * Alsa mixer interface function for getting the volume read from the DGC in a + * 0 -100 alsa mixer format. + */ +static int __pcm_playback_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 volL; + u16 volR; + u16 val; + + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL); + M_DPRINTK("registry value = %d!\n", val); + volL = DGC_DALVL_EXTRACT(val); + volR = DGC_DARVL_EXTRACT(val); + volL = volL & ~DGC_DALMU; // make sure that other bits are not on + volR = volR & ~DGC_DARMU; + + volL = get_dac_gain_control_volume_as_mixer_volume(volL); + volR = get_dac_gain_control_volume_as_mixer_volume(volR); + + ucontrol->value.integer.value[0] = volL; // L + ucontrol->value.integer.value[1] = volR; // R + + M_DPRINTK("mixer volume left = %ld, right = %ld\n", ucontrol->value.integer.value[0], ucontrol->value.integer.value[1]); + return 0; +} + +static int __pcm_playback_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + return set_mixer_volume_as_dac_gain_control_volume(ucontrol->value.integer.value[0], + ucontrol->value.integer.value[1]); +} + +static int __pcm_playback_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/* + * When DGC_DALMU (bit 15) is 1, the left channel is muted. + * When DGC_DALMU is 0, left channel is not muted. + * Same logic apply also for the right channel. + */ +static int __pcm_playback_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL); + + ucontrol->value.integer.value[0] = IS_DGC_DALMU_UNMUTED(val); + ucontrol->value.integer.value[1] = IS_DGC_DARMU_UNMUTED(val); + return 0; +} + +static int __pcm_playback_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + return dac_gain_control_unmute_control(ucontrol->value.integer.value[0], + ucontrol->value.integer.value[1]); +/* + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL); + int count = 0; + + // in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off, 0 --> on + // so if values are same, it's time to change the registry value. + if (ucontrol->value.integer.value[0] == GET_DGC_DALMU_BIT_VALUE(val)) { + if (ucontrol->value.integer.value[0] == 0) { + val = val | DGC_DALMU; // mute --> turn bit on + } + else { + val = val & ~DGC_DALMU; // unmute --> turn bit off + } + count++; + } // L + if (ucontrol->value.integer.value[1] == GET_DGC_DARMU_BIT_VALUE(val)) { + if (ucontrol->value.integer.value[1] == 0) { + val = val | DGC_DARMU; // mute --> turn bit on + } + else { + val = val & ~DGC_DARMU; // unmute --> turn bit off + } + count++; + } // R + if (count) { + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_DAC_GAIN_CTRL, val); + M_DPRINTK("changed value, is_unmuted left = %d, right = %d\n", + IS_DGC_DALMU_UNMUTED(val), + IS_DGC_DARMU_UNMUTED(val)); + } + return count; +*/ +} + +static int __headset_playback_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int __headset_playback_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 val; + u16 vol; + + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL); + M_DPRINTK("registry value = %d\n", val); + vol = HGC_ADPGA_HED_EXTRACT(val); + vol = vol & ~HGC_ADMUT_HED; + + vol = get_headset_gain_control_volume_as_mixer_volume(vol); + ucontrol->value.integer.value[0] = vol; + + M_DPRINTK("mixer volume returned = %ld\n", ucontrol->value.integer.value[0]); + return 0; +} + +static int __headset_playback_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + return set_mixer_volume_as_headset_gain_control_volume(ucontrol->value.integer.value[0]); +} + +static int __headset_playback_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/* When HGC_ADMUT_HED (bit 15) is 1, the headset is muted. + * When HGC_ADMUT_HED is 0, headset is not muted. + */ +static int __headset_playback_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL); + ucontrol->value.integer.value[0] = IS_DGC_HGCMU_UNMUTED(val); + return 0; +} + +static int __headset_playback_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + int count = 0; + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL); + // in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off, 0 --> on + // so if values are same, it's time to change the registry value... + if (ucontrol->value.integer.value[0] == GET_DGC_HGCMU_BIT_VALUE(val)) { + if (ucontrol->value.integer.value[0] == 0) { + val = val | HGC_ADMUT_HED; // mute --> turn bit on + } + else { + val = val & ~HGC_ADMUT_HED; // unmute --> turn bit off + } + count++; + M_DPRINTK("changed value, is_unmuted = %d\n", IS_DGC_HGCMU_UNMUTED(val)); + } + if (count) { + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HEADSET_GAIN_CTRL, + val); + } + return count; +} + +static int __handset_playback_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int __handset_playback_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 val; + u16 vol; + + val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_HANDSET_GAIN_CTRL); + M_DPRINTK("registry value = %d\n", val); + vol = HNGC_ADPGA_HND_EXTRACT(val); + vol = vol & ~HNGC_ADMUT_HND; + vol = get_handset_gain_control_volume_as_mixer_volume(vol); + ucontrol->value.integer.value[0] = vol; + + M_DPRINTK("mixer volume returned = %ld\n", ucontrol->value.integer.value[0]); + return 0; +} + +static int __handset_playback_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + return set_mixer_volume_as_handset_gain_control_volume(ucontrol->value.integer.value[0]); +} + +static int __handset_playback_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/* When HNGC_ADMUT_HND (bit 15) is 1, the handset is muted. + * When HNGC_ADMUT_HND is 0, handset is not muted. + */ +static int __handset_playback_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_HANDSET_GAIN_CTRL); + ucontrol->value.integer.value[0] = IS_DGC_HNGCMU_UNMUTED(val); + return 0; +} + +static int __handset_playback_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol) +{ + int count = 0; + u16 val = omap_tsc2101_read(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, TSC2101_HANDSET_GAIN_CTRL); + + // in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off, 0 --> on + // so if values are same, it's time to change the registry value... + if (ucontrol->value.integer.value[0] == GET_DGC_HNGCMU_BIT_VALUE(val)) { + if (ucontrol->value.integer.value[0] == 0) { + val = val | HNGC_ADMUT_HND; // mute --> turn bit on + } + else { + val = val & ~HNGC_ADMUT_HND; // unmute --> turn bit off + } + M_DPRINTK("changed value, is_unmuted = %d\n", IS_DGC_HNGCMU_UNMUTED(val)); + count++; + } + if (count) { + omap_tsc2101_write(TSC2101_AUDIO_CODEC_REGISTERS_PAGE2, + TSC2101_HANDSET_GAIN_CTRL, + val); + } + return count; +} + +static snd_kcontrol_new_t tsc2101_control[] __devinitdata = { + { + .name = "Playback Playback Route", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_target_info, + .get = __pcm_playback_target_get, + .put = __pcm_playback_target_put, + }, { + .name = "Master Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_volume_info, + .get = __pcm_playback_volume_get, + .put = __pcm_playback_volume_put, + }, { + .name = "Master Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __pcm_playback_switch_info, + .get = __pcm_playback_switch_get, + .put = __pcm_playback_switch_put, + }, { + .name = "Headset Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 1, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __headset_playback_volume_info, + .get = __headset_playback_volume_get, + .put = __headset_playback_volume_put, + }, { + .name = "Headset Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 1, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __headset_playback_switch_info, + .get = __headset_playback_switch_get, + .put = __headset_playback_switch_put, + }, { + .name = "Handset Playback Volume", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 2, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __handset_playback_volume_info, + .get = __handset_playback_volume_get, + .put = __handset_playback_volume_put, + }, { + .name = "Handset Playback Switch", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 2, + .access= SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = __handset_playback_switch_info, + .get = __handset_playback_switch_get, + .put = __handset_playback_switch_put, + } +}; + +#ifdef CONFIG_PM + +void snd_omap_suspend_mixer(void) +{ +} + +void snd_omap_resume_mixer(void) +{ + snd_omap_init_mixer(); +} +#endif + +int snd_omap_mixer(struct snd_card_omap_codec *tsc2101) +{ + int i=0; + int err=0; + + if (!tsc2101) { + return -EINVAL; + } + for (i=0; i < ARRAY_SIZE(tsc2101_control); i++) { + if ((err = snd_ctl_add(tsc2101->card, + snd_ctl_new1(&tsc2101_control[i], + tsc2101->card))) < 0) { + return err; + } + } + return 0; +} Index: linux-omap-2.6.git-q/sound/arm/omap/omap-alsa-tsc2101-mixer.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-omap-2.6.git-q/sound/arm/omap/omap-alsa-tsc2101-mixer.h 2006-02-16 09:07:06.000000000 -0400 @@ -0,0 +1,79 @@ +/* + * sound/arm/omap/omap-alsa-tsc2101-mixer.c + * + * Alsa Driver for TSC2101 codec for OMAP platform boards. + * + * Copyright (C) 2005 Mika Laitio and + * Everett Coleman II + * + * Based on the ideas in omap-aic23.c and sa11xx-uda1341.c + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Copyright (C) 2002 Tomas Kasparek + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: + * + * 2006-02-10 Mika Laitio - Mixer for the tsc2101 driver used in omap boards. + * Can switch between headset and loudspeaker playback, + * mute and unmute dgc, set dgc volume. Record source switch, + * keyclick, buzzer and headset volume and handset volume control + * are still missing. + */ + +#ifndef OMAPALSATSC2101MIXER_H_ +#define OMAPALSATSC2101MIXER_H_ + +#include +#include <../drivers/ssi/omap-tsc2101.h> +#include "omap-alsa-dma.h" + +/* tsc2101 DAC gain control volume specific */ +#define OUTPUT_VOLUME_MIN 0x7F // 1111111 = -63.5 DB +#define OUTPUT_VOLUME_MAX 0x32 // 110010 +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) + +/* use input vol of 75 for 0dB gain */ +#define INPUT_VOLUME_MIN 0x0 +#define INPUT_VOLUME_MAX 0x7D +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) + +#define PLAYBACK_TARGET_COUNT 0x02 +#define PLAYBACK_TARGET_LOUDSPEAKER 0x00 +#define PLAYBACK_TARGET_HEADPHONE 0x01 + +/* following are used for register 03h Mixer PGA control bits D7-D5 for selecting record source */ +#define REC_SRC_TARGET_COUNT 0x08 +#define REC_SRC_SINGLE_ENDED_MICIN_HED MPC_MICSEL(0) // oss code referred to MIXER_LINE +#define REC_SRC_SINGLE_ENDED_MICIN_HND MPC_MICSEL(1) // oss code referred to MIXER_MIC +#define REC_SRC_SINGLE_ENDED_AUX1 MPC_MICSEL(2) +#define REC_SRC_SINGLE_ENDED_AUX2 MPC_MICSEL(3) +#define REC_SRC_MICIN_HED_AND_AUX1 MPC_MICSEL(4) +#define REC_SRC_MICIN_HED_AND_AUX2 MPC_MICSEL(5) +#define REC_SRC_MICIN_HND_AND_AUX1 MPC_MICSEL(6) +#define REC_SRC_MICIN_HND_AND_AUX2 MPC_MICSEL(7) + +#define DEFAULT_OUTPUT_VOLUME 90 // default output volume to dac dgc +#define DEFAULT_INPUT_VOLUME 20 // default record volume + +#define TSC2101_AUDIO_CODEC_REGISTERS_PAGE2 (2) + +#endif /*OMAPALSATSC2101MIXER_H_*/ --------------040208090207000909000404 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline --------------040208090207000909000404--