From: Tony Lindgren Date: Wed, 22 Mar 2006 16:06:12 +0000 (-0800) Subject: Merge source.mvista.com:/home/git/linux-omap-2.6 X-Git-Tag: v2.6.16-omap1~20 X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=37fbceefd6290a47ceb8f8547b4ddac18da8cb4f;hp=adee4c097cd2759ec78ab2bc914905a8c56ff365;p=linux-2.6-omap-h63xx.git Merge source.mvista.com:/home/git/linux-omap-2.6 --- diff --git a/arch/arm/mach-omap1/board-h2.c b/arch/arm/mach-omap1/board-h2.c index 4364d51ebd6..51634ce12cb 100644 --- a/arch/arm/mach-omap1/board-h2.c +++ b/arch/arm/mach-omap1/board-h2.c @@ -40,8 +40,9 @@ #include #include #include -#include #include +#include +#include extern int omap_gpio_init(void); @@ -285,6 +286,41 @@ static struct platform_device h2_lcd_device = { .id = -1, }; +static struct omap_mcbsp_reg_cfg mcbsp_regs = { + .spcr2 = FREE | FRST | GRST | XRST | XINTM(3), + .spcr1 = RINTM(3) | RRST, + .rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) | + RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(1), + .rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16), + .xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) | + XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(1) | XFIG, + .xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16), + .srgr1 = FWID(15), + .srgr2 = GSYNC | CLKSP | FSGM | FPER(31), + + .pcr0 = CLKXM | CLKRM | FSXP | FSRP | CLKXP | CLKRP, + //.pcr0 = CLKXP | CLKRP, /* mcbsp: slave */ +}; + +static struct omap_alsa_codec_config alsa_config = { + .name = "H2 TSC2101", + .mcbsp_regs_alsa = &mcbsp_regs, + .codec_configure_dev = NULL, // tsc2101_configure, + .codec_set_samplerate = NULL, // tsc2101_set_samplerate, + .codec_clock_setup = NULL, // tsc2101_clock_setup, + .codec_clock_on = NULL, // tsc2101_clock_on, + .codec_clock_off = NULL, // tsc2101_clock_off, + .get_default_samplerate = NULL, // tsc2101_get_default_samplerate, +}; + +static struct platform_device h2_mcbsp1_device = { + .name = "omap_alsa_mcbsp", + .id = 1, + .dev = { + .platform_data = &alsa_config, + }, +}; + static struct platform_device *h2_devices[] __initdata = { &h2_nor_device, &h2_nand_device, @@ -292,6 +328,7 @@ static struct platform_device *h2_devices[] __initdata = { &h2_irda_device, &h2_kp_device, &h2_lcd_device, + &h2_mcbsp1_device, }; static void __init h2_init_smc91x(void) diff --git a/arch/arm/mach-omap1/board-osk.c b/arch/arm/mach-omap1/board-osk.c index 56c8a4b12bb..76248bbc512 100644 --- a/arch/arm/mach-omap1/board-osk.c +++ b/arch/arm/mach-omap1/board-osk.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include static int osk_keymap[] = { KEY(0, 0, KEY_F1), @@ -149,9 +151,40 @@ static struct platform_device osk5912_cf_device = { .resource = osk5912_cf_resources, }; +#define DEFAULT_BITPERSAMPLE 16 + +static struct omap_mcbsp_reg_cfg mcbsp_regs = { + .spcr2 = FREE | FRST | GRST | XRST | XINTM(3), + .spcr1 = RINTM(3) | RRST, + .rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) | + RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(0), + .rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16), + .xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) | + XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(0) | XFIG, + .xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16), + .srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1), + .srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1), + /*.pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP,*/ /* mcbsp: master */ + .pcr0 = CLKXP | CLKRP, /* mcbsp: slave */ +}; + +static struct omap_alsa_codec_config alsa_config = { + .name = "OSK AIC23", + .mcbsp_regs_alsa = &mcbsp_regs, + .codec_configure_dev = NULL, // aic23_configure, + .codec_set_samplerate = NULL, // aic23_set_samplerate, + .codec_clock_setup = NULL, // aic23_clock_setup, + .codec_clock_on = NULL, // aic23_clock_on, + .codec_clock_off = NULL, // aic23_clock_off, + .get_default_samplerate = NULL, // aic23_get_default_samplerate, +}; + static struct platform_device osk5912_mcbsp1_device = { - .name = "omap_mcbsp", - .id = 1, + .name = "omap_alsa_mcbsp", + .id = 1, + .dev = { + .platform_data = &alsa_config, + }, }; static struct resource osk5912_kp_resources[] = { diff --git a/sound/arm/omap-aic23.h b/include/asm-arm/arch-omap/omap-alsa.h similarity index 58% rename from sound/arm/omap-aic23.h rename to include/asm-arm/arch-omap/omap-alsa.h index c8ab42c2935..b4ec45a84e6 100644 --- a/sound/arm/omap-aic23.h +++ b/include/asm-arm/arch-omap/omap-alsa.h @@ -1,7 +1,9 @@ /* - * sound/arm/omap-aic23.h + * linux/include/asm-arm/arch-omap/omap-alsa.h * - * Alsa Driver for AIC23 codec on OSK5912 platform board + * Alsa Driver for AIC23 and TSC2101 codecs on OMAP platform boards. + * + * Copyright (C) 2006 Mika Laitio * * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil * Written by Daniel Petrini, David Cohen, Anderson Briglia @@ -33,39 +35,38 @@ * 2005/07/25 INdT-10LE Kernel Team - Alsa driver for omap osk, * original version based in sa1100 driver * and omap oss driver. - * - * 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu */ -#ifndef __OMAP_AIC23_H -#define __OMAP_AIC23_H +#ifndef __OMAP_ALSA_H +#define __OMAP_ALSA_H #include #include #include #include +#include +#include +/* + * Debug functions + */ +#undef DEBUG +//#define DEBUG + +#define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS); + +#ifdef DEBUG +#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS) +#define ADEBUG() printk("XXX Alsa debug f:%s, l:%d\n", __FUNCTION__, __LINE__) +#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__) +#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n) +#else +#define DPRINTK(ARGS...) /* nop */ +#define ADEBUG() /* nop */ +#define FN_IN /* nop */ +#define FN_OUT(n) /* nop */ +#endif -#define DEFAULT_OUTPUT_VOLUME 0x60 -#define DEFAULT_INPUT_VOLUME 0x00 /* 0 ==> mute line in */ - -#define OUTPUT_VOLUME_MIN LHV_MIN -#define OUTPUT_VOLUME_MAX LHV_MAX -#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN) -#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX - -#define INPUT_VOLUME_MIN LIV_MIN -#define INPUT_VOLUME_MAX LIV_MAX -#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) -#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX - -#define SIDETONE_MASK 0x1c0 -#define SIDETONE_0 0x100 -#define SIDETONE_6 0x000 -#define SIDETONE_9 0x040 -#define SIDETONE_12 0x080 -#define SIDETONE_18 0x0c0 - -#define DEFAULT_ANALOG_AUDIO_CONTROL DAC_SELECTED | STE_ENABLED | BYPASS_ON | INSEL_MIC | MICB_20DB +#define DMA_BUF_SIZE (1024 * 8) /* * Buffer management for alsa and dma @@ -93,38 +94,51 @@ struct audio_stream { /* * Alsa card structure for aic23 */ -struct snd_card_omap_aic23 { +struct snd_card_omap_codec { snd_card_t *card; snd_pcm_t *pcm; long samplerate; struct audio_stream s[2]; /* playback & capture */ }; -/*********** Function Prototypes *************************/ +/* Codec specific information and function pointers. + * Codec (omap-alsa-aic23.c and omap-alsa-tsc2101.c) + * are responsible for defining the function pointers. + */ +struct omap_alsa_codec_config { + char *name; + struct omap_mcbsp_reg_cfg *mcbsp_regs_alsa; + snd_pcm_hw_constraint_list_t *hw_constraints_rates; + snd_pcm_hardware_t *snd_omap_alsa_playback; + snd_pcm_hardware_t *snd_omap_alsa_capture; + void (*codec_configure_dev)(void); + void (*codec_set_samplerate)(long); + void (*codec_clock_setup)(void); + int (*codec_clock_on)(void); + int (*codec_clock_off)(void); + int (*get_default_samplerate)(void); +}; -void audio_dma_callback(void *); -int snd_omap_mixer(struct snd_card_omap_aic23 *); +/*********** Mixer function prototypes *************************/ +int snd_omap_mixer(struct snd_card_omap_codec *); void snd_omap_init_mixer(void); -/* Clock functions */ -int omap_aic23_clock_on(void); -int omap_aic23_clock_off(void); #ifdef CONFIG_PM void snd_omap_suspend_mixer(void); void snd_omap_resume_mixer(void); #endif -/* Codec AIC23 */ -#if defined(CONFIG_SENSORS_TLV320AIC23) || defined (CONFIG_SENSORS_TLV320AIC23_MODULE) - -extern int tlv320aic23_write_value(u8 reg, u16 value); - -/* TLV320AIC23 is a write only device */ -static __inline__ void audio_aic23_write(u8 address, u16 data) -{ - tlv320aic23_write_value(address, data); -} +int snd_omap_alsa_post_probe(struct platform_device *pdev, struct omap_alsa_codec_config *config); +int snd_omap_alsa_remove(struct platform_device *pdev); +#ifdef CONFIG_PM +int snd_omap_alsa_suspend(struct platform_device *pdev, pm_message_t state); +int snd_omap_alsa_resume(struct platform_device *pdev); +#else +#define snd_omap_alsa_suspend NULL +#define snd_omap_alsa_resume NULL +#endif -#endif /* CONFIG_SENSORS_TLV320AIC23 */ +/*********** function prototype to function called from the dma interrupt handler ******/ +void callback_omap_alsa_sound_dma(void *); #endif diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig index cf071547a13..cccdf23e8e6 100644 --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@ -44,5 +44,19 @@ config SND_OMAP_AIC23 To compile this driver as a module, choose M here: the module will be called snd-omap-aic23. + +config SND_OMAP_TSC2101 + tristate "OMAP TSC2101 alsa driver" + depends on ARCH_OMAP && SND + select SND_PCM + select OMAP_TSC2101 + select OMAP_UWIRE if ARCH_OMAP + help + Say Y here if you have a OMAP platform board + and want to use its TSC2101 audio chip. Driver has + been tested with H2 and iPAQ h6300. + + To compile this driver as a module, choose M here: the module + will be called snd-omap-tsc2101. endmenu diff --git a/sound/arm/Makefile b/sound/arm/Makefile index 019a9f03c2c..bd12f53404a 100644 --- a/sound/arm/Makefile +++ b/sound/arm/Makefile @@ -14,5 +14,4 @@ snd-pxa2xx-pcm-objs := pxa2xx-pcm.o obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o snd-pxa2xx-ac97-objs := pxa2xx-ac97.o -obj-$(CONFIG_SND_OMAP_AIC23) += snd-omap-aic23.o -snd-omap-aic23-objs := omap-aic23.o omap-alsa-dma.o omap-alsa-mixer.o +obj-$(CONFIG_SND) += omap/ diff --git a/sound/arm/omap-aic23.c b/sound/arm/omap-aic23.c deleted file mode 100644 index c92e24534f4..00000000000 --- a/sound/arm/omap-aic23.c +++ /dev/null @@ -1,929 +0,0 @@ -/* - * sound/arm/omap-aic23.c - * - * Alsa Driver for AIC23 codec on OSK5912 platform board - * - * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil - * Written by Daniel Petrini, David Cohen, Anderson Briglia - * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br - * - * Based on sa11xx-uda1341.c, - * 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: - * - * 2005-07-29 INdT Kernel Team - Alsa driver for omap osk. Creation of new - * file omap-aic23.c - * - * 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef CONFIG_PM -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "omap-alsa-dma.h" -#include "omap-aic23.h" - -#undef DEBUG - -#ifdef DEBUG -#define ADEBUG() printk("XXX Alsa debug f:%s, l:%d\n", __FUNCTION__, __LINE__) -#else -#define ADEBUG() /* nop */ -#endif - -/* Define to set the AIC23 as the master w.r.t McBSP */ -#define AIC23_MASTER - -/* - * AUDIO related MACROS - */ -#define DEFAULT_BITPERSAMPLE 16 -#define AUDIO_RATE_DEFAULT 44100 -#define AUDIO_MCBSP OMAP_MCBSP1 -#define NUMBER_SAMPLE_RATES_SUPPORTED 10 - - -MODULE_AUTHOR("Daniel Petrini, David Cohen, Anderson Briglia - INdT"); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("OMAP AIC23 driver for ALSA"); -MODULE_SUPPORTED_DEVICE("{{AIC23,OMAP AIC23}}"); -MODULE_ALIAS("omap_mcbsp.1"); - -static char *id = NULL; -MODULE_PARM_DESC(id, "OMAP OSK ALSA Driver for AIC23 chip."); - -static struct snd_card_omap_aic23 *omap_aic23 = NULL; - -static struct clk *aic23_mclk = 0; - -struct sample_rate_rate_reg_info { - u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ - u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ -}; - -/* - * DAC USB-mode sampling rates (MCLK = 12 MHz) - * The rates and rate_reg_into MUST be in the same order - */ -static unsigned int rates[] = { - 4000, 8000, 16000, 22050, - 24000, 32000, 44100, - 48000, 88200, 96000, -}; -static const struct sample_rate_rate_reg_info - rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { - {0x06, 1}, /* 4000 */ - {0x06, 0}, /* 8000 */ - {0x0C, 1}, /* 16000 */ - {0x11, 1}, /* 22050 */ - {0x00, 1}, /* 24000 */ - {0x0C, 0}, /* 32000 */ - {0x11, 0}, /* 44100 */ - {0x00, 0}, /* 48000 */ - {0x1F, 0}, /* 88200 */ - {0x0E, 0}, /* 96000 */ -}; - -/* - * mcbsp configuration structure - */ -static struct omap_mcbsp_reg_cfg initial_config_mcbsp = { - .spcr2 = FREE | FRST | GRST | XRST | XINTM(3), - .spcr1 = RINTM(3) | RRST, - .rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) | - RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(0), - .rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16), - .xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) | - XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(0) | XFIG, - .xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16), - .srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1), - .srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1), -#ifndef AIC23_MASTER - /* configure McBSP to be the I2S master */ - .pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP, -#else - /* configure McBSP to be the I2S slave */ - .pcr0 = CLKXP | CLKRP, -#endif /* AIC23_MASTER */ -}; - -static snd_pcm_hw_constraint_list_t hw_constraints_rates = { - .count = ARRAY_SIZE(rates), - .list = rates, - .mask = 0, -}; - -/* - * HW interface start and stop helper functions - */ -static int audio_ifc_start(void) -{ - omap_mcbsp_start(AUDIO_MCBSP); - return 0; -} - -static int audio_ifc_stop(void) -{ - omap_mcbsp_stop(AUDIO_MCBSP); - return 0; -} - -/* - * Codec/mcbsp init and configuration section - * codec dependent code. - */ - -/* - * Sample rate changing - */ -static void omap_aic23_set_samplerate(struct snd_card_omap_aic23 - *omap_aic23, long rate) -{ - u8 count = 0; - u16 data = 0; - - /* Fix the rate if it has a wrong value */ - if (rate >= 96000) - rate = 96000; - else if (rate >= 88200) - rate = 88200; - else if (rate >= 48000) - rate = 48000; - else if (rate >= 44100) - rate = 44100; - else if (rate >= 32000) - rate = 32000; - else if (rate >= 24000) - rate = 24000; - else if (rate >= 22050) - rate = 22050; - else if (rate >= 16000) - rate = 16000; - else if (rate >= 8000) - rate = 8000; - else - rate = 4000; - - /* Search for the right sample rate */ - /* Verify what happens if the rate is not supported - * now it goes to 96Khz */ - while ((rates[count] != rate) && - (count < (NUMBER_SAMPLE_RATES_SUPPORTED - 1))) { - count++; - } - - data = (rate_reg_info[count].divider << CLKIN_SHIFT) | - (rate_reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON; - - audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data); - - omap_aic23->samplerate = rate; -} - -static inline void aic23_configure(void) -{ - /* Reset codec */ - audio_aic23_write(RESET_CONTROL_ADDR, 0); - - /* Initialize the AIC23 internal state */ - - /* Analog audio path control, DAC selected, delete INSEL_MIC for line in */ - audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DEFAULT_ANALOG_AUDIO_CONTROL); - - /* Digital audio path control, de-emphasis control 44.1kHz */ - audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K); - - /* Digital audio interface, master/slave mode, I2S, 16 bit */ -#ifdef AIC23_MASTER - audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, - MS_MASTER | IWL_16 | FOR_DSP); -#else - audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP); -#endif - - /* Enable digital interface */ - audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); - -} - -static void omap_aic23_audio_init(struct snd_card_omap_aic23 *omap_aic23) -{ - /* Setup DMA stuff */ - omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Alsa AIC23 out"; - omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = - SNDRV_PCM_STREAM_PLAYBACK; - omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev = - OMAP_DMA_MCBSP1_TX; - omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].hw_start = - audio_ifc_start; - omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].hw_stop = - audio_ifc_stop; - - omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].id = "Alsa AIC23 in"; - omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = - SNDRV_PCM_STREAM_CAPTURE; - omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev = - OMAP_DMA_MCBSP1_RX; - omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].hw_start = - audio_ifc_start; - omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].hw_stop = - audio_ifc_stop; - - /* configuring the McBSP */ - omap_mcbsp_request(AUDIO_MCBSP); - - /* if configured, then stop mcbsp */ - omap_mcbsp_stop(AUDIO_MCBSP); - - omap_mcbsp_config(AUDIO_MCBSP, &initial_config_mcbsp); - omap_mcbsp_start(AUDIO_MCBSP); - aic23_configure(); -} - -/* - * DMA functions - * Depends on omap-aic23-dma.c functions and (omap) dma.c - * - */ -#define DMA_BUF_SIZE 1024 * 8 - -static int audio_dma_request(struct audio_stream *s, - void (*callback) (void *)) -{ - int err; - - err = omap_request_sound_dma(s->dma_dev, s->id, s, &s->lch); - if (err < 0) - printk(KERN_ERR "unable to grab audio dma 0x%x\n", - s->dma_dev); - return err; -} - -static int audio_dma_free(struct audio_stream *s) -{ - int err = 0; - - err = omap_free_sound_dma(s, &s->lch); - if (err < 0) - printk(KERN_ERR "Unable to free audio dma channels!\n"); - return err; -} - -/* - * This function should calculate the current position of the dma in the - * buffer. It will help alsa middle layer to continue update the buffer. - * Its correctness is crucial for good functioning. - */ -static u_int audio_get_dma_pos(struct audio_stream *s) -{ - snd_pcm_substream_t *substream = s->stream; - snd_pcm_runtime_t *runtime = substream->runtime; - unsigned int offset; - unsigned long flags; - dma_addr_t count; - ADEBUG(); - - /* this must be called w/ interrupts locked as requested in dma.c */ - spin_lock_irqsave(&s->dma_lock, flags); - - /* For the current period let's see where we are */ - count = omap_get_dma_src_addr_counter(s->lch[s->dma_q_head]); - - spin_unlock_irqrestore(&s->dma_lock, flags); - - /* Now, the position related to the end of that period */ - offset = bytes_to_frames(runtime, s->offset) - bytes_to_frames(runtime, count); - - if (offset >= runtime->buffer_size || offset < 0) - offset = 0; - - return offset; -} - -/* - * this stops the dma and clears the dma ptrs - */ -static void audio_stop_dma(struct audio_stream *s) -{ - unsigned long flags; - ADEBUG(); - - spin_lock_irqsave(&s->dma_lock, flags); - s->active = 0; - s->period = 0; - s->periods = 0; - - /* this stops the dma channel and clears the buffer ptrs */ - omap_audio_stop_dma(s); - - omap_clear_sound_dma(s); - - spin_unlock_irqrestore(&s->dma_lock, flags); -} - -/* - * Main dma routine, requests dma according where you are in main alsa buffer - */ -static void audio_process_dma(struct audio_stream *s) -{ - snd_pcm_substream_t *substream = s->stream; - snd_pcm_runtime_t *runtime; - unsigned int dma_size; - unsigned int offset; - int ret; - - runtime = substream->runtime; - if (s->active) { - dma_size = frames_to_bytes(runtime, runtime->period_size); - offset = dma_size * s->period; - snd_assert(dma_size <= DMA_BUF_SIZE,); - ret = - omap_start_sound_dma(s, - (dma_addr_t) runtime->dma_area + - offset, dma_size); - if (ret) { - printk(KERN_ERR - "audio_process_dma: cannot queue DMA buffer (%i)\n", - ret); - return; - } - - s->period++; - s->period %= runtime->periods; - s->periods++; - s->offset = offset; - } -} - -/* - * This is called when dma IRQ occurs at the end of each transmited block - */ -void audio_dma_callback(void *data) -{ - struct audio_stream *s = data; - - /* - * If we are getting a callback for an active stream then we inform - * the PCM middle layer we've finished a period - */ - if (s->active) - snd_pcm_period_elapsed(s->stream); - - spin_lock(&s->dma_lock); - if (s->periods > 0) { - s->periods--; - } - audio_process_dma(s); - spin_unlock(&s->dma_lock); -} - - -/* - * Alsa section - * PCM settings and callbacks - */ - -static int snd_omap_aic23_trigger(snd_pcm_substream_t * substream, int cmd) -{ - struct snd_card_omap_aic23 *chip = - snd_pcm_substream_chip(substream); - int stream_id = substream->pstr->stream; - struct audio_stream *s = &chip->s[stream_id]; - int err = 0; - ADEBUG(); - - /* note local interrupts are already disabled in the midlevel code */ - spin_lock(&s->dma_lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - /* requested stream startup */ - s->active = 1; - audio_process_dma(s); - break; - case SNDRV_PCM_TRIGGER_STOP: - /* requested stream shutdown */ - audio_stop_dma(s); - break; - default: - err = -EINVAL; - break; - } - spin_unlock(&s->dma_lock); - - return err; -} - -static int snd_omap_aic23_prepare(snd_pcm_substream_t * substream) -{ - struct snd_card_omap_aic23 *chip = - snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; - struct audio_stream *s = &chip->s[substream->pstr->stream]; - - /* set requested samplerate */ - omap_aic23_set_samplerate(chip, runtime->rate); - - s->period = 0; - s->periods = 0; - - return 0; -} - -static snd_pcm_uframes_t snd_omap_aic23_pointer(snd_pcm_substream_t * - substream) -{ - struct snd_card_omap_aic23 *chip = - snd_pcm_substream_chip(substream); - - return audio_get_dma_pos(&chip->s[substream->pstr->stream]); -} - -/* Hardware capabilities */ - -static snd_pcm_hardware_t snd_omap_aic23_capture = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), - .formats = (SNDRV_PCM_FMTBIT_S16_LE), - .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_KNOT), - .rate_min = 8000, - .rate_max = 96000, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 128 * 1024, - .period_bytes_min = 32, - .period_bytes_max = 8 * 1024, - .periods_min = 16, - .periods_max = 255, - .fifo_size = 0, -}; - -static snd_pcm_hardware_t snd_omap_aic23_playback = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), - .formats = (SNDRV_PCM_FMTBIT_S16_LE), - .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_KNOT), - .rate_min = 8000, - .rate_max = 96000, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 128 * 1024, - .period_bytes_min = 32, - .period_bytes_max = 8 * 1024, - .periods_min = 16, - .periods_max = 255, - .fifo_size = 0, -}; - -static int snd_card_omap_aic23_open(snd_pcm_substream_t * substream) -{ - struct snd_card_omap_aic23 *chip = - snd_pcm_substream_chip(substream); - snd_pcm_runtime_t *runtime = substream->runtime; - int stream_id = substream->pstr->stream; - int err; - ADEBUG(); - - chip->s[stream_id].stream = substream; - - omap_aic23_clock_on(); - - if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) - runtime->hw = snd_omap_aic23_playback; - else - runtime->hw = snd_omap_aic23_capture; - if ((err = - snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS)) < - 0) - return err; - if ((err = - snd_pcm_hw_constraint_list(runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &hw_constraints_rates)) < 0) - return err; - - return 0; -} - -static int snd_card_omap_aic23_close(snd_pcm_substream_t * substream) -{ - struct snd_card_omap_aic23 *chip = - snd_pcm_substream_chip(substream); - ADEBUG(); - - omap_aic23_clock_off(); - chip->s[substream->pstr->stream].stream = NULL; - - return 0; -} - -/* HW params & free */ - -static int snd_omap_aic23_hw_params(snd_pcm_substream_t * substream, - snd_pcm_hw_params_t * hw_params) -{ - return snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); -} - -static int snd_omap_aic23_hw_free(snd_pcm_substream_t * substream) -{ - return snd_pcm_lib_free_pages(substream); -} - -/* pcm operations */ - -static snd_pcm_ops_t snd_card_omap_aic23_playback_ops = { - .open = snd_card_omap_aic23_open, - .close = snd_card_omap_aic23_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_omap_aic23_hw_params, - .hw_free = snd_omap_aic23_hw_free, - .prepare = snd_omap_aic23_prepare, - .trigger = snd_omap_aic23_trigger, - .pointer = snd_omap_aic23_pointer, -}; - -static snd_pcm_ops_t snd_card_omap_aic23_capture_ops = { - .open = snd_card_omap_aic23_open, - .close = snd_card_omap_aic23_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_omap_aic23_hw_params, - .hw_free = snd_omap_aic23_hw_free, - .prepare = snd_omap_aic23_prepare, - .trigger = snd_omap_aic23_trigger, - .pointer = snd_omap_aic23_pointer, -}; - -/* - * Alsa init and exit section - * - * Inits pcm alsa structures, allocate the alsa buffer, suspend, resume - */ -static int __init snd_card_omap_aic23_pcm(struct snd_card_omap_aic23 - *omap_aic23, int device) -{ - snd_pcm_t *pcm; - int err; - ADEBUG(); - - if ((err = - snd_pcm_new(omap_aic23->card, "AIC23 PCM", device, 1, 1, - &pcm)) < 0) - return err; - - /* sets up initial buffer with continuous allocation */ - snd_pcm_lib_preallocate_pages_for_all(pcm, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data - (GFP_KERNEL), - 128 * 1024, 128 * 1024); - - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, - &snd_card_omap_aic23_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, - &snd_card_omap_aic23_capture_ops); - pcm->private_data = omap_aic23; - pcm->info_flags = 0; - strcpy(pcm->name, "omap aic23 pcm"); - - omap_aic23_audio_init(omap_aic23); - - /* setup DMA controller */ - audio_dma_request(&omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK], - audio_dma_callback); - audio_dma_request(&omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE], - audio_dma_callback); - - omap_aic23->pcm = pcm; - - return 0; -} - - -#ifdef CONFIG_PM - -static int snd_omap_aic23_suspend(snd_card_t * card, pm_message_t state) -{ - struct snd_card_omap_aic23 *chip = card->private_data; - ADEBUG(); - - if (chip->card->power_state != SNDRV_CTL_POWER_D3hot) { - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); - snd_pcm_suspend_all(chip->pcm); - /* Mutes and turn clock off */ - omap_aic23_clock_off(); - snd_omap_suspend_mixer(); - } - - return 0; -} - -/* - * Prepare hardware for resume - */ -static int snd_omap_aic23_resume(snd_card_t * card) -{ - struct snd_card_omap_aic23 *chip = card->private_data; - ADEBUG(); - - if (chip->card->power_state != SNDRV_CTL_POWER_D0) { - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); - omap_aic23_clock_on(); - snd_omap_resume_mixer(); - } - - return 0; -} - -/* - * Driver suspend/resume - calls alsa functions. Some hints from aaci.c - */ -static int omap_aic23_suspend(struct platform_device *pdev, pm_message_t state) -{ - snd_card_t *card = platform_get_drvdata(pdev); - - if (card->power_state != SNDRV_CTL_POWER_D3hot) { - snd_omap_aic23_suspend(card, PMSG_SUSPEND); - } - return 0; -} - -static int omap_aic23_resume(struct platform_device *pdev) -{ - snd_card_t *card = platform_get_drvdata(pdev); - - if (card->power_state != SNDRV_CTL_POWER_D0) { - snd_omap_aic23_resume(card); - } - return 0; -} - -#else -#define snd_omap_aic23_suspend NULL -#define snd_omap_aic23_resume NULL -#define omap_aic23_suspend NULL -#define omap_aic23_resume NULL - -#endif /* CONFIG_PM */ - -/* - */ -void snd_omap_aic23_free(snd_card_t * card) -{ - struct snd_card_omap_aic23 *chip = card->private_data; - ADEBUG(); - - /* - * Turn off codec after it is done. - * Can't do it immediately, since it may still have - * buffered data. - */ - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(2); - - omap_mcbsp_stop(AUDIO_MCBSP); - omap_mcbsp_free(AUDIO_MCBSP); - - audio_aic23_write(RESET_CONTROL_ADDR, 0); - audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0xff); - - audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); - audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); -} - -/* - * Omap MCBSP clock configuration - * - * Here we have some functions that allows clock to be enabled and - * disabled only when needed. Besides doing clock configuration - * it allows turn on/turn off audio when necessary. - */ -#define CODEC_CLOCK 12000000 -#define AUDIO_RATE_DEFAULT 44100 - -/* - * Do clock framework mclk search - */ -static __init void omap_aic23_clock_setup(void) -{ - aic23_mclk = clk_get(0, "mclk"); -} - -/* - * Do some sanity check, set clock rate, starts it and - * turn codec audio on - */ -int omap_aic23_clock_on(void) -{ - if (clk_get_usecount(aic23_mclk) > 0) { - /* MCLK is already in use */ - printk(KERN_WARNING - "MCLK in use at %d Hz. We change it to %d Hz\n", - (uint) clk_get_rate(aic23_mclk), - CODEC_CLOCK); - } - - if (clk_set_rate(aic23_mclk, CODEC_CLOCK)) { - printk(KERN_ERR - "Cannot set MCLK for AIC23 CODEC\n"); - return -ECANCELED; - } - - clk_enable(aic23_mclk); - - printk(KERN_DEBUG - "MCLK = %d [%d], usecount = %d\n", - (uint) clk_get_rate(aic23_mclk), CODEC_CLOCK, - clk_get_usecount(aic23_mclk)); - - /* Now turn the audio on */ - audio_aic23_write(POWER_DOWN_CONTROL_ADDR, - ~DEVICE_POWER_OFF & ~OUT_OFF & ~DAC_OFF & - ~ADC_OFF & ~MIC_OFF & ~LINE_OFF); - - return 0; -} -/* - * Do some sanity check, turn clock off and then turn - * codec audio off - */ -int omap_aic23_clock_off(void) -{ - if (clk_get_usecount(aic23_mclk) > 0) { - if (clk_get_rate(aic23_mclk) != CODEC_CLOCK) { - printk(KERN_WARNING - "MCLK for audio should be %d Hz. But is %d Hz\n", - (uint) clk_get_rate(aic23_mclk), - CODEC_CLOCK); - } - - clk_disable(aic23_mclk); - } - - audio_aic23_write(POWER_DOWN_CONTROL_ADDR, - DEVICE_POWER_OFF | OUT_OFF | DAC_OFF | - ADC_OFF | MIC_OFF | LINE_OFF); - return 0; -} - -/* module init & exit */ - -/* - * Inits alsa soudcard structure - */ -static int __init snd_omap_aic23_probe(struct platform_device *pdev) -{ - int err = 0; - snd_card_t *card; - ADEBUG(); - - /* gets clock from clock framework */ - omap_aic23_clock_setup(); - - /* register the soundcard */ - card = snd_card_new(-1, id, THIS_MODULE, sizeof(omap_aic23)); - if (card == NULL) - return -ENOMEM; - - omap_aic23 = kcalloc(1, sizeof(*omap_aic23), GFP_KERNEL); - if (omap_aic23 == NULL) - return -ENOMEM; - - card->private_data = (void *) omap_aic23; - card->private_free = snd_omap_aic23_free; - - omap_aic23->card = card; - omap_aic23->samplerate = AUDIO_RATE_DEFAULT; - - spin_lock_init(&omap_aic23->s[0].dma_lock); - spin_lock_init(&omap_aic23->s[1].dma_lock); - - /* mixer */ - if ((err = snd_omap_mixer(omap_aic23)) < 0) - goto nodev; - - /* PCM */ - if ((err = snd_card_omap_aic23_pcm(omap_aic23, 0)) < 0) - goto nodev; - - strcpy(card->driver, "AIC23"); - strcpy(card->shortname, "OSK AIC23"); - sprintf(card->longname, "OMAP OSK with AIC23"); - - snd_omap_init_mixer(); - - snd_card_set_dev(card, &pdev->dev); - - if ((err = snd_card_register(card)) == 0) { - printk(KERN_INFO "OSK audio support initialized\n"); - platform_set_drvdata(pdev, card); - return 0; - } - -nodev: - snd_omap_aic23_free(card); - - return err; -} - -static int snd_omap_aic23_remove(struct platform_device *pdev) -{ - snd_card_t *card = platform_get_drvdata(pdev); - struct snd_card_omap_aic23 *chip = card->private_data; - - snd_card_free(card); - - omap_aic23 = NULL; - card->private_data = NULL; - kfree(chip); - - platform_set_drvdata(pdev, NULL); - - return 0; - -} - -static struct platform_driver omap_alsa_driver = { - .probe = snd_omap_aic23_probe, - .remove = snd_omap_aic23_remove, - .suspend = omap_aic23_suspend, - .resume = omap_aic23_resume, - .driver = { - .name = "omap_mcbsp", - }, -}; - -static int __init omap_aic23_init(void) -{ - int err; - ADEBUG(); - - err = platform_driver_register(&omap_alsa_driver); - - return err; -} - -static void __exit omap_aic23_exit(void) -{ - ADEBUG(); - - platform_driver_unregister(&omap_alsa_driver); -} - -module_init(omap_aic23_init); -module_exit(omap_aic23_exit); diff --git a/sound/arm/omap/Makefile b/sound/arm/omap/Makefile new file mode 100644 index 00000000000..354c7b4aabf --- /dev/null +++ b/sound/arm/omap/Makefile @@ -0,0 +1,9 @@ +# +## Makefile for ALSA OMAP +# +# +obj-$(CONFIG_SND_OMAP_AIC23) += snd-omap-alsa-aic23.o +snd-omap-alsa-aic23-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-aic23.o omap-alsa-aic23-mixer.o + +obj-$(CONFIG_SND_OMAP_TSC2101) += snd-omap-alsa-tsc2101.o +snd-omap-alsa-tsc2101-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-tsc2101.o omap-alsa-tsc2101-mixer.o diff --git a/sound/arm/omap-alsa-mixer.c b/sound/arm/omap/omap-alsa-aic23-mixer.c similarity index 97% rename from sound/arm/omap-alsa-mixer.c rename to sound/arm/omap/omap-alsa-aic23-mixer.c index 742e5b627c4..1fb0160d1b2 100644 --- a/sound/arm/omap-alsa-mixer.c +++ b/sound/arm/omap/omap-alsa-aic23-mixer.c @@ -1,5 +1,5 @@ /* - * sound/arm/omap-alsa-mixer.c + * sound/arm/omap/omap-alsa-aic23-mixer.c * * Alsa Driver Mixer for generic codecs for omap boards * @@ -39,20 +39,10 @@ #include #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include #include -#include "omap-aic23.h" +#include +#include "omap-alsa-aic23.h" #include #include @@ -67,7 +57,7 @@ MODULE_DESCRIPTION("OMAP Alsa mixer driver for ALSA"); /* Codec AIC23 */ #if defined(CONFIG_SENSORS_TLV320AIC23) || defined (CONFIG_SENSORS_TLV320AIC23_MODULE) -extern __inline__ void audio_aic23_write(u8, u16); +extern void audio_aic23_write(u8, u16); #define MIXER_NAME "Mixer AIC23" #define SND_OMAP_WRITE(reg, val) audio_aic23_write(reg, val) @@ -411,31 +401,6 @@ static snd_kcontrol_new_t snd_omap_controls[] = { OMAP_MUX("Capture Source", ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, INSEL_MIC), }; -void snd_omap_init_mixer(void) -{ - u16 vol_reg; - - /* Line's default values */ - omap_regs[LINE_INDEX].l_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; - omap_regs[LINE_INDEX].r_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; - omap_regs[LINE_INDEX].sw = 0; - SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); - SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); - - /* Analog Audio Control's default values */ - omap_regs[AAC_INDEX].l_reg = DEFAULT_ANALOG_AUDIO_CONTROL; - - /* Headphone's default values */ - vol_reg = LZC_ON; - vol_reg &= ~OUTPUT_VOLUME_MASK; - vol_reg |= DEFAULT_OUTPUT_VOLUME; - omap_regs[PCM_INDEX].l_reg = DEFAULT_OUTPUT_VOLUME; - omap_regs[PCM_INDEX].r_reg = DEFAULT_OUTPUT_VOLUME; - omap_regs[PCM_INDEX].sw = 1; - SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, vol_reg); - SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, vol_reg); -} - #ifdef CONFIG_PM void snd_omap_suspend_mixer(void) @@ -474,7 +439,32 @@ void snd_omap_resume_mixer(void) } #endif -int snd_omap_mixer(struct snd_card_omap_aic23 *chip) +void snd_omap_init_mixer(void) +{ + u16 vol_reg; + + /* Line's default values */ + omap_regs[LINE_INDEX].l_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; + omap_regs[LINE_INDEX].r_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK; + omap_regs[LINE_INDEX].sw = 0; + SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); + SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK); + + /* Analog Audio Control's default values */ + omap_regs[AAC_INDEX].l_reg = DEFAULT_ANALOG_AUDIO_CONTROL; + + /* Headphone's default values */ + vol_reg = LZC_ON; + vol_reg &= ~OUTPUT_VOLUME_MASK; + vol_reg |= DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].l_reg = DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].r_reg = DEFAULT_OUTPUT_VOLUME; + omap_regs[PCM_INDEX].sw = 1; + SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, vol_reg); + SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, vol_reg); +} + +int snd_omap_mixer(struct snd_card_omap_codec *chip) { snd_card_t *card; unsigned int idx; @@ -493,4 +483,3 @@ int snd_omap_mixer(struct snd_card_omap_aic23 *chip) return 0; } - diff --git a/sound/arm/omap/omap-alsa-aic23.c b/sound/arm/omap/omap-alsa-aic23.c new file mode 100644 index 00000000000..f4906161819 --- /dev/null +++ b/sound/arm/omap/omap-alsa-aic23.c @@ -0,0 +1,321 @@ +/* + * arch/arm/mach-omap1/omap-alsa-aic23.c + * + * Alsa codec Driver for AIC23 chip on OSK5912 platform board + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * Based in former alsa driver for osk and oss driver + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "omap-alsa-aic23.h" + +static struct clk *aic23_mclk = 0; + +/* aic23 related */ +static const struct aic23_samplerate_reg_info + rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + {4000, 0x06, 1}, /* 4000 */ + {8000, 0x06, 0}, /* 8000 */ + {16000, 0x0C, 1}, /* 16000 */ + {22050, 0x11, 1}, /* 22050 */ + {24000, 0x00, 1}, /* 24000 */ + {32000, 0x0C, 0}, /* 32000 */ + {44100, 0x11, 0}, /* 44100 */ + {48000, 0x00, 0}, /* 48000 */ + {88200, 0x1F, 0}, /* 88200 */ + {96000, 0x0E, 0}, /* 96000 */ +}; + +/* + * Hardware capabilities + */ + + /* + * DAC USB-mode sampling rates (MCLK = 12 MHz) + * The rates and rate_reg_into MUST be in the same order + */ +static unsigned int rates[] = { + 4000, 8000, 16000, 22050, + 24000, 32000, 44100, + 48000, 88200, 96000, +}; + +static snd_pcm_hw_constraint_list_t aic23_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static snd_pcm_hardware_t aic23_snd_omap_alsa_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t aic23_snd_omap_alsa_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +/* + * Codec/mcbsp init and configuration section + * codec dependent code. + */ + +extern int tlv320aic23_write_value(u8 reg, u16 value); + +/* TLV320AIC23 is a write only device */ +void audio_aic23_write(u8 address, u16 data) +{ + tlv320aic23_write_value(address, data); +} +EXPORT_SYMBOL_GPL(audio_aic23_write); + +/* + * Sample rate changing + */ +void aic23_set_samplerate(long rate) +{ + u8 count = 0; + u16 data = 0; + + /* Fix the rate if it has a wrong value */ + if (rate >= 96000) + rate = 96000; + else if (rate >= 88200) + rate = 88200; + else if (rate >= 48000) + rate = 48000; + else if (rate >= 44100) + rate = 44100; + else if (rate >= 32000) + rate = 32000; + else if (rate >= 24000) + rate = 24000; + else if (rate >= 22050) + rate = 22050; + else if (rate >= 16000) + rate = 16000; + else if (rate >= 8000) + rate = 8000; + else + rate = 4000; + + /* Search for the right sample rate */ + /* Verify what happens if the rate is not supported + * now it goes to 96Khz */ + while ((rate_reg_info[count].sample_rate != rate) && + (count < (NUMBER_SAMPLE_RATES_SUPPORTED - 1))) { + count++; + } + + data = (rate_reg_info[count].divider << CLKIN_SHIFT) | + (rate_reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON; + + audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data); +} + +inline void aic23_configure(void) +{ + /* Reset codec */ + audio_aic23_write(RESET_CONTROL_ADDR, 0); + + /* Initialize the AIC23 internal state */ + + /* Analog audio path control, DAC selected, delete INSEL_MIC for line in */ + audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DEFAULT_ANALOG_AUDIO_CONTROL); + + /* Digital audio path control, de-emphasis control 44.1kHz */ + audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K); + + /* Digital audio interface, master/slave mode, I2S, 16 bit */ +#ifdef AIC23_MASTER + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, + MS_MASTER | IWL_16 | FOR_DSP); +#else + audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP); +#endif + + /* Enable digital interface */ + audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); +} + +/* + * Omap MCBSP clock configuration and Power Management + * + * Here we have some functions that allows clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * it allows turn on/turn off audio when necessary. + */ +/* + * Do clock framework mclk search + */ +void aic23_clock_setup(void) +{ + aic23_mclk = clk_get(0, "mclk"); +} + +/* + * Do some sanity check, set clock rate, starts it and + * turn codec audio on + */ +int aic23_clock_on(void) +{ + if (clk_get_usecount(aic23_mclk) > 0) { + /* MCLK is already in use */ + printk(KERN_WARNING + "MCLK in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(aic23_mclk), + CODEC_CLOCK); + } + + if (clk_set_rate(aic23_mclk, CODEC_CLOCK)) { + printk(KERN_ERR + "Cannot set MCLK for AIC23 CODEC\n"); + return -ECANCELED; + } + + clk_enable(aic23_mclk); + + printk(KERN_DEBUG + "MCLK = %d [%d], usecount = %d\n", + (uint) clk_get_rate(aic23_mclk), CODEC_CLOCK, + clk_get_usecount(aic23_mclk)); + + /* Now turn the audio on */ + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, + ~DEVICE_POWER_OFF & ~OUT_OFF & ~DAC_OFF & + ~ADC_OFF & ~MIC_OFF & ~LINE_OFF); + return 0; +} +/* + * Do some sanity check, turn clock off and then turn + * codec audio off + */ +int aic23_clock_off(void) +{ + if (clk_get_usecount(aic23_mclk) > 0) { + if (clk_get_rate(aic23_mclk) != CODEC_CLOCK) { + printk(KERN_WARNING + "MCLK for audio should be %d Hz. But is %d Hz\n", + (uint) clk_get_rate(aic23_mclk), + CODEC_CLOCK); + } + + clk_disable(aic23_mclk); + } + + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, + DEVICE_POWER_OFF | OUT_OFF | DAC_OFF | + ADC_OFF | MIC_OFF | LINE_OFF); + return 0; +} + +int aic23_get_default_samplerate(void) +{ + return DEFAULT_SAMPLE_RATE; +} + +static int __init snd_omap_alsa_aic23_probe(struct platform_device *pdev) +{ + int ret; + struct omap_alsa_codec_config *codec_cfg; + + codec_cfg = pdev->dev.platform_data; + if (codec_cfg != NULL) { + codec_cfg->hw_constraints_rates = &aic23_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = &aic23_snd_omap_alsa_playback; + codec_cfg->snd_omap_alsa_capture = &aic23_snd_omap_alsa_capture; + codec_cfg->codec_configure_dev = aic23_configure; + codec_cfg->codec_set_samplerate = aic23_set_samplerate; + codec_cfg->codec_clock_setup = aic23_clock_setup; + codec_cfg->codec_clock_on = aic23_clock_on; + codec_cfg->codec_clock_off = aic23_clock_off; + codec_cfg->get_default_samplerate = aic23_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + } + else + ret = -ENODEV; + return ret; +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_aic23_probe, + .remove = snd_omap_alsa_remove, + .suspend = snd_omap_alsa_suspend, + .resume = snd_omap_alsa_resume, + .driver = { + .name = "omap_alsa_mcbsp", + }, +}; + +static int __init omap_alsa_aic23_init(void) +{ + int err; + + ADEBUG(); + err = platform_driver_register(&omap_alsa_driver); + + return err; +} + +static void __exit omap_alsa_aic23_exit(void) +{ + ADEBUG(); + + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_aic23_init); +module_exit(omap_alsa_aic23_exit); diff --git a/sound/arm/omap/omap-alsa-aic23.h b/sound/arm/omap/omap-alsa-aic23.h new file mode 100644 index 00000000000..63907c41c41 --- /dev/null +++ b/sound/arm/omap/omap-alsa-aic23.h @@ -0,0 +1,83 @@ +/* + * sound/arm/omap-alsa-aic23.h + * + * Alsa Driver for AIC23 codec on OSK5912 platform board + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * 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. + */ + +#ifndef __OMAP_ALSA_AIC23_H +#define __OMAP_ALSA_AIC23_H + +#include +#include +#include +#include +#include + +/* Define to set the AIC23 as the master w.r.t McBSP */ +#define AIC23_MASTER + +#define NUMBER_SAMPLE_RATES_SUPPORTED 10 + +/* + * AUDIO related MACROS + */ +#ifndef DEFAULT_BITPERSAMPLE +#define DEFAULT_BITPERSAMPLE 16 +#endif + +#define DEFAULT_SAMPLE_RATE 44100 +#define CODEC_CLOCK 12000000 +#define AUDIO_MCBSP OMAP_MCBSP1 + +#define DEFAULT_OUTPUT_VOLUME 0x60 +#define DEFAULT_INPUT_VOLUME 0x00 /* 0 ==> mute line in */ + +#define OUTPUT_VOLUME_MIN LHV_MIN +#define OUTPUT_VOLUME_MAX LHV_MAX +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN) +#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX + +#define INPUT_VOLUME_MIN LIV_MIN +#define INPUT_VOLUME_MAX LIV_MAX +#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN) +#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX + +#define SIDETONE_MASK 0x1c0 +#define SIDETONE_0 0x100 +#define SIDETONE_6 0x000 +#define SIDETONE_9 0x040 +#define SIDETONE_12 0x080 +#define SIDETONE_18 0x0c0 + +#define DEFAULT_ANALOG_AUDIO_CONTROL DAC_SELECTED | STE_ENABLED | BYPASS_ON | INSEL_MIC | MICB_20DB + +struct aic23_samplerate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* + * Defines codec specific functions pointers that can be used from the + * common omap-alse base driver for all omap codecs. (tsc2101 and aic23) + */ +void define_codec_functions(struct omap_alsa_codec_config *codec_config); +inline void aic23_configure(void); +void aic23_set_samplerate(long rate); +void aic23_clock_setup(void); +int aic23_clock_on(void); +int aic23_clock_off(void); +int aic23_get_default_samplerate(void); + +#endif diff --git a/sound/arm/omap-alsa-dma.c b/sound/arm/omap/omap-alsa-dma.c similarity index 88% rename from sound/arm/omap-alsa-dma.c rename to sound/arm/omap/omap-alsa-dma.c index 8530acf7694..7537d8e00bb 100644 --- a/sound/arm/omap-alsa-dma.c +++ b/sound/arm/omap/omap-alsa-dma.c @@ -1,8 +1,10 @@ /* - * sound/arm/omap-alsa-dma.c + * sound/arm/omap/omap-alsa-dma.c * * Common audio DMA handling for the OMAP processors * + * Copyright (C) 2006 Mika Laitio + * * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil * * Copyright (C) 2004 Texas Instruments, Inc. @@ -66,20 +68,9 @@ #include -#include "omap-aic23.h" +#include #undef DEBUG -//#define DEBUG -#ifdef DEBUG -#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS) -#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__) -#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n) -#else - -#define DPRINTK( x... ) -#define FN_IN -#define FN_OUT(x) -#endif #define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS); @@ -165,7 +156,7 @@ static void omap_sound_dma_link_lch(void *data) FN_OUT(0); } -int omap_request_sound_dma(int device_id, const char *device_name, +int omap_request_alsa_sound_dma(int device_id, const char *device_name, void *data, int **channels) { int i, err = 0; @@ -186,10 +177,11 @@ int omap_request_sound_dma(int device_id, const char *device_name, } spin_lock(&dma_list_lock); for (i = 0; i < nr_linked_channels; i++) { - err = - omap_request_dma(device_id, device_name, - sound_dma_irq_handler, data, - &chan[i]); + err = omap_request_dma(device_id, + device_name, + sound_dma_irq_handler, + data, + &chan[i]); /* Handle Failure condition here */ if (err < 0) { @@ -223,7 +215,7 @@ int omap_request_sound_dma(int device_id, const char *device_name, **************************************************************************************/ static void omap_sound_dma_unlink_lch(void *data) { - struct audio_stream *s = (struct audio_stream *) data; + struct audio_stream *s = (struct audio_stream *)data; int *chan = s->lch; int i; @@ -243,11 +235,11 @@ static void omap_sound_dma_unlink_lch(void *data) FN_OUT(0); } -int omap_free_sound_dma(void *data, int **channels) +int omap_free_alsa_sound_dma(void *data, int **channels) { - int i; int *chan = NULL; + FN_IN; if (unlikely(NULL == channels)) { BUG(); @@ -277,10 +269,11 @@ int omap_free_sound_dma(void *data, int **channels) * Stop all the DMA channels of the stream * **************************************************************************************/ -void omap_audio_stop_dma(struct audio_stream *s) +void omap_stop_alsa_sound_dma(struct audio_stream *s) { int *chan = s->lch; int i; + FN_IN; if (unlikely(NULL == chan)) { BUG(); @@ -299,7 +292,7 @@ void omap_audio_stop_dma(struct audio_stream *s) * Clear any pending transfers * **************************************************************************************/ -void omap_clear_sound_dma(struct audio_stream * s) +void omap_clear_alsa_sound_dma(struct audio_stream * s) { FN_IN; omap_clear_dma(s->lch[s->dma_q_head]); @@ -307,13 +300,6 @@ void omap_clear_sound_dma(struct audio_stream * s) return; } -/*********************************** MODULE FUNCTIONS DEFINTIONS ***********************/ - -#ifdef OMAP1610_MCBSP1_BASE -#undef OMAP1610_MCBSP1_BASE -#endif -#define OMAP1610_MCBSP1_BASE 0xE1011000 - /*************************************************************************************** * * DMA related functions @@ -325,9 +311,10 @@ static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, int dt = 0x1; /* data type 16 */ int cen = 32; /* Stereo */ int cfn = dma_size / (2 * cen); + FN_IN; omap_set_dma_dest_params(channel, 0x05, 0x00, - (OMAP1610_MCBSP1_BASE + 0x806), + (OMAP1510_MCBSP1_BASE + 0x06), 0, 0); omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr, 0, 0); @@ -341,11 +328,11 @@ static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, { int dt = 0x1; /* data type 16 */ int cen = 32; /* stereo */ - int cfn = dma_size / (2 * cen); + FN_IN; omap_set_dma_src_params(channel, 0x05, 0x00, - (OMAP1610_MCBSP1_BASE + 0x802), + (OMAP1510_MCBSP1_BASE + 0x02), 0, 0); omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr, 0, 0); omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0); @@ -358,7 +345,7 @@ static int audio_start_dma_chain(struct audio_stream *s) int channel = s->lch[s->dma_q_head]; FN_IN; if (!s->started) { - s->hw_stop(); /* stops McBSP Interface */ + s->hw_stop(); /* stops McBSP Interface */ omap_start_dma(channel); s->started = 1; s->hw_start(); /* start McBSP interface */ @@ -372,8 +359,9 @@ static int audio_start_dma_chain(struct audio_stream *s) * Do the initial set of work to initialize all the channels as required. * We shall then initate a transfer */ -int omap_start_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr, - u_int dma_size) +int omap_start_alsa_sound_dma(struct audio_stream *s, + dma_addr_t dma_ptr, + u_int dma_size) { int ret = -EPERM; @@ -439,18 +427,17 @@ static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status, } if (ch_status & DCSR_END_BLOCK) - audio_dma_callback(s); + callback_omap_alsa_sound_dma(s); FN_OUT(0); return; } MODULE_AUTHOR("Texas Instruments"); -MODULE_DESCRIPTION - ("Common DMA handling for Audio driver on OMAP processors"); +MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors"); MODULE_LICENSE("GPL"); -EXPORT_SYMBOL(omap_start_sound_dma); -EXPORT_SYMBOL(omap_clear_sound_dma); -EXPORT_SYMBOL(omap_request_sound_dma); -EXPORT_SYMBOL(omap_free_sound_dma); -EXPORT_SYMBOL(omap_audio_stop_dma); +EXPORT_SYMBOL(omap_start_alsa_sound_dma); +EXPORT_SYMBOL(omap_clear_alsa_sound_dma); +EXPORT_SYMBOL(omap_request_alsa_sound_dma); +EXPORT_SYMBOL(omap_free_alsa_sound_dma); +EXPORT_SYMBOL(omap_stop_alsa_sound_dma); diff --git a/sound/arm/omap-alsa-dma.h b/sound/arm/omap/omap-alsa-dma.h similarity index 64% rename from sound/arm/omap-alsa-dma.h rename to sound/arm/omap/omap-alsa-dma.h index 2ac7650abe3..1cecc8aa55c 100644 --- a/sound/arm/omap-alsa-dma.h +++ b/sound/arm/omap/omap-alsa-dma.h @@ -1,8 +1,10 @@ /* - * linux/sound/arm/omap-alsa-dma.h + * linux/sound/arm/omap/omap-alsa-dma.h * * Common audio DMA handling for the OMAP processors * + * Copyright (C) 2006 Mika Laitio + * * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil * * Copyright (C) 2004 Texas Instruments, Inc. @@ -30,14 +32,7 @@ /************************** INCLUDES *************************************/ -#include "omap-aic23.h" - -/************************** GLOBAL MACROS *************************************/ - -/* Provide the Macro interfaces common across platforms */ -#define DMA_REQUEST(e,s, cb) {e=omap_request_sound_dma(s->dma_dev, s->id, s, &s->lch);} -#define DMA_FREE(s) omap_free_sound_dma(s, &s->lch) -#define DMA_CLEAR(s) omap_clear_sound_dma(s) +#include /************************** GLOBAL DATA STRUCTURES *********************************/ @@ -45,15 +40,14 @@ typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data); /**************** ARCH SPECIFIC FUNCIONS *******************************************/ -void omap_clear_sound_dma(struct audio_stream * s); +void omap_clear_alsa_sound_dma(struct audio_stream * s); -int omap_request_sound_dma(int device_id, const char *device_name, +int omap_request_alsa_sound_dma(int device_id, const char *device_name, void *data, int **channels); -int omap_free_sound_dma(void *data, int **channels); +int omap_free_alsa_sound_dma(void *data, int **channels); -int omap_start_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr, - u_int dma_size); +int omap_start_alsa_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr, u_int dma_size); -void omap_audio_stop_dma(struct audio_stream *s); +void omap_stop_alsa_sound_dma(struct audio_stream *s); #endif diff --git a/sound/arm/omap/omap-alsa-tsc2101-mixer.c b/sound/arm/omap/omap-alsa-tsc2101-mixer.c new file mode 100644 index 00000000000..9dfaea8d7bd --- /dev/null +++ b/sound/arm/omap/omap-alsa-tsc2101-mixer.c @@ -0,0 +1,864 @@ +/* + * 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-03-01 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 "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; + + 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) { + /* mute --> turn bit on */ + val = val | DGC_DALMU; + } + else { + /* unmute --> turn bit off */ + val = val & ~DGC_DALMU; + } + count++; + } /* L */ + if (muteRight == GET_DGC_DARMU_BIT_VALUE(val)) { + if (muteRight == 0) { + /* mute --> turn bit on */ + val = val | DGC_DARMU; + } + else { + /* unmute --> turn bit off */ + val = val & ~DGC_DARMU; + } + 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)); + + /* 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)); + + /* 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); + /* unmute */ + dac_gain_control_unmute_control(1, 1); +} + +/* + * 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); + /* make sure that other bits are not on */ + volL = volL & ~DGC_DALMU; + 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]); +} + +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) { + /* mute --> turn bit on */ + val = val | HGC_ADMUT_HED; + } + else { + /* unmute --> turn bit off */ + val = val & ~HGC_ADMUT_HED; + } + 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) { + /* mute --> turn bit on */ + val = val | HNGC_ADMUT_HND; + } + else { + /* unmute --> turn bit off */ + val = val & ~HNGC_ADMUT_HND; + } + 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; +} diff --git a/sound/arm/omap/omap-alsa-tsc2101-mixer.h b/sound/arm/omap/omap-alsa-tsc2101-mixer.h new file mode 100644 index 00000000000..f68f33dd864 --- /dev/null +++ b/sound/arm/omap/omap-alsa-tsc2101-mixer.h @@ -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-03-01 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_*/ diff --git a/sound/arm/omap/omap-alsa-tsc2101.c b/sound/arm/omap/omap-alsa-tsc2101.c new file mode 100644 index 00000000000..a0096cb62fc --- /dev/null +++ b/sound/arm/omap/omap-alsa-tsc2101.c @@ -0,0 +1,439 @@ +/* + * arch/arm/mach-omap1/omap-alsa-tsc2101.c + * + * Alsa codec Driver for TSC2101 chip for OMAP platform boards. + * Code obtained from oss omap drivers + * + * Copyright (C) 2004 Texas Instruments, Inc. + * Written by Nishanth Menon and Sriram Kannan + * + * Copyright (C) 2006 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Alsa modularization by Daniel Petrini (d.pensator@gmail.com) + * + * Copyright (C) 2006 Mika Laitio + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#ifdef CONFIG_PM +#include +#endif +#include +#include +#include + +#include +#include <../drivers/ssi/omap-tsc2101.h> + +#include +#include "omap-alsa-tsc2101.h" + +static struct clk *tsc2101_mclk = 0; + +//#define DUMP_TSC2101_AUDIO_REGISTERS +#undef DUMP_TSC2101_AUDIO_REGISTERS + +/* + * Hardware capabilities + */ + +/* + * DAC USB-mode sampling rates (MCLK = 12 MHz) + * The rates and rate_reg_into MUST be in the same order + */ +static unsigned int rates[] = { + 7350, 8000, 8018, 8727, + 8820, 9600, 11025, 12000, + 14700, 16000, 22050, 24000, + 29400, 32000, 44100, 48000, +}; + +static snd_pcm_hw_constraint_list_t tsc2101_hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct tsc2101_samplerate_reg_info + rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + /* Div 6 */ + {7350, 7, 1}, + {8000, 7, 0}, + /* Div 5.5 */ + {8018, 6, 1}, + {8727, 6, 0}, + /* Div 5 */ + {8820, 5, 1}, + {9600, 5, 0}, + /* Div 4 */ + {11025, 4, 1}, + {12000, 4, 0}, + /* Div 3 */ + {14700, 3, 1}, + {16000, 3, 0}, + /* Div 2 */ + {22050, 2, 1}, + {24000, 2, 0}, + /* Div 1.5 */ + {29400, 1, 1}, + {32000, 1, 0}, + /* Div 1 */ + {44100, 0, 1}, + {48000, 0, 0}, +}; + +static snd_pcm_hardware_t tsc2101_snd_omap_alsa_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 7350, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t tsc2101_snd_omap_alsa_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 7350, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 16, + .periods_max = 255, + .fifo_size = 0, +}; + +/* + * Simplified write for tsc Audio + */ +inline void tsc2101_audio_write(u8 address, u16 data) +{ + omap_tsc2101_write(PAGE2_AUDIO_CODEC_REGISTERS, address, data); +} + +/* + * Simplified read for tsc Audio + */ +inline u16 tsc2101_audio_read(u8 address) +{ + return (omap_tsc2101_read(PAGE2_AUDIO_CODEC_REGISTERS, address)); +} + +#ifdef DUMP_TSC2101_AUDIO_REGISTERS +void dump_tsc2101_audio_reg(void) { + printk("TSC2101_AUDIO_CTRL_1 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_1)); + printk("TSC2101_HEADSET_GAIN_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL)); + printk("TSC2101_DAC_GAIN_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL)); + printk("TSC2101_MIXER_PGA_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_MIXER_PGA_CTRL)); + printk("TSC2101_AUDIO_CTRL_2 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_2)); + printk("TSC2101_CODEC_POWER_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_CODEC_POWER_CTRL)); + printk("TSC2101_AUDIO_CTRL_3 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_3)); + printk("TSC2101_LCH_BASS_BOOST_N0 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N0)); + printk("TSC2101_LCH_BASS_BOOST_N1 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N1)); + printk("TSC2101_LCH_BASS_BOOST_N2 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N2)); + printk("TSC2101_LCH_BASS_BOOST_N3 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N3)); + printk("TSC2101_LCH_BASS_BOOST_N4 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N4)); + printk("TSC2101_LCH_BASS_BOOST_N5 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N5)); + printk("TSC2101_LCH_BASS_BOOST_D1 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D1)); + printk("TSC2101_LCH_BASS_BOOST_D2 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D2)); + printk("TSC2101_LCH_BASS_BOOST_D4 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D4)); + printk("TSC2101_LCH_BASS_BOOST_D5 = 0x%04x\n", tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D5)); + + printk("TSC2101_RCH_BASS_BOOST_N0 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N0)); + printk("TSC2101_RCH_BASS_BOOST_N1 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N1)); + printk("TSC2101_RCH_BASS_BOOST_N2 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N2)); + printk("TSC2101_RCH_BASS_BOOST_N3 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N3)); + printk("TSC2101_RCH_BASS_BOOST_N4 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N4)); + printk("TSC2101_RCH_BASS_BOOST_N5 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N5)); + printk("TSC2101_RCH_BASS_BOOST_D1 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D1)); + printk("TSC2101_RCH_BASS_BOOST_D2 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D2)); + printk("TSC2101_RCH_BASS_BOOST_D4 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D4)); + printk("TSC2101_RCH_BASS_BOOST_D5 = 0x%04x\n", tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D5)); + + printk("TSC2101_PLL_PROG_1 = 0x%04x\n", tsc2101_audio_read(TSC2101_PLL_PROG_1)); + printk("TSC2101_PLL_PROG_1 = 0x%04x\n", tsc2101_audio_read(TSC2101_PLL_PROG_2)); + printk("TSC2101_AUDIO_CTRL_4 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_4)); + printk("TSC2101_HANDSET_GAIN_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL)); + printk("TSC2101_BUZZER_GAIN_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL)); + printk("TSC2101_AUDIO_CTRL_5 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_5)); + printk("TSC2101_AUDIO_CTRL_6 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_6)); + printk("TSC2101_AUDIO_CTRL_7 = 0x%04x\n", tsc2101_audio_read(TSC2101_AUDIO_CTRL_7)); + printk("TSC2101_GPIO_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_GPIO_CTRL)); + printk("TSC2101_AGC_CTRL = 0x%04x\n", tsc2101_audio_read(TSC2101_AGC_CTRL)); + printk("TSC2101_POWERDOWN_STS = 0x%04x\n", tsc2101_audio_read(TSC2101_POWERDOWN_STS)); + printk("TSC2101_MIC_AGC_CONTROL = 0x%04x\n", tsc2101_audio_read(TSC2101_MIC_AGC_CONTROL)); + printk("TSC2101_CELL_AGC_CONTROL = 0x%04x\n", tsc2101_audio_read(TSC2101_CELL_AGC_CONTROL)); +} +#endif + +/* + * ALSA operations according to board file + */ + +/* + * Sample rate changing + */ +void tsc2101_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + int clkgdv = 0; + + u16 srgr1, srgr2; + /* wait for any frame to complete */ + udelay(125); + ADEBUG(); + + sample_rate = sample_rate; + /* Search for the right sample rate */ + while ((rate_reg_info[count].sample_rate != sample_rate) && + (count < NUMBER_SAMPLE_RATES_SUPPORTED)) { + count++; + } + if (count == NUMBER_SAMPLE_RATES_SUPPORTED) { + printk(KERN_ERR "Invalid Sample Rate %d requested\n", + (int) sample_rate); + return; // -EPERM; + } + + /* Set AC1 */ + data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_1); + /* Clear prev settings */ + data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07)); + data |= AC1_DACFS(rate_reg_info[count].divisor) | + AC1_ADCFS(rate_reg_info[count].divisor); + tsc2101_audio_write(TSC2101_AUDIO_CTRL_1, data); + + /* Set the AC3 */ + data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_3); + /*Clear prev settings */ + data &= ~(AC3_REFFS | AC3_SLVMS); + data |= (rate_reg_info[count].fs_44kHz) ? AC3_REFFS : 0; +#ifdef TSC_MASTER + data |= AC3_SLVMS; +#endif /* #ifdef TSC_MASTER */ + tsc2101_audio_write(TSC2101_AUDIO_CTRL_3, data); + + /* program the PLLs */ + if (rate_reg_info[count].fs_44kHz) { + /* 44.1 khz - 12 MHz Mclk */ + tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | + PLL1_PVAL(1) | PLL1_I_VAL(7)); /* PVAL 1; I_VAL 7 */ + tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490)); /* D_VAL 5264 */ + } else { + /* 48 khz - 12 Mhz Mclk */ + tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | + PLL1_PVAL(1) | PLL1_I_VAL(8)); /* PVAL 1; I_VAL 8 */ + tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780)); /* D_VAL 1920 */ + } + + /* Set the sample rate */ +#ifndef TSC_MASTER + clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1)); + if (clkgdv) + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + else + return (1); + + /* Stereo Mode */ + srgr2 = (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); +#else + srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + srgr2 = ((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1))); + +#endif /* end of #ifdef TSC_MASTER */ + OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR2, srgr2); + OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR1, srgr1); +} + +void tsc2101_configure(void) +{ +} + +/* + * Omap MCBSP clock and Power Management configuration + * + * Here we have some functions that allows clock to be enabled and + * disabled only when needed. Besides doing clock configuration + * it allows turn on/turn off audio when necessary. + */ + +/* + * Do clock framework mclk search + */ +void tsc2101_clock_setup(void) +{ + tsc2101_mclk = clk_get(0, "mclk"); +} + +/* + * Do some sanity check, set clock rate, starts it and turn codec audio on + */ +int tsc2101_clock_on(void) +{ + int curUseCount; + uint curRate; + int err; + + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("clock use count = %d\n", curUseCount); + if (curUseCount > 0) { + // MCLK is already in use + printk(KERN_WARNING + "MCLK already in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(tsc2101_mclk), + CODEC_CLOCK); + } + curRate = (uint)clk_get_rate(tsc2101_mclk); + DPRINTK("old clock rate = %d\n", curRate); + if (curRate != CODEC_CLOCK) { + err = clk_set_rate(tsc2101_mclk, CODEC_CLOCK); + if (err) { + printk(KERN_WARNING + "Cannot set MCLK clock rate for TSC2101 CODEC, error code = %d\n", err); + //return -ECANCELED; + } + } + else + { + printk(KERN_INFO + "omap_alsa_tsc2101_clock_on(), no need to change rate, no need to change clock rate, rate already %d Hz.\n", + CODEC_CLOCK); + } + err = clk_enable(tsc2101_mclk); + curRate = (uint)clk_get_rate(tsc2101_mclk); + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("MCLK = %d [%d], usecount = %d, clk_enable retval = %d\n", + curRate, + CODEC_CLOCK, + curUseCount, + err); + + // Now turn the audio on + omap_tsc2101_write(PAGE2_AUDIO_CODEC_REGISTERS, + TSC2101_CODEC_POWER_CTRL, + 0x0000); + return 0; +} + +/* + * Do some sanity check, turn clock off and then turn + * codec audio off + */ +int tsc2101_clock_off(void) +{ + int curUseCount; + int curRate; + + curUseCount = clk_get_usecount(tsc2101_mclk); + DPRINTK("clock use count = %d\n", curUseCount); + if (curUseCount > 0) { + curRate = clk_get_rate(tsc2101_mclk); + DPRINTK("clock rate = %d\n", curRate); + if (curRate != CODEC_CLOCK) { + printk(KERN_WARNING + "MCLK for audio should be %d Hz. But is %d Hz\n", + (uint) clk_get_rate(tsc2101_mclk), + CODEC_CLOCK); + } + clk_disable(tsc2101_mclk); + DPRINTK("clock disabled\n"); + } + tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + DPRINTK("audio codec off\n"); +#ifdef DUMP_TSC2101_AUDIO_REGISTERS + printk("tsc2101_clock_off()\n"); + dump_tsc2101_audio_reg(); +#endif + return 0; +} + +int tsc2101_get_default_samplerate(void) +{ + return DEFAULT_SAMPLE_RATE; +} + +static int __init snd_omap_alsa_tsc2101_probe(struct platform_device *pdev) +{ + int ret; + struct omap_alsa_codec_config *codec_cfg; + + codec_cfg = pdev->dev.platform_data; + if (codec_cfg != NULL) { + codec_cfg->hw_constraints_rates = &tsc2101_hw_constraints_rates; + codec_cfg->snd_omap_alsa_playback = &tsc2101_snd_omap_alsa_playback; + codec_cfg->snd_omap_alsa_capture = &tsc2101_snd_omap_alsa_capture; + codec_cfg->codec_configure_dev = tsc2101_configure; + codec_cfg->codec_set_samplerate = tsc2101_set_samplerate; + codec_cfg->codec_clock_setup = tsc2101_clock_setup; + codec_cfg->codec_clock_on = tsc2101_clock_on; + codec_cfg->codec_clock_off = tsc2101_clock_off; + codec_cfg->get_default_samplerate = tsc2101_get_default_samplerate; + ret = snd_omap_alsa_post_probe(pdev, codec_cfg); + } + else + ret = -ENODEV; + return ret; +} + +static struct platform_driver omap_alsa_driver = { + .probe = snd_omap_alsa_tsc2101_probe, + .remove = snd_omap_alsa_remove, + .suspend = snd_omap_alsa_suspend, + .resume = snd_omap_alsa_resume, + .driver = { + .name = "omap_alsa_mcbsp", + }, +}; + +static int __init omap_alsa_tsc2101_init(void) +{ + int err; + + ADEBUG(); + err = platform_driver_register(&omap_alsa_driver); + + return err; +} + +static void __exit omap_alsa_tsc2101_exit(void) +{ + ADEBUG(); + platform_driver_unregister(&omap_alsa_driver); +} + +module_init(omap_alsa_tsc2101_init); +module_exit(omap_alsa_tsc2101_exit); diff --git a/sound/arm/omap/omap-alsa-tsc2101.h b/sound/arm/omap/omap-alsa-tsc2101.h new file mode 100644 index 00000000000..803d215e59a --- /dev/null +++ b/sound/arm/omap/omap-alsa-tsc2101.h @@ -0,0 +1,61 @@ +/* + * arch/arc/mach-omap1/omap-alsa-tsc2101.h + * + * Alsa Driver for TSC2101 codec for OMAP platform boards. + * + * Based on former omap-aic23.h and tsc2101 OSS drivers. + * Copyright (C) 2004 Texas Instruments, Inc. + * Written by Nishanth Menon and Sriram Kannan + * + * Copyright (C) 2006 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Alsa modularization by Daniel Petrini (d.pensator@gmail.com) + * + * Copyright (C) 2006 Mika Laitio + * + * 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. + */ + +#ifndef OMAP_ALSA_TSC2101_H_ +#define OMAP_ALSA_TSC2101_H_ + +#include + +/* Define to set the tsc as the master w.r.t McBSP */ +#define TSC_MASTER + +#define NUMBER_SAMPLE_RATES_SUPPORTED 16 + +/* + * AUDIO related MACROS + */ +#ifndef DEFAULT_BITPERSAMPLE +#define DEFAULT_BITPERSAMPLE 16 +#endif + +#define DEFAULT_SAMPLE_RATE 44100 +#define CODEC_CLOCK 12000000 +#define AUDIO_MCBSP OMAP_MCBSP1 + +#define PAGE2_AUDIO_CODEC_REGISTERS (2) + +struct tsc2101_samplerate_reg_info { + u16 sample_rate; + u8 divisor; + u8 fs_44kHz; /* if 0 48 khz, if 1 44.1 khz fsref */ +}; + +/* + * Defines codec specific functions pointers that can be used from the + * common omap-alse base driver for all omap codecs. (tsc2101 and aic23) + */ +inline void tsc2101_configure(void); +void tsc2101_set_samplerate(long rate); +void tsc2101_clock_setup(void); +int tsc2101_clock_on(void); +int tsc2101_clock_off(void); +int tsc2101_get_default_samplerate(void); + +#endif /*OMAP_ALSA_TSC2101_H_*/ diff --git a/sound/arm/omap/omap-alsa.c b/sound/arm/omap/omap-alsa.c new file mode 100644 index 00000000000..328003be2bc --- /dev/null +++ b/sound/arm/omap/omap-alsa.c @@ -0,0 +1,579 @@ +/* + * sound/arm/omap-alsa.c + * + * Alsa Driver for OMAP + * + * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil + * Written by Daniel Petrini, David Cohen, Anderson Briglia + * {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br + * + * Copyright (C) 2006 Mika Laitio + * + * Based on sa11xx-uda1341.c, + * 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: + * + * 2005-07-29 INdT Kernel Team - Alsa driver for omap osk. Creation of new + * file omap-aic23.c + * + * 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed + * by Ajaya Babu + * + */ + +#include +#ifdef CONFIG_PM +#include +#endif +#include +#include + +#include +#include "omap-alsa-dma.h" + +MODULE_AUTHOR("Mika Laitio, Daniel Petrini, David Cohen, Anderson Briglia - INdT"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OMAP driver for ALSA"); +MODULE_ALIAS("omap_alsa_mcbsp.1"); + +static char *id = NULL; +static struct snd_card_omap_codec *alsa_codec = NULL; +static struct omap_alsa_codec_config *alsa_codec_config = NULL; + +/* + * HW interface start and stop helper functions + */ +static int audio_ifc_start(void) +{ + omap_mcbsp_start(AUDIO_MCBSP); + return 0; +} + +static int audio_ifc_stop(void) +{ + omap_mcbsp_stop(AUDIO_MCBSP); + return 0; +} + +static void omap_alsa_audio_init(struct snd_card_omap_codec *omap_alsa) +{ + /* Setup DMA stuff */ + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Alsa omap out"; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = + SNDRV_PCM_STREAM_PLAYBACK; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev = + OMAP_DMA_MCBSP1_TX; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_start = + audio_ifc_start; + omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_stop = + audio_ifc_stop; + + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].id = "Alsa omap in"; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = + SNDRV_PCM_STREAM_CAPTURE; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev = + OMAP_DMA_MCBSP1_RX; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_start = + audio_ifc_start; + omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_stop = + audio_ifc_stop; +} + +/* + * DMA functions + * Depends on omap-alsa-dma.c functions and (omap) dma.c + * + */ +static int audio_dma_request(struct audio_stream *s, + void (*callback) (void *)) +{ + int err; + ADEBUG(); + + err = omap_request_alsa_sound_dma(s->dma_dev, s->id, s, &s->lch); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dma_dev); + return err; +} + +static int audio_dma_free(struct audio_stream *s) +{ + int err = 0; + ADEBUG(); + + err = omap_free_alsa_sound_dma(s, &s->lch); + if (err < 0) + printk(KERN_ERR "Unable to free audio dma channels!\n"); + return err; +} + +/* + * This function should calculate the current position of the dma in the + * buffer. It will help alsa middle layer to continue update the buffer. + * Its correctness is crucial for good functioning. + */ +static u_int audio_get_dma_pos(struct audio_stream *s) +{ + snd_pcm_substream_t *substream = s->stream; + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int offset; + unsigned long flags; + dma_addr_t count; + ADEBUG(); + + /* this must be called w/ interrupts locked as requested in dma.c */ + spin_lock_irqsave(&s->dma_lock, flags); + + /* For the current period let's see where we are */ + count = omap_get_dma_src_addr_counter(s->lch[s->dma_q_head]); + + spin_unlock_irqrestore(&s->dma_lock, flags); + + /* Now, the position related to the end of that period */ + offset = bytes_to_frames(runtime, s->offset) - bytes_to_frames(runtime, count); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream *s) +{ + unsigned long flags; + ADEBUG(); + + spin_lock_irqsave(&s->dma_lock, flags); + s->active = 0; + s->period = 0; + s->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + omap_stop_alsa_sound_dma(s); + + omap_clear_alsa_sound_dma(s); + + spin_unlock_irqrestore(&s->dma_lock, flags); +} + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream *s) +{ + snd_pcm_substream_t *substream = s->stream; + snd_pcm_runtime_t *runtime; + unsigned int dma_size; + unsigned int offset; + int ret; +#ifdef CONFIG_MACH_OMAP_H6300 + unsigned long flags; +#endif + + ADEBUG(); + runtime = substream->runtime; + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->period; + snd_assert(dma_size <= DMA_BUF_SIZE,); +#ifdef CONFIG_MACH_OMAP_H6300 + spin_lock_irqsave(&s->dma_lock, flags); + omap_stop_alsa_sound_dma(s); + spin_unlock_irqrestore(&s->dma_lock, flags); +#endif + ret = omap_start_alsa_sound_dma(s, + (dma_addr_t) runtime->dma_area + + offset, dma_size); + if (ret) { + printk(KERN_ERR + "audio_process_dma: cannot queue DMA buffer (%i)\n", + ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +void callback_omap_alsa_sound_dma(void *data) +{ + struct audio_stream *s = data; + + ADEBUG(); + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +/* + * Alsa section + * PCM settings and callbacks + */ +static int snd_omap_alsa_trigger(snd_pcm_substream_t * substream, int cmd) +{ + struct snd_card_omap_codec *chip = + snd_pcm_substream_chip(substream); + int stream_id = substream->pstr->stream; + struct audio_stream *s = &chip->s[stream_id]; + int err = 0; + + ADEBUG(); + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* requested stream startup */ + s->active = 1; + audio_process_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* requested stream shutdown */ + audio_stop_dma(s); + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + + return err; +} + +static int snd_omap_alsa_prepare(snd_pcm_substream_t * substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + struct audio_stream *s = &chip->s[substream->pstr->stream]; + + ADEBUG(); + /* set requested samplerate */ + alsa_codec_config->codec_set_samplerate(runtime->rate); + chip->samplerate = runtime->rate; + + s->period = 0; + s->periods = 0; + + return 0; +} + +static snd_pcm_uframes_t snd_omap_alsa_pointer(snd_pcm_substream_t *substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + + ADEBUG(); + return audio_get_dma_pos(&chip->s[substream->pstr->stream]); +} + +static int snd_card_omap_alsa_open(snd_pcm_substream_t * substream) +{ + struct snd_card_omap_codec *chip = + snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + int err; + + ADEBUG(); + chip->s[stream_id].stream = substream; + alsa_codec_config->codec_clock_on(); + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = *(alsa_codec_config->snd_omap_alsa_playback); + else + runtime->hw = *(alsa_codec_config->snd_omap_alsa_capture); + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < 0) + return err; + + if ((err = snd_pcm_hw_constraint_list(runtime, + 0, + SNDRV_PCM_HW_PARAM_RATE, + alsa_codec_config->hw_constraints_rates)) < 0) + return err; + + return 0; +} + +static int snd_card_omap_alsa_close(snd_pcm_substream_t * substream) +{ + struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream); + + ADEBUG(); + alsa_codec_config->codec_clock_off(); + chip->s[substream->pstr->stream].stream = NULL; + + return 0; +} + +/* HW params & free */ +static int snd_omap_alsa_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_omap_alsa_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* pcm operations */ +static snd_pcm_ops_t snd_card_omap_alsa_playback_ops = { + .open = snd_card_omap_alsa_open, + .close = snd_card_omap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_omap_alsa_hw_params, + .hw_free = snd_omap_alsa_hw_free, + .prepare = snd_omap_alsa_prepare, + .trigger = snd_omap_alsa_trigger, + .pointer = snd_omap_alsa_pointer, +}; + +static snd_pcm_ops_t snd_card_omap_alsa_capture_ops = { + .open = snd_card_omap_alsa_open, + .close = snd_card_omap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_omap_alsa_hw_params, + .hw_free = snd_omap_alsa_hw_free, + .prepare = snd_omap_alsa_prepare, + .trigger = snd_omap_alsa_trigger, + .pointer = snd_omap_alsa_pointer, +}; + +/* + * Alsa init and exit section + * + * Inits pcm alsa structures, allocate the alsa buffer, suspend, resume + */ +static int __init snd_card_omap_alsa_pcm(struct snd_card_omap_codec *omap_alsa, + int device) +{ + snd_pcm_t *pcm; + int err; + + ADEBUG(); + if ((err = snd_pcm_new(omap_alsa->card, "OMAP PCM", device, 1, 1, &pcm)) < 0) + return err; + + /* sets up initial buffer with continuous allocation */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + 128 * 1024, 128 * 1024); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_omap_alsa_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_omap_alsa_capture_ops); + pcm->private_data = omap_alsa; + pcm->info_flags = 0; + strcpy(pcm->name, "omap alsa pcm"); + + omap_alsa_audio_init(omap_alsa); + + /* setup DMA controller */ + audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK], + callback_omap_alsa_sound_dma); + audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE], + callback_omap_alsa_sound_dma); + + omap_alsa->pcm = pcm; + + return 0; +} + + +#ifdef CONFIG_PM +/* + * Driver suspend/resume - calls alsa functions. Some hints from aaci.c + */ +int snd_omap_alsa_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_card_omap_codec *chip; + snd_card_t *card = platform_get_drvdata(pdev); + + if (card->power_state != SNDRV_CTL_POWER_D3hot) { + chip = card->private_data; + if (chip->card->power_state != SNDRV_CTL_POWER_D3hot) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + /* Mutes and turn clock off */ + alsa_codec_config->codec_clock_off(); + snd_omap_suspend_mixer(); + } + } + return 0; +} + +int snd_omap_alsa_resume(struct platform_device *pdev) +{ + struct snd_card_omap_codec *chip; + snd_card_t *card = platform_get_drvdata(pdev); + + if (card->power_state != SNDRV_CTL_POWER_D0) { + chip = card->private_data; + if (chip->card->power_state != SNDRV_CTL_POWER_D0) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + alsa_codec_config->codec_clock_on(); + snd_omap_resume_mixer(); + } + } + return 0; +} + +#endif /* CONFIG_PM */ + +void snd_omap_alsa_free(snd_card_t * card) +{ + struct snd_card_omap_codec *chip = card->private_data; + ADEBUG(); + + /* + * Turn off codec after it is done. + * Can't do it immediately, since it may still have + * buffered data. + */ + schedule_timeout_interruptible(2); + + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); +} + +/* module init & exit */ + +/* + * Inits alsa soudcard structure. + * Called by the probe method in codec after function pointers has been set. + */ +int snd_omap_alsa_post_probe(struct platform_device *pdev, struct omap_alsa_codec_config *config) +{ + int err = 0; + int def_rate; + snd_card_t *card; + + ADEBUG(); + alsa_codec_config = config; + + alsa_codec_config->codec_clock_setup(); + alsa_codec_config->codec_clock_on(); + + omap_mcbsp_request(AUDIO_MCBSP); + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_config(AUDIO_MCBSP, alsa_codec_config->mcbsp_regs_alsa); + omap_mcbsp_start(AUDIO_MCBSP); + + if (alsa_codec_config && alsa_codec_config->codec_configure_dev) + alsa_codec_config->codec_configure_dev(); + + alsa_codec_config->codec_clock_off(); + + /* register the soundcard */ + card = snd_card_new(-1, id, THIS_MODULE, sizeof(alsa_codec)); + if (card == NULL) + goto nodev1; + + alsa_codec = kcalloc(1, sizeof(*alsa_codec), GFP_KERNEL); + if (alsa_codec == NULL) + goto nodev2; + + card->private_data = (void *)alsa_codec; + card->private_free = snd_omap_alsa_free; + + alsa_codec->card = card; + def_rate = alsa_codec_config->get_default_samplerate(); + alsa_codec->samplerate = def_rate; + + spin_lock_init(&alsa_codec->s[0].dma_lock); + spin_lock_init(&alsa_codec->s[1].dma_lock); + + /* mixer */ + if ((err = snd_omap_mixer(alsa_codec)) < 0) + goto nodev3; + + /* PCM */ + if ((err = snd_card_omap_alsa_pcm(alsa_codec, 0)) < 0) + goto nodev3; + + strcpy(card->driver, "OMAP_ALSA"); + strcpy(card->shortname, alsa_codec_config->name); + sprintf(card->longname, alsa_codec_config->name); + + snd_omap_init_mixer(); + snd_card_set_dev(card, &pdev->dev); + + if ((err = snd_card_register(card)) == 0) { + printk(KERN_INFO "audio support initialized\n"); + platform_set_drvdata(pdev, card); + return 0; + } + +nodev3: + kfree(alsa_codec); +nodev2: + snd_card_free(card); +nodev1: + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + return err; +} + +int snd_omap_alsa_remove(struct platform_device *pdev) +{ + snd_card_t *card = platform_get_drvdata(pdev); + struct snd_card_omap_codec *chip = card->private_data; + + snd_card_free(card); + + alsa_codec = NULL; + card->private_data = NULL; + kfree(chip); + + platform_set_drvdata(pdev, NULL); + + return 0; +}