From: Tony Lindgren Date: Mon, 9 May 2005 21:32:41 +0000 (-0700) Subject: Add OMAP audio driver X-Git-Tag: v2.6.13-omap1~155 X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=4ed2ff0a50128139f4b8579900d1ec7c1bc0599a;p=linux-2.6-omap-h63xx.git Add OMAP audio driver Adds OMAP audio driver. Signed-off-by: Tony Lindgren --- diff --git a/include/asm-arm/hardware/tsc2101.h b/include/asm-arm/hardware/tsc2101.h new file mode 100644 index 00000000000..449045841f6 --- /dev/null +++ b/include/asm-arm/hardware/tsc2101.h @@ -0,0 +1,300 @@ +/* + * + * TI TSC2101 Audio CODEC and TS control registers definition + * + * + * Copyright 2003 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * source@mvista.com + * + * 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. + */ + +#ifndef __ASM_HARDWARE_TSC2101_H +#define __ASM_HARDWARE_TSC2101_H + +/* Page 0 Touch Screen Data Registers */ +#define TSC2101_TS_X (0x00) +#define TSC2101_TS_Y (0x01) +#define TSC2101_TS_Z1 (0x02) +#define TSC2101_TS_Z2 (0x03) +#define TSC2101_TS_BAT (0x05) +#define TSC2101_TS_AUX1 (0x07) +#define TSC2101_TS_AUX2 (0x08) +#define TSC2101_TS_TEMP1 (0x09) +#define TSC2101_TS_TEMP2 (0x0A) + +/* Page 1 Touch Screen Control registers */ +#define TSC2101_TS_ADC_CTRL (0x00) +#define TSC2101_TS_STATUS (0x01) +#define TSC2101_TS_BUFFER_CTRL (0x02) +#define TSC2101_TS_REF_CTRL (0x03) +#define TSC2101_TS_RESET_CTRL (0x04) +#define TSC2101_TS_CONFIG_CTRL (0x05) +#define TSC2101_TS_TEMP_MAX_THRESHOLD (0x06) +#define TSC2101_TS_TEMP_MIN_THRESHOLD (0x07) +#define TSC2101_TS_AUX1_MAX_THRESHOLD (0x08) +#define TSC2101_TS_AUX1_MIN_THRESHOLD (0x09) +#define TSC2101_TS_AUX2_MAX_THRESHOLD (0x0A) +#define TSC2101_TS_AUX2_MIN_THRESHOLD (0x0B) +#define TSC2101_TS_MEASURE_CONFIG (0x0C) +#define TSC2101_TS_PROG_DELAY (0x0D) + +/* Page 2 Audio codec Control registers */ +#define TSC2101_AUDIO_CTRL_1 (0x00) +#define TSC2101_HEADSET_GAIN_CTRL (0x01) +#define TSC2101_DAC_GAIN_CTRL (0x02) +#define TSC2101_MIXER_PGA_CTRL (0x03) +#define TSC2101_AUDIO_CTRL_2 (0x04) +#define TSC2101_CODEC_POWER_CTRL (0x05) +#define TSC2101_AUDIO_CTRL_3 (0x06) +#define TSC2101_LCH_BASS_BOOST_N0 (0x07) +#define TSC2101_LCH_BASS_BOOST_N1 (0x08) +#define TSC2101_LCH_BASS_BOOST_N2 (0x09) +#define TSC2101_LCH_BASS_BOOST_N3 (0x0A) +#define TSC2101_LCH_BASS_BOOST_N4 (0x0B) +#define TSC2101_LCH_BASS_BOOST_N5 (0x0C) +#define TSC2101_LCH_BASS_BOOST_D1 (0x0D) +#define TSC2101_LCH_BASS_BOOST_D2 (0x0E) +#define TSC2101_LCH_BASS_BOOST_D4 (0x0F) +#define TSC2101_LCH_BASS_BOOST_D5 (0x10) +#define TSC2101_RCH_BASS_BOOST_N0 (0x11) +#define TSC2101_RCH_BASS_BOOST_N1 (0x12) +#define TSC2101_RCH_BASS_BOOST_N2 (0x13) +#define TSC2101_RCH_BASS_BOOST_N3 (0x14) +#define TSC2101_RCH_BASS_BOOST_N4 (0x15) +#define TSC2101_RCH_BASS_BOOST_N5 (0x16) +#define TSC2101_RCH_BASS_BOOST_D1 (0x17) +#define TSC2101_RCH_BASS_BOOST_D2 (0x18) +#define TSC2101_RCH_BASS_BOOST_D4 (0x19) +#define TSC2101_RCH_BASS_BOOST_D5 (0x1A) +#define TSC2101_PLL_PROG_1 (0x1B) +#define TSC2101_PLL_PROG_2 (0x1C) +#define TSC2101_AUDIO_CTRL_4 (0x1D) +#define TSC2101_HANDSET_GAIN_CTRL (0x1E) +#define TSC2101_BUZZER_GAIN_CTRL (0x1F) +#define TSC2101_AUDIO_CTRL_5 (0x20) +#define TSC2101_AUDIO_CTRL_6 (0x21) +#define TSC2101_AUDIO_CTRL_7 (0x22) +#define TSC2101_GPIO_CTRL (0x23) +#define TSC2101_AGC_CTRL (0x24) +#define TSC2101_POWERDOWN_STS (0x25) +#define TSC2101_MIC_AGC_CONTROL (0x26) +#define TSC2101_CELL_AGC_CONTROL (0x27) + +/* Bit field definitions for TS Control */ +#define TSC2101_DATA_AVAILABLE 0x4000 +#define TSC2101_BUFFERMODE_DISABLE 0x0 +#define TSC2101_REF_POWERUP 0x16 +#define TSC2101_ENABLE_TOUCHDETECT 0x08 +#define TSC2101_PRG_DELAY 0x0900 +#define TSC2101_ADC_CONTROL 0x8874 +#define TSC2101_ADC_POWERDOWN 0x4000 + +/* Bit position */ +#define TSC2101_BIT(ARG) ((0x01)<<(ARG)) + +/* Field masks for Audio Control 1 */ +#define AC1_ADCHPF(ARG) (((ARG) & 0x03) << 14) +#define AC1_WLEN(ARG) (((ARG) & 0x03) << 10) +#define AC1_DATFM(ARG) (((ARG) & 0x03) << 8) +#define AC1_DACFS(ARG) (((ARG) & 0x07) << 3) +#define AC1_ADCFS(ARG) (((ARG) & 0x07)) + +/* Field masks for TSC2101_HEADSET_GAIN_CTRL */ +#define HGC_ADMUT_HED TSC2101_BIT(15) +#define HGC_ADPGA_HED(ARG) (((ARG) & 0x7F) << 8) +#define HGC_AGCTG_HED(ARG) (((ARG) & 0x07) << 5) +#define HGC_AGCTC_HED(ARG) (((ARG) & 0x0F) << 1) +#define HGC_AGCEN_HED (0x01) + +/* Field masks for TSC2101_DAC_GAIN_CTRL */ +#define DGC_DALMU TSC2101_BIT(15) +#define DGC_DALVL(ARG) (((ARG) & 0x7F) << 8) +#define DGC_DARMU TSC2101_BIT(7) +#define DGC_DARVL(ARG) (((ARG) & 0x7F)) + +/* Field masks for TSC2101_MIXER_PGA_CTRL */ +#define MPC_ASTMU TSC2101_BIT(15) +#define MPC_ASTG(ARG) (((ARG) & 0x7F) << 8) +#define MPC_MICSEL(ARG) (((ARG) & 0x07) << 5) +#define MPC_MICADC TSC2101_BIT(4) +#define MPC_CPADC TSC2101_BIT(3) +#define MPC_ASTGF (0x01) + +/* Field formats for TSC2101_AUDIO_CTRL_2 */ +#define AC2_KCLEN TSC2101_BIT(15) +#define AC2_KCLAC(ARG) (((ARG) & 0x07) << 12) +#define AC2_APGASS TSC2101_BIT(11) +#define AC2_KCLFRQ(ARG) (((ARG) & 0x07) << 8) +#define AC2_KCLLN(ARG) (((ARG) & 0x0F) << 4) +#define AC2_DLGAF TSC2101_BIT(3) +#define AC2_DRGAF TSC2101_BIT(2) +#define AC2_DASTC TSC2101_BIT(1) +#define AC2_ADGAF (0x01) + +/* Field masks for TSC2101_CODEC_POWER_CTRL */ +#define CPC_MBIAS_HND TSC2101_BIT(15) +#define CPC_MBIAS_HED TSC2101_BIT(14) +#define CPC_ASTPWD TSC2101_BIT(13) +#define CPC_SP1PWDN TSC2101_BIT(12) +#define CPC_SP2PWDN TSC2101_BIT(11) +#define CPC_DAPWDN TSC2101_BIT(10) +#define CPC_ADPWDN TSC2101_BIT(9) +#define CPC_VGPWDN TSC2101_BIT(8) +#define CPC_COPWDN TSC2101_BIT(7) +#define CPC_LSPWDN TSC2101_BIT(6) +#define CPC_ADPWDF TSC2101_BIT(5) +#define CPC_LDAPWDF TSC2101_BIT(4) +#define CPC_RDAPWDF TSC2101_BIT(3) +#define CPC_ASTPWF TSC2101_BIT(2) +#define CPC_BASSBC TSC2101_BIT(1) +#define CPC_DEEMPF (0x01) + +/* Field masks for TSC2101_AUDIO_CTRL_3 */ +#define AC3_DMSVOL(ARG) (((ARG) & 0x03) << 14) +#define AC3_REFFS TSC2101_BIT(13) +#define AC3_DAXFM TSC2101_BIT(12) +#define AC3_SLVMS TSC2101_BIT(11) +#define AC3_ADCOVF TSC2101_BIT(8) +#define AC3_DALOVF TSC2101_BIT(7) +#define AC3_DAROVF TSC2101_BIT(6) +#define AC3_CLPST TSC2101_BIT(3) +#define AC3_REVID(ARG) (((ARG) & 0x07)) + +/* Field masks for TSC2101_PLL_PROG_1 */ +#define PLL1_PLLSEL TSC2101_BIT(15) +#define PLL1_QVAL(ARG) (((ARG) & 0x0F) << 11) +#define PLL1_PVAL(ARG) (((ARG) & 0x07) << 8) +#define PLL1_I_VAL(ARG) (((ARG) & 0x3F) << 2) + +/* Field masks of TSC2101_PLL_PROG_2 */ +#define PLL2_D_VAL(ARG) (((ARG) & 0x3FFF) << 2) + +/* Field masks for TSC2101_AUDIO_CTRL_4 */ +#define AC4_ADSTPD TSC2101_BIT(15) +#define AC4_DASTPD TSC2101_BIT(14) +#define AC4_ASSTPD TSC2101_BIT(13) +#define AC4_CISTPD TSC2101_BIT(12) +#define AC4_BISTPD TSC2101_BIT(11) +#define AC4_AGCHYS(ARG) (((ARG) & 0x03) << 9) +#define AC4_MB_HED(ARG) (((ARG) & 0x03) << 7) +#define AC4_MB_HND TSC2101_BIT(6) +#define AC4_SCPFL TSC2101_BIT(1) + +/* Field masks settings for TSC2101_HANDSET_GAIN_CTRL */ +#define HNGC_ADMUT_HND TSC2101_BIT(15) +#define HNGC_ADPGA_HND(ARG) (((ARG) & 0x7F) << 8) +#define HNGC_AGCTG_HND(ARG) (((ARG) & 0x07) << 5) +#define HNGC_AGCTC_HND(ARG) (((ARG) & 0x0F) << 1) +#define HNGC_AGCEN_HND (0x01) + +/* Field masks settings for TSC2101_BUZZER_GAIN_CTRL */ +#define BGC_MUT_CP TSC2101_BIT(15) +#define BGC_CPGA(ARG) (((ARG) & 0x7F) << 8) +#define BGC_CPGF TSC2101_BIT(7) +#define BGC_MUT_BU TSC2101_BIT(6) +#define BGC_BPGA(ARG) (((ARG) & 0x0F) << 2) +#define BGC_BUGF TSC2101_BIT(1) + +/* Field masks settings for TSC2101_AUDIO_CTRL_5 */ +#define AC5_DIFFIN TSC2101_BIT(15) +#define AC5_DAC2SPK1(ARG) (((ARG) & 0x03) << 13) +#define AC5_AST2SPK1 TSC2101_BIT(12) +#define AC5_BUZ2SPK1 TSC2101_BIT(11) +#define AC5_KCL2SPK1 TSC2101_BIT(10) +#define AC5_CPI2SPK1 TSC2101_BIT(9) +#define AC5_DAC2SPK2(ARG) (((ARG) & 0x03) << 7) +#define AC5_AST2SPK2 TSC2101_BIT(6) +#define AC5_BUZ2SPK2 TSC2101_BIT(5) +#define AC5_KCL2SPK2 TSC2101_BIT(4) +#define AC5_CPI2SPK2 TSC2101_BIT(3) +#define AC5_MUTSPK1 TSC2101_BIT(2) +#define AC5_MUTSPK2 TSC2101_BIT(1) +#define AC5_HDSCPTC (0x01) + +/* Field masks settings for TSC2101_AUDIO_CTRL_6 */ +#define AC6_SPL2LSK TSC2101_BIT(15) +#define AC6_AST2LSK TSC2101_BIT(14) +#define AC6_BUZ2LSK TSC2101_BIT(13) +#define AC6_KCL2LSK TSC2101_BIT(12) +#define AC6_CPI2LSK TSC2101_BIT(11) +#define AC6_MIC2CPO TSC2101_BIT(10) +#define AC6_SPL2CPO TSC2101_BIT(9) +#define AC6_SPR2CPO TSC2101_BIT(8) +#define AC6_MUTLSPK TSC2101_BIT(7) +#define AC6_MUTSPK2 TSC2101_BIT(6) +#define AC6_LDSCPTC TSC2101_BIT(5) +#define AC6_VGNDSCPTC TSC2101_BIT(4) +#define AC6_CAPINTF TSC2101_BIT(3) + +/* Field masks settings for TSC2101_AUDIO_CTRL_7 */ +#define AC7_DETECT TSC2101_BIT(15) +#define AC7_HESTYPE(ARG) (((ARG) & 0x03) << 13) +#define AC7_HDDETFL TSC2101_BIT(12) +#define AC7_BDETFL TSC2101_BIT(11) +#define AC7_HDDEBNPG(ARG) (((ARG) & 0x03) << 9) +#define AC7_BDEBNPG(ARG) (((ARG) & 0x03) << 6) +#define AC7_DGPIO2 TSC2101_BIT(4) +#define AC7_DGPIO1 TSC2101_BIT(3) +#define AC7_CLKGPIO2 TSC2101_BIT(2) +#define AC7_ADWSF(ARG) (((ARG) & 0x03)) + +/* Field masks settings for TSC2101_GPIO_CTRL */ +#define GC_GPO2EN TSC2101_BIT(15) +#define GC_GPO2SG TSC2101_BIT(14) +#define GC_GPI2EN TSC2101_BIT(13) +#define GC_GPI2SGF TSC2101_BIT(12) +#define GC_GPO1EN TSC2101_BIT(11) +#define GC_GPO1SG TSC2101_BIT(10) +#define GC_GPI1EN TSC2101_BIT(9) +#define GC_GPI1SGF TSC2101_BIT(8) + +/* Field masks for TSC2101_AGC_CTRL */ +#define AC_AGCNF_CELL TSC2101_BIT(14) +#define AC_AGCNL(ARG) (((ARG) & 0x07) << 11) +#define AC_AGCHYS_CELL(ARG) (((ARG) & 0x03) << 9) +#define AC_CLPST_CELL TSC2101_BIT(8) +#define AC_AGCTG_CELL(ARG) (((ARG) & 0x07) << 5) +#define AC_AGCTC_CELL(ARG) (((ARG) & 0x0F) << 1) +#define AC_AGCEN_CELL (0x01) + +/* Field masks for TSC2101_POWERDOWN_STS */ +#define PS_SPK1FL TSC2101_BIT(15) +#define PS_SPK2FL TSC2101_BIT(14) +#define PS_HNDFL TSC2101_BIT(13) +#define PS_VGNDFL TSC2101_BIT(12) +#define PS_LSPKFL TSC2101_BIT(11) +#define PS_CELLFL TSC2101_BIT(10) +#define PS_PSEQ TSC2101_BIT(5) +#define PS_PSTIME TSC2101_BIT(4) + +/* Field masks for Register Mic AGC Control */ +#define MAC_MMPGA(ARG) (((ARG) & 0x7F) << 9) +#define MAC_MDEBNS(ARG) (((ARG) & 0x07) << 6) +#define MAC_MDEBSN(ARG) (((ARG) & 0x07) << 3) + +/* Field masks for Register Cellphone AGC Control */ +#define CAC_CMPGA(ARG) (((ARG) & 0x7F) << 9) +#define CAC_CDEBNS(ARG) (((ARG) & 0x07) << 6) +#define CAC_CDEBSN(ARG) (((ARG) & 0x07) << 3) + +#endif /* __ASM_HARDWARE_TSC2101_H */ diff --git a/sound/oss/Kconfig b/sound/oss/Kconfig index a9602f89d6b..f78444ff508 100644 --- a/sound/oss/Kconfig +++ b/sound/oss/Kconfig @@ -4,6 +4,33 @@ # More hacking for modularisation. # # Prompt user for primary drivers. +config SOUND_OMAP + tristate "OMAP Sound Driver" + depends on SOUND_PRIME!=n && SOUND && ARCH_OMAP + ---help--- + OMAP Audio driver + +config SOUND_OMAP_TSC2101 + tristate "TSC2101 Stereo Codec" + depends on SOUND_OMAP && ( MACH_OMAP_H2 || MACH_OMAP_H3 ) + select OMAP_TSC2101 + select OMAP_UWIRE if ARCH_OMAP + ---help--- + Tsc2101 Audio Codec Driver for OMAP will be enabled. + Will also Enable the following: + 1. uWire Driver based on Platform + 2. TSC2101 Glue driver + +config SOUND_OMAP_AIC23 + tristate "AIC23 Stereo Codec" + depends on SOUND_OMAP && ( MACH_OMAP_INNOVATOR || MACH_OMAP_OSK ) + select OMAP_DSP if ARCH_OMAP + select SENSORS_TLV320AIC23 if ARCH_OMAP + ---help--- + AIC23 Audio Codec Driver for OMAP will be enabled. + This will also enable OMAP DSP support because McBSP needed for + this is a DSP peripheral. Additionally, AIC23 I2C support is enabled. + config SOUND_BT878 tristate "BT878 audio dma" depends on SOUND_PRIME!=n && SOUND diff --git a/sound/oss/Makefile b/sound/oss/Makefile index db9afb61d6f..20ebcc805f8 100644 --- a/sound/oss/Makefile +++ b/sound/oss/Makefile @@ -8,6 +8,10 @@ obj-$(CONFIG_SOUND_OSS) += sound.o obj-$(CONFIG_SOUND_CS4232) += cs4232.o ad1848.o +obj-$(CONFIG_SOUND_OMAP) += omap-audio-dma-intfc.o omap-audio.o +obj-$(CONFIG_SOUND_OMAP_TSC2101)+= omap-audio-tsc2101.o +obj-$(CONFIG_SOUND_OMAP_AIC23) += omap-audio-aic23.o + # Please leave it as is, cause the link order is significant ! obj-$(CONFIG_SOUND_SH_DAC_AUDIO) += sh_dac_audio.o diff --git a/sound/oss/omap-audio-aic23.c b/sound/oss/omap-audio-aic23.c new file mode 100644 index 00000000000..09d963fe38b --- /dev/null +++ b/sound/oss/omap-audio-aic23.c @@ -0,0 +1,734 @@ +/* + * linux/sound/oss/omap-audio-aic23.c + * + * Glue audio driver for TI TLV320AIC23 codec + * + * Copyright (c) 2000 Nicolas Pitre + * Copyright (C) 2001, Steve Johnson + * Copyright (C) 2004 Texas Instruments, Inc. + * Copyright (C) 2005 Dirk Behme + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "omap-audio.h" +#include "omap-audio-dma-intfc.h" + +#ifdef CONFIG_PROC_FS +#include +#define PROC_START_FILE "driver/aic23-audio-start" +#define PROC_STOP_FILE "driver/aic23-audio-stop" +#endif + +//#define DEBUG + +#ifdef DEBUG +#define DPRINTK(ARGS...) printk("<%s>: ",__FUNCTION__);printk(ARGS) +#else +#define DPRINTK( x... ) +#endif + +#define CODEC_NAME "AIC23" + +#if CONFIG_MACH_OMAP_OSK +#define PLATFORM_NAME "OMAP OSK" +#elif CONFIG_MACH_OMAP_INNOVATOR +#define PLATFORM_NAME "OMAP INNOVATOR" +#else +#error "Unsupported plattform" +#endif + +/* Define to set the AIC23 as the master w.r.t McBSP */ +#define AIC23_MASTER + +#define CODEC_CLOCK 12000000 + +/* + * AUDIO related MACROS + */ +#define DEFAULT_BITPERSAMPLE 16 +#define AUDIO_RATE_DEFAULT 44100 + +/* Select the McBSP For Audio */ +#define AUDIO_MCBSP OMAP_MCBSP1 + +#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC) +#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME) + +#define SET_VOLUME 1 +#define SET_LINE 2 + +#define DEFAULT_OUTPUT_VOLUME 93 +#define DEFAULT_INPUT_VOLUME 0 /* 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 NUMBER_SAMPLE_RATES_SUPPORTED 9 + +static audio_stream_t output_stream = { + .id = "AIC23 out", + .dma_dev = OMAP_DMA_MCBSP1_TX, + .input_or_output = FMODE_WRITE +}; + +static audio_stream_t input_stream = { + .id = "AIC23 in", + .dma_dev = OMAP_DMA_MCBSP1_RX, + .input_or_output = FMODE_READ +}; + +static struct clk *aic23_mclk = 0; + +static int audio_dev_id, mixer_dev_id; + +static struct aic23_local_info { + u8 volume; + u16 volume_reg; + u8 line; + u8 mic; + u16 input_volume_reg; + int mod_cnt; +} aic23_local; + +struct sample_rate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* To Store the default sample rate */ +static long audio_samplerate = AUDIO_RATE_DEFAULT; + +/* DAC USB-mode sampling rates (MCLK = 12 MHz) */ +static const struct sample_rate_reg_info +reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + {96000, 0x0E, 0}, + {88200, 0x1F, 0}, + {48000, 0x00, 0}, + {44100, 0x11, 0}, + {32000, 0x0C, 0}, + {24000, 0x00, 1}, + {16000, 0x0C, 1}, + { 8000, 0x06, 0}, + { 4000, 0x06, 1}, +}; + +static struct omap_mcbsp_reg_cfg initial_config = { + .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 void omap_aic23_initialize(void *dummy); +static void omap_aic23_shutdown(void *dummy); +static int omap_aic23_ioctl(struct inode *inode, struct file *file, + uint cmd, ulong arg); +static int omap_aic23_probe(void); +#ifdef MODULE +static void omap_aic23_remove(void); +#endif +static int omap_aic23_suspend(void); +static int omap_aic23_resume(void); +static inline void aic23_configure(void); +static int mixer_open(struct inode *inode, struct file *file); +static int mixer_release(struct inode *inode, struct file *file); +static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +#ifdef CONFIG_PROC_FS +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data); +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data); +#endif + + +/* File Op structure for mixer */ +static struct file_operations omap_mixer_fops = { + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .owner = THIS_MODULE +}; + +/* To store characteristic info regarding the codec for the audio driver */ +static audio_state_t aic23_state = { + .output_stream = &output_stream, + .input_stream = &input_stream, +/* .need_tx_for_rx = 1, //Once the Full Duplex works */ + .need_tx_for_rx = 0, + .hw_init = omap_aic23_initialize, + .hw_shutdown = omap_aic23_shutdown, + .client_ioctl = omap_aic23_ioctl, + .hw_probe = omap_aic23_probe, + .hw_remove = __exit_p(omap_aic23_remove), + .hw_suspend = omap_aic23_suspend, + .hw_resume = omap_aic23_resume, + .sem = __MUTEX_INITIALIZER(aic23_state.sem), +}; + +/* This will be defined in the audio.h */ +static struct file_operations *omap_audio_fops; + +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); +} + +static int aic23_update(int flag, int val) +{ + u16 volume; + + /* Ignore separate left/right channel for now, + even the codec does support it. */ + val &= 0xff; + + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n",val); + return -EPERM; + } + + switch (flag) { + case SET_VOLUME: + // Convert 0 -> 100 volume to 0x00 (LHV_MIN) -> 0x7f (LHV_MAX) + // volume range + volume = ((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN; + + // R/LHV[6:0] 1111111 (+6dB) to 0000000 (-73dB) in 1db steps, + // default 1111001 (0dB) + aic23_local.volume_reg &= ~OUTPUT_VOLUME_MASK; + aic23_local.volume_reg |= volume; + audio_aic23_write(LEFT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg); + audio_aic23_write(RIGHT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg); + break; + + case SET_LINE: + // Convert 0 -> 100 volume to 0x0 (LIV_MIN) -> 0x1f (LIV_MAX) + // volume range + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + + // R/LIV[4:0] 11111 (+12dB) to 00000 (-34.5dB) in 1.5dB steps, + // default 10111 (0dB) + aic23_local.input_volume_reg &= ~INPUT_VOLUME_MASK; + aic23_local.input_volume_reg |= volume; + audio_aic23_write(LEFT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg); + audio_aic23_write(RIGHT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg); + break; + } + return 0; +} + +static int mixer_open(struct inode *inode, struct file *file) +{ + /* Any mixer specific initialization */ + + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + /* Any mixer specific Un-initialization */ + + return 0; +} + +static int +mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + int val; + int ret = 0; + int nr = _IOC_NR(cmd); + + /* + * We only accept mixer (type 'M') ioctls. + */ + if (_IOC_TYPE(cmd) != 'M') + return -EINVAL; + + DPRINTK(" 0x%08x\n", cmd); + + if (cmd == SOUND_MIXER_INFO) { + struct mixer_info mi; + + strncpy(mi.id, "AIC23", sizeof(mi.id)); + strncpy(mi.name, "TI AIC23", sizeof(mi.name)); + mi.modify_counter = aic23_local.mod_cnt; + return copy_to_user((void *)arg, &mi, sizeof(mi)); + } + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = get_user(val, (int *)arg); + if (ret) + goto out; + + + switch (nr) { + case SOUND_MIXER_VOLUME: + aic23_local.volume = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_VOLUME, val); + break; + + case SOUND_MIXER_LINE: + aic23_local.line = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_LINE, val); + break; + + case SOUND_MIXER_MIC: + aic23_local.mic = val; + aic23_local.mod_cnt++; + ret = aic23_update(SET_LINE, val); + break; + + case SOUND_MIXER_RECSRC: + break; + + default: + ret = -EINVAL; + } + } + + if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { + ret = 0; + + switch (nr) { + case SOUND_MIXER_VOLUME: + val = aic23_local.volume; + break; + case SOUND_MIXER_LINE: + val = aic23_local.line; + break; + case SOUND_MIXER_MIC: + val = aic23_local.mic; + break; + case SOUND_MIXER_RECSRC: + val = REC_MASK; + break; + case SOUND_MIXER_RECMASK: + val = REC_MASK; + break; + case SOUND_MIXER_DEVMASK: + val = DEV_MASK; + break; + case SOUND_MIXER_CAPS: + val = 0; + break; + case SOUND_MIXER_STEREODEVS: + val = 0; + break; + default: + val = 0; + ret = -EINVAL; + break; + } + + if (ret == 0) + ret = put_user(val, (int *)arg); + } +out: + return ret; + +} + +int omap_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + /* wait for any frame to complete */ + udelay(125); + + /* Search for the right sample rate */ + while ((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; + } + + if (machine_is_omap_innovator()) { + /* set the CODEC clock input source to 12.000MHz */ + fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~0x01, + OMAP1510_FPGA_POWER); + } + + data = (reg_info[count].divider << CLKIN_SHIFT) | + (reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON; + + audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data); + + audio_samplerate = sample_rate; + +#ifndef AIC23_MASTER + { + int clkgdv = 0; + /* + Set Sample Rate at McBSP + + Formula : + Codec System Clock = CODEC_CLOCK, or half if clock_divider = 1; + clkgdv = ((Codec System Clock / (SampleRate * BitsPerSample * 2)) - 1); + + FWID = BitsPerSample - 1; + FPER = (BitsPerSample * 2) - 1; + */ + if (reg_info[count].divider) + clkgdv = CODEC_CLOCK / 2; + else + clkgdv = CODEC_CLOCK; + + clkgdv = (clkgdv / (sample_rate * DEFAULT_BITPERSAMPLE * 2)) - 1; + + initial_config.srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + + initial_config.srgr2 = + (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + } +#endif /* AIC23_MASTER */ + + return 0; +} + +static void omap_aic23_initialize(void *dummy) +{ + DPRINTK("entry\n"); + + /* initialize with default sample rate */ + audio_samplerate = AUDIO_RATE_DEFAULT; + + omap_mcbsp_request(AUDIO_MCBSP); + + /* if configured, then stop mcbsp */ + omap_mcbsp_stop(AUDIO_MCBSP); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + omap_mcbsp_start(AUDIO_MCBSP); + aic23_configure(); + + DPRINTK("exit\n"); +} + +static void omap_aic23_shutdown(void *dummy) +{ + /* + Turn off codec after it is done. + Can't do it immediately, since it may still have + buffered data. + + Wait 20ms (arbitrary value) and then turn it off. + */ + + 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); +} + +static inline void aic23_configure() +{ + /* Reset codec */ + audio_aic23_write(RESET_CONTROL_ADDR, 0); + + /* Initialize the AIC23 internal state */ + + /* Left/Right line input volume control */ + aic23_local.line = DEFAULT_INPUT_VOLUME; + aic23_local.mic = DEFAULT_INPUT_VOLUME; + aic23_update(SET_LINE, DEFAULT_INPUT_VOLUME); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + aic23_local.volume_reg = LZC_ON; + aic23_update(SET_VOLUME, aic23_local.volume); + + /* Analog audio path control, DAC selected, delete INSEL_MIC for line in */ + audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DAC_SELECTED | INSEL_MIC); + + /* Digital audio path control, de-emphasis control 44.1kHz */ + audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K); + + /* Power control, everything is on */ + audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0); + + /* 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 /* AIC23_MASTER */ + + /* Enable digital interface */ + audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON); + + /* clock configuration */ + omap_set_samplerate(audio_samplerate); +} + +static int +omap_aic23_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + long val; + int ret = 0; + + DPRINTK(" 0x%08x\n", cmd); + + /* + * These are platform dependent ioctls which are not handled by the + * generic omap-audio module. + */ + switch (cmd) { + case SNDCTL_DSP_STEREO: + ret = get_user(val, (int *)arg); + if (ret) + return ret; + /* the AIC23 is stereo only */ + ret = (val == 0) ? -EINVAL : 1; + return put_user(ret, (int *)arg); + + case SNDCTL_DSP_CHANNELS: + case SOUND_PCM_READ_CHANNELS: + /* the AIC23 is stereo only */ + return put_user(2, (long *)arg); + + case SNDCTL_DSP_SPEED: + ret = get_user(val, (long *)arg); + if (ret) + break; + ret = omap_set_samplerate(val); + if (ret) + break; + /* fall through */ + + case SOUND_PCM_READ_RATE: + return put_user(audio_samplerate, (long *)arg); + + case SOUND_PCM_READ_BITS: + case SNDCTL_DSP_SETFMT: + case SNDCTL_DSP_GETFMTS: + /* we can do 16-bit only */ + return put_user(AFMT_S16_LE, (long *)arg); + + default: + /* Maybe this is meant for the mixer (As per OSS Docs) */ + return mixer_ioctl(inode, file, cmd, arg); + } + + return ret; +} + +static int omap_aic23_probe(void) +{ + /* Get the fops from audio oss driver */ + if (!(omap_audio_fops = audio_get_fops())) { + printk(KERN_ERR "Unable to get the file operations for AIC23 OSS driver\n"); + audio_unregister_codec(&aic23_state); + return -EPERM; + } + + aic23_local.volume = DEFAULT_OUTPUT_VOLUME; + + /* register devices */ + audio_dev_id = register_sound_dsp(omap_audio_fops, -1); + mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1); + +#ifdef CONFIG_PROC_FS + create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_start, NULL /* client data */ ); + + create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_stop, NULL /* client data */ ); +#endif + + /* Announcement Time */ + printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME + " audio support initialized\n"); + return 0; +} + +#ifdef MODULE +static void __exit omap_aic23_remove(void) +{ + /* Un-Register the codec with the audio driver */ + unregister_sound_dsp(audio_dev_id); + unregister_sound_mixer(mixer_dev_id); + +#ifdef CONFIG_PROC_FS + remove_proc_entry(PROC_START_FILE, NULL); + remove_proc_entry(PROC_STOP_FILE, NULL); +#endif +} +#endif /* MODULE */ + +static int omap_aic23_suspend(void) +{ + /* Empty for the moment */ + return 0; +} + +static int omap_aic23_resume(void) +{ + /* Empty for the moment */ + return 0; +} + +static int __init audio_aic23_init(void) +{ + + int err = 0; + + if (machine_is_omap_h2() || machine_is_omap_h3()) + return -ENODEV; + + if (machine_is_omap_osk()) { + /* Set MCLK to be clock input for AIC23 */ + aic23_mclk = clk_get(0, "mclk"); + + if(clk_get_rate( aic23_mclk) != CODEC_CLOCK){ + /* MCLK ist not at CODEC_CLOCK */ + 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_use( aic23_mclk ); + + DPRINTK("MCLK = %d [%d], usecount = %d\n",(uint)clk_get_rate( aic23_mclk ), + CODEC_CLOCK, clk_get_usecount( aic23_mclk)); + } + + if (machine_is_omap_innovator()) { + u8 fpga; + /* + Turn on chip select for CODEC (shared with touchscreen). + Don't turn it back off, in case touch screen needs it. + */ + fpga = fpga_read(OMAP1510_FPGA_TOUCHSCREEN); + fpga |= 0x4; + fpga_write(fpga, OMAP1510_FPGA_TOUCHSCREEN); + } + + /* register the codec with the audio driver */ + if ((err = audio_register_codec(&aic23_state))) { + printk(KERN_ERR + "Failed to register AIC23 driver with Audio OSS Driver\n"); + } + + return err; +} + +static void __exit audio_aic23_exit(void) +{ + (void)audio_unregister_codec(&aic23_state); + return; +} + +#ifdef CONFIG_PROC_FS +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + void *foo = NULL; + + omap_aic23_initialize(foo); + + printk("AIC23 codec initialization done.\n"); + return 0; +} +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + void *foo = NULL; + + omap_aic23_shutdown(foo); + + printk("AIC23 codec shutdown.\n"); + return 0; +} +#endif /* CONFIG_PROC_FS */ + +module_init(audio_aic23_init); +module_exit(audio_aic23_exit); + +MODULE_AUTHOR("Dirk Behme "); +MODULE_DESCRIPTION("Glue audio driver for the TI AIC23 codec."); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/omap-audio-dma-intfc.c b/sound/oss/omap-audio-dma-intfc.c new file mode 100644 index 00000000000..b0214627104 --- /dev/null +++ b/sound/oss/omap-audio-dma-intfc.c @@ -0,0 +1,954 @@ +/* + * linux/sound/oss/omap-audio-dma-intfc.c + * + * Common audio DMA handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c. This file + * will contain only the DMA interface and buffer handling of OMAP + * audio driver. + * + * 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking of DMA logical channel. + * + * 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004-11-01 Nishanth Menon - 16xx platform code base modified to support multi channel chaining. + * + * 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic introduced - tasklets, queue handling updated + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "omap-audio-dma-intfc.h" + +#include + +#include "omap-audio.h" + +#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); + +#define AUDIO_NAME "omap-audio" +#define AUDIO_NBFRAGS_DEFAULT 8 +#define AUDIO_FRAGSIZE_DEFAULT 8192 + +#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref) + +#define SPIN_ADDR (dma_addr_t)0 +#define SPIN_SIZE 2048 + +/* Channel Queue Handling macros + * tail always points to the current free entry + * Head always points to the current entry being used + * end is either head or tail + */ + +#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0; +#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count) +#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count) +#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count) +#define __AUDIO_INCREMENT_QUEUE(end) ((end)=((end)+1) % nr_linked_channels) +#define AUDIO_INCREMENT_HEAD(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_head); s->dma_q_count--; +#define AUDIO_INCREMENT_TAIL(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); s->dma_q_count++; + +/* DMA buffer fragmentation sizes */ +#define MAX_DMA_SIZE 0x1000000 +#define CUT_DMA_SIZE 0x1000 +/* TODO: To be moved to more appropriate location */ +#define DCSR_ERROR 0x3 +#define DCSR_SYNC_SET (1 << 6) + +#define DCCR_FS (1 << 5) +#define DCCR_PRIO (1 << 6) +#define DCCR_EN (1 << 7) +#define DCCR_AI (1 << 8) +#define DCCR_REPEAT (1 << 9) +/* if 0 the channel works in 3.1 compatible mode*/ +#define DCCR_N31COMP (1 << 10) +#define DCCR_EP (1 << 11) +#define DCCR_SRC_AMODE_BIT 12 +#define DCCR_SRC_AMODE_MASK (0x3<<12) +#define DCCR_DST_AMODE_BIT 14 +#define DCCR_DST_AMODE_MASK (0x3<<14) +#define AMODE_CONST 0x0 +#define AMODE_POST_INC 0x1 +#define AMODE_SINGLE_INDEX 0x2 +#define AMODE_DOUBLE_INDEX 0x3 + +/**************************** DATA STRUCTURES *****************************************/ + +static spinlock_t dma_list_lock = SPIN_LOCK_UNLOCKED; + +struct audio_isr_work_item { + int current_lch; + u16 ch_status; + audio_stream_t *s; +}; + +static char work_item_running = 0; +static char nr_linked_channels = 1; +static struct audio_isr_work_item work1, work2; + + +/*********************************** MODULE SPECIFIC FUNCTIONS PROTOTYPES *************/ + +static void audio_dsr_handler(unsigned long); +static DECLARE_TASKLET(audio_isr_work1, audio_dsr_handler, + (unsigned long)&work1); +static DECLARE_TASKLET(audio_isr_work2, audio_dsr_handler, + (unsigned long)&work2); + +static void sound_dma_irq_handler(int lch, u16 ch_status, void *data); +static void audio_dma_callback(int lch, u16 ch_status, void *data); +static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr, + u_int size); +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size); +static int audio_start_dma_chain(audio_stream_t * s); + +/*********************************** GLOBAL FUNCTIONS DEFINTIONS ***********************/ + +/*************************************************************************************** + * + * Buffer creation/destruction + * + **************************************************************************************/ +int audio_setup_buf(audio_stream_t * s) +{ + int frag; + int dmasize = 0; + char *dmabuf = NULL; + dma_addr_t dmaphys = 0; + FN_IN; + if (s->buffers) { + FN_OUT(1); + return -EBUSY; + } + s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL); + if (!s->buffers) + goto err; + memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags); + for (frag = 0; frag < s->nbfrags; frag++) { + audio_buf_t *b = &s->buffers[frag]; + /* + * Let's allocate non-cached memory for DMA buffers. + * We try to allocate all memory at once. + * If this fails (a common reason is memory fragmentation), + * then we allocate more smaller buffers. + */ + if (!dmasize) { + dmasize = (s->nbfrags - frag) * s->fragsize; + do { + dmabuf = + dma_alloc_coherent(NULL, dmasize, &dmaphys, + 0); + if (!dmabuf) + dmasize -= s->fragsize; + } + while (!dmabuf && dmasize); + if (!dmabuf) + goto err; + b->master = dmasize; + memzero(dmabuf, dmasize); + } + b->data = dmabuf; + b->dma_addr = dmaphys; + dmabuf += s->fragsize; + dmaphys += s->fragsize; + dmasize -= s->fragsize; + } + s->usr_head = s->dma_head = s->dma_tail = 0; + AUDIO_QUEUE_INIT(s); + s->started = 0; + s->bytecount = 0; + s->fragcount = 0; + sema_init(&s->sem, s->nbfrags); + FN_OUT(0); + return 0; + err: + audio_discard_buf(s); + FN_OUT(1); + return -ENOMEM; +} + +void audio_discard_buf(audio_stream_t * s) +{ + FN_IN; + /* ensure DMA isn't using those buffers */ + audio_reset(s); + if (s->buffers) { + int frag; + for (frag = 0; frag < s->nbfrags; frag++) { + if (!s->buffers[frag].master) + continue; + dma_free_coherent(NULL, + s->buffers[frag].master, + s->buffers[frag].data, + s->buffers[frag].dma_addr); + } + kfree(s->buffers); + s->buffers = NULL; + } + FN_OUT(0); +} + +/*************************************************************************************** + * + * DMA channel requests + * + **************************************************************************************/ +static void omap_sound_dma_link_lch(void *data) +{ + audio_stream_t *s = (audio_stream_t *) data; + int *chan = s->lch; + int i; + + FN_IN; + if (s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_link_lch(cur_chan, nex_chan); + } + s->linked = 1; + FN_OUT(0); +} + +int +omap_request_sound_dma(int device_id, const char *device_name, void *data, + int **channels) +{ + int i, err = 0; + int *chan = NULL; + FN_IN; + if (unlikely((NULL == channels) || (NULL == device_name))) { + BUG(); + return -EPERM; + } + /* Try allocate memory for the num channels */ + *channels = + (int *)kmalloc(sizeof(int) * nr_linked_channels, + GFP_KERNEL); + chan = *channels; + if (NULL == chan) { + ERR("No Memory for channel allocs!\n"); + FN_OUT(-ENOMEM); + return -ENOMEM; + } + 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]); + /* Handle Failure condition here */ + if (err < 0) { + int j; + for (j = 0; j < i; j++) { + omap_free_dma(chan[j]); + } + spin_unlock(&dma_list_lock); + kfree(chan); + *channels = NULL; + ERR("Error in requesting channel %d=0x%x\n", i, err); + FN_OUT(err); + return err; + } + } + + /* Chain the channels together */ + if (!cpu_is_omap1510()) + omap_sound_dma_link_lch(data); + + spin_unlock(&dma_list_lock); + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * DMA channel requests Freeing + * + **************************************************************************************/ +static void omap_sound_dma_unlink_lch(void *data) +{ + audio_stream_t *s = (audio_stream_t *) data; + int *chan = s->lch; + int i; + + FN_IN; + if (!s->linked) { + FN_OUT(1); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + int nex_chan = + ((nr_linked_channels - 1 == + i) ? chan[0] : chan[i + 1]); + omap_dma_unlink_lch(cur_chan, nex_chan); + } + s->linked = 0; + FN_OUT(0); +} + +int omap_free_sound_dma(void *data, int **channels) +{ + int i; + int *chan = NULL; + FN_IN; + if (unlikely(NULL == channels)) { + BUG(); + return -EPERM; + } + if (unlikely(NULL == *channels)) { + BUG(); + return -EPERM; + } + chan = (*channels); + + if (!cpu_is_omap1510()) + omap_sound_dma_unlink_lch(data); + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + omap_free_dma(cur_chan); + } + kfree(*channels); + *channels = NULL; + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * Process DMA requests - This will end up starting the transfer. Proper fragments of + * Transfers will be initiated. + * + **************************************************************************************/ +int audio_process_dma(audio_stream_t * s) +{ + int ret = 0; + unsigned long flags; + FN_IN; + + /* Dont let the ISR over ride touching the in_use flag */ + local_irq_save(flags); + if (1 == s->in_use) { + local_irq_restore(flags); + ERR("Called again while In Use\n"); + return 0; + } + s->in_use = 1; + local_irq_restore(flags); + + if (s->stopped) + goto spin; + + if (s->dma_spinref > 0 && s->pending_frags) { + s->dma_spinref = 0; + DMA_CLEAR(s); + } + while (s->pending_frags) { + audio_buf_t *b = &s->buffers[s->dma_head]; + u_int dma_size = s->fragsize - b->offset; + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + ret = + omap_start_sound_dma(s, b->dma_addr + b->offset, dma_size); + if (ret) { + goto process_out; + } + b->dma_ref++; + b->offset += dma_size; + if (b->offset >= s->fragsize) { + s->pending_frags--; + if (++s->dma_head >= s->nbfrags) + s->dma_head = 0; + } + } + spin: + if (s->spin_idle) { + int spincnt = 0; + ERR("we are spinning\n"); + while (omap_start_sound_dma(s, SPIN_ADDR, SPIN_SIZE) == 0) + spincnt++; + /* + * Note: if there is still a data buffer being + * processed then the ref count is negative. This + * allows for the DMA termination to be accounted in + * the proper order. Of course dma_spinref can't be + * greater than 0 if dma_ref is not 0 since we kill + * the spinning above as soon as there is real data to process. + */ + if (s->buffers && s->buffers[s->dma_tail].dma_ref) + spincnt = -spincnt; + s->dma_spinref += spincnt; + } + + process_out: + s->in_use = 0; + + FN_OUT(ret); + return ret; +} + +/*************************************************************************************** + * + * Prime Rx - Since the recieve buffer has no time limit as to when it would arrive, + * we need to prime it + * + **************************************************************************************/ +void audio_prime_rx(audio_state_t * state) +{ + audio_stream_t *is = state->input_stream; + + FN_IN; + if (state->need_tx_for_rx) { + /* + * With some codecs like the Philips UDA1341 we must ensure + * there is an output stream at any time while recording since + * this is how the UDA1341 gets its clock from the SA1100. + * So while there is no playback data to send, the output DMA + * will spin with all zeroes. We use the cache flush special + * area for that. + */ + state->output_stream->spin_idle = 1; + audio_process_dma(state->output_stream); + } + is->pending_frags = is->nbfrags; + sema_init(&is->sem, 0); + is->active = 1; + audio_process_dma(is); + + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * set the fragment size + * + **************************************************************************************/ +int audio_set_fragments(audio_stream_t * s, int val) +{ + FN_IN; + if (s->active) + return -EBUSY; + if (s->buffers) + audio_discard_buf(s); + s->nbfrags = (val >> 16) & 0x7FFF; + val &= 0xFFFF; + if (val < 4) + val = 4; + if (val > 15) + val = 15; + s->fragsize = 1 << val; + if (s->nbfrags < 2) + s->nbfrags = 2; + if (s->nbfrags * s->fragsize > 128 * 1024) + s->nbfrags = 128 * 1024 / s->fragsize; + FN_OUT(0); + if (audio_setup_buf(s)) + return -ENOMEM; + return val | (s->nbfrags << 16); + +} + +/*************************************************************************************** + * + * Sync up the buffers before we shutdown, else under-run errors will happen + * + **************************************************************************************/ +int audio_sync(struct file *file) +{ + audio_state_t *state = file->private_data; + audio_stream_t *s = state->output_stream; + audio_buf_t *b; + u_int shiftval = 0; + unsigned long flags; + + DECLARE_WAITQUEUE(wait, current); + + FN_IN; + + if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped) { + FN_OUT(1); + return 0; + } + + /* + * Send current buffer if it contains data. Be sure to send + * a full sample count. + */ + b = &s->buffers[s->usr_head]; + if (b->offset &= ~3) { + down(&s->sem); + /* + * HACK ALERT ! + * To avoid increased complexity in the rest of the code + * where full fragment sizes are assumed, we cheat a little + * with the start pointer here and don't forget to restore + * it later. + */ + + /* As this is a last frag we need only one dma channel + * to complete. So it's need to unlink dma channels + * to avoid empty dma work. + */ + if (!cpu_is_omap1510()) + omap_sound_dma_unlink_lch(s); + + shiftval = s->fragsize - b->offset; + b->offset = shiftval; + b->dma_addr -= shiftval; + b->data -= shiftval; + local_irq_save(flags); + s->bytecount -= shiftval; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + + s->pending_frags++; + audio_process_dma(s); + local_irq_restore(flags); + } + + /* Let's wait for all buffers to complete */ + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&s->wq, &wait); + while ((s->pending_frags || (atomic_read(&s->sem.count) < s->nbfrags)) + && !signal_pending(current)) { + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&s->wq, &wait); + + /* undo the pointer hack above */ + if (shiftval) { + local_irq_save(flags); + b->dma_addr += shiftval; + b->data += shiftval; + /* ensure sane DMA code behavior if not yet processed */ + if (b->offset != 0) + b->offset = s->fragsize; + local_irq_restore(flags); + } + + FN_OUT(0); + return 0; +} + +/*************************************************************************************** + * + * Stop all the DMA channels of the stream + * + **************************************************************************************/ +void audio_stop_dma(audio_stream_t * s) +{ + int *chan = s->lch; + int i; + FN_IN; + if (unlikely(NULL == chan)) { + BUG(); + return; + } + for (i = 0; i < nr_linked_channels; i++) { + int cur_chan = chan[i]; + omap_stop_dma(cur_chan); + } + s->started = 0; + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * Get the dma posn + * + **************************************************************************************/ +u_int audio_get_dma_pos(audio_stream_t * s) +{ + audio_buf_t *b = &s->buffers[s->dma_tail]; + u_int offset; + + FN_IN; + if (b->dma_ref) { + offset = omap_get_dma_src_pos(s->lch[s->dma_q_head]) - b->dma_addr; + if (offset >= s->fragsize) + offset = s->fragsize - 4; + } else if (s->pending_frags) { + offset = b->offset; + } else { + offset = 0; + } + FN_OUT(offset); + return offset; +} + +/*************************************************************************************** + * + * Reset the audio buffers + * + **************************************************************************************/ +void audio_reset(audio_stream_t * s) +{ + FN_IN; + if (s->buffers) { + audio_stop_dma(s); + s->buffers[s->dma_head].offset = 0; + s->buffers[s->usr_head].offset = 0; + s->usr_head = s->dma_head; + s->pending_frags = 0; + sema_init(&s->sem, s->nbfrags); + } + s->active = 0; + s->stopped = 0; + s->started = 0; + FN_OUT(0); + return; +} + +/*************************************************************************************** + * + * Clear any pending transfers + * + **************************************************************************************/ +void omap_clear_sound_dma(audio_stream_t * s) +{ + FN_IN; + omap_clear_dma(s->lch[s->dma_q_head]); + FN_OUT(0); + return; +} + +/*********************************** MODULE FUNCTIONS DEFINTIONS ***********************/ + +#ifdef OMAP1610_MCBSP1_BASE +#undef OMAP1610_MCBSP1_BASE +#endif +#define OMAP1610_MCBSP1_BASE 0xE1011000 + +/*************************************************************************************** + * + * DMA related functions + * + **************************************************************************************/ +static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + 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)); + omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr); + omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00); + FN_OUT(0); + return 0; +} + +static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr, + u_int dma_size) +{ + int dt = 0x1; /* data type 16 */ + int cen = 16; /* mono */ + int cfn = dma_size / (2 * cen); + FN_IN; + omap_set_dma_src_params(channel, 0x05, 0x00, + (OMAP1610_MCBSP1_BASE + 0x802)); + omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr); + omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00); + FN_OUT(0); + return 0; +} + +static int audio_start_dma_chain(audio_stream_t * s) +{ + int channel = s->lch[s->dma_q_head]; + FN_IN; + if (!s->started) { + omap_start_dma(channel); + s->started = 1; + } + /* else the dma itself will progress forward with out our help */ + FN_OUT(0); + return 0; +} + +/* Start DMA - + * Do the initial set of work to initialize all the channels as required. + * We shall then initate a transfer + */ +static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr, + u_int dma_size) +{ + int ret = -EPERM; + + FN_IN; + if (unlikely(dma_size > MAX_DMA_SIZE)) { + ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size, + MAX_DMA_SIZE); + return -EOVERFLOW; + } + + if (AUDIO_QUEUE_FULL(s)) { + ret = -2; + goto sound_out; + } + + if (s->input_or_output == FMODE_WRITE) + /*playback */ + { + ret = + audio_set_dma_params_play(s->lch[s->dma_q_tail], dma_ptr, + dma_size); + } else { + ret = + audio_set_dma_params_capture(s->lch[s->dma_q_tail], dma_ptr, + dma_size); + } + if (ret != 0) { + ret = -2; /* indicate queue full */ + goto sound_out; + } + AUDIO_INCREMENT_TAIL(s); + ret = audio_start_dma_chain(s); + if (ret) { + ERR("dma start failed"); + } + sound_out: + FN_OUT(ret); + return ret; + +} + +/*************************************************************************************** + * + * ISR related functions + * + **************************************************************************************/ +/* The work item handler */ +static void audio_dsr_handler(unsigned long inData) +{ + void *data = (void *)inData; + struct audio_isr_work_item *work = data; + audio_stream_t *s = (work->s); + int sound_curr_lch = work->current_lch; + u16 ch_status = work->ch_status; + + FN_IN; + DPRINTK("lch=%d,status=0x%x, data=%p as=%p\n", sound_curr_lch, + ch_status, data, s); + if (AUDIO_QUEUE_EMPTY(s)) { + ERR("Interrupt(%d) for empty queue(h=%d, T=%d)???\n", + sound_curr_lch, s->dma_q_head, s->dma_q_tail); + ERR("nbfrag=%d,pendfrags=%d,USR-H=%d, QH-%d QT-%d\n", + s->nbfrags, s->pending_frags, s->usr_head, s->dma_head, + s->dma_tail); + FN_OUT(-1); + return; + } + + AUDIO_INCREMENT_HEAD(s); /* Empty the queue */ + + /* Try to fill again */ + audio_dma_callback(sound_curr_lch, ch_status, s); + FN_OUT(0); + +} + +/* Macro to trace the IRQ calls - checks for multi-channel irqs */ +//#define IRQ_TRACE +#ifdef IRQ_TRACE +#define MAX_UP 10 +static char xyz[MAX_UP] = { 0 }; +static int h = 0; +#endif + +/* ISRs have to be short and smart.. So we transfer every heavy duty stuff to the + * work item + */ +static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status, void *data) +{ + int dma_status = ch_status; + audio_stream_t *s = (audio_stream_t *) data; + FN_IN; +#ifdef IRQ_TRACE + xyz[h++] = '0' + sound_curr_lch; + if (h == MAX_UP - 1) { + printk("%s-", xyz); + h = 0; + } +#endif + DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n", sound_curr_lch, + ch_status, dma_status, data); + + if (dma_status & (DCSR_ERROR)) { + omap_writew(omap_readw(OMAP_DMA_CCR(sound_curr_lch)) & ~DCCR_EN, + OMAP_DMA_CCR(sound_curr_lch)); + ERR("DCSR_ERROR!\n"); + FN_OUT(-1); + return; + } + + if (AUDIO_QUEUE_LAST(s)) + audio_stop_dma(s); + + /* Start the work item - we ping pong the work items */ + if (!work_item_running) { + work1.current_lch = sound_curr_lch; + work1.ch_status = ch_status; + work1.s = s; + /* schedule tasklet 1 */ + tasklet_schedule(&audio_isr_work1); + work_item_running = 1; + } else { + work2.current_lch = sound_curr_lch; + work2.ch_status = ch_status; + work2.s = s; + /* schedule tasklet 2 */ + tasklet_schedule(&audio_isr_work2); + work_item_running = 0; + } + + FN_OUT(0); + return; +} + +/* The call back that handles buffer stuff */ +static void audio_dma_callback(int lch, u16 ch_status, void *data) +{ + audio_stream_t *s = data; + audio_buf_t *b = &s->buffers[s->dma_tail]; + FN_IN; + + if (s->dma_spinref > 0) { + s->dma_spinref--; + } else if (!s->buffers) { + printk(KERN_CRIT + "omap_audio: received DMA IRQ for non existent buffers!\n"); + return; + } else if (b->dma_ref && --b->dma_ref == 0 && b->offset >= s->fragsize) { + /* This fragment is done */ + b->offset = 0; + s->bytecount += s->fragsize; + s->fragcount++; + s->dma_spinref = -s->dma_spinref; + + if (++s->dma_tail >= s->nbfrags) + s->dma_tail = 0; + + if (!s->mapped) + up(&s->sem); + else + s->pending_frags++; + + wake_up(&s->wq); + } + + audio_process_dma(s); + + FN_OUT(0); + return; +} + +/********************************************************************************* + * + * audio_get_dma_callback(): return the dma interface call back function + * + *********************************************************************************/ +dma_callback_t audio_get_dma_callback(void) +{ + FN_IN; + FN_OUT(0); + return audio_dma_callback; +} + +static int __init audio_dma_init(void) +{ + if (!cpu_is_omap1510()) + nr_linked_channels = 2; + + return 0; +} + +static void __exit audio_dma_exit(void) +{ + /* Nothing */ +} + +module_init(audio_dma_init); +module_exit(audio_dma_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(omap_clear_sound_dma); +EXPORT_SYMBOL(omap_request_sound_dma); +EXPORT_SYMBOL(omap_free_sound_dma); + +EXPORT_SYMBOL(audio_get_dma_callback); +EXPORT_SYMBOL(audio_setup_buf); +EXPORT_SYMBOL(audio_process_dma); +EXPORT_SYMBOL(audio_prime_rx); +EXPORT_SYMBOL(audio_set_fragments); +EXPORT_SYMBOL(audio_sync); +EXPORT_SYMBOL(audio_stop_dma); +EXPORT_SYMBOL(audio_get_dma_pos); +EXPORT_SYMBOL(audio_reset); +EXPORT_SYMBOL(audio_discard_buf); diff --git a/sound/oss/omap-audio-dma-intfc.h b/sound/oss/omap-audio-dma-intfc.h new file mode 100644 index 00000000000..65aa528f21c --- /dev/null +++ b/sound/oss/omap-audio-dma-intfc.h @@ -0,0 +1,63 @@ +/* + * linux/sound/oss/omap-audio-dma-intfc.h + * + * Common audio DMA handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + */ + +#ifndef __OMAP_AUDIO_DMA_INTFC_H +#define __OMAP_AUDIO_DMA_INTFC_H + +/************************** INCLUDES *************************************/ + +/* Requires omap-audio.h */ +#include "omap-audio.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) + +/************************** GLOBAL DATA STRUCTURES *********************************/ + +typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data); + +/************************** GLOBAL FUNCTIONS ***************************************/ + +dma_callback_t audio_get_dma_callback(void); +int audio_setup_buf(audio_stream_t * s); +int audio_process_dma(audio_stream_t * s); +void audio_prime_rx(audio_state_t * state); +int audio_set_fragments(audio_stream_t * s, int val); +int audio_sync(struct file *file); +void audio_stop_dma(audio_stream_t * s); +u_int audio_get_dma_pos(audio_stream_t * s); +void audio_reset(audio_stream_t * s); +void audio_discard_buf(audio_stream_t * s); + +/**************** ARCH SPECIFIC FUNCIONS *******************************************/ + +void omap_clear_sound_dma(audio_stream_t * s); + +int omap_request_sound_dma(int device_id, const char *device_name, void *data, + int **channels); +int omap_free_sound_dma(void *data, int **channels); + +#endif /* #ifndef __OMAP_AUDIO_DMA_INTFC_H */ diff --git a/sound/oss/omap-audio-tsc2101.c b/sound/oss/omap-audio-tsc2101.c new file mode 100644 index 00000000000..320e37737e6 --- /dev/null +++ b/sound/oss/omap-audio-tsc2101.c @@ -0,0 +1,1222 @@ +/* + * linux/sound/oss/omap-audio-tsc2101.c + * + * Glue driver for TSC2101 for OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * ------- + * 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms. + * 2004-09-14 Sriram Kannan - Added /proc support for asynchronous starting/stopping the codec + * (without affecting the normal driver flow). + * 2004-11-04 Nishanth Menon - Support for power management + * 2004-11-07 Nishanth Menon - Support for Common TSC access b/w Touchscreen and audio drivers + */ + +/***************************** INCLUDES ************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "omap-audio.h" +#include "omap-audio-dma-intfc.h" +#include +#if CONFIG_ARCH_OMAP16XX +#include <../drivers/ssi/omap-uwire.h> +#include +#else +#error "Unsupported configuration" +#endif + +#include +#include <../drivers/ssi/omap-tsc2101.h> + +/***************************** MACROS ************************************/ + +#define PROC_SUPPORT + +#ifdef PROC_SUPPORT +#include +#define PROC_START_FILE "driver/tsc2101-audio-start" +#define PROC_STOP_FILE "driver/tsc2101-audio-stop" +#endif + +#define CODEC_NAME "TSC2101" + +#if CONFIG_ARCH_OMAP16XX +#define PLATFORM_NAME "OMAP16XX" +#endif + +#if CONFIG_ARCH_OMAP16XX +#define OMAP_DSP_BASE 0xE0000000 +#endif + +/* Define to set the tsc as the master w.r.t McBSP */ +#define TSC_MASTER + +/* + * AUDIO related MACROS + */ +#define DEFAULT_BITPERSAMPLE 16 +#define AUDIO_RATE_DEFAULT 44100 +#define PAGE2_AUDIO_CODEC_REGISTERS (2) +#define LEAVE_CS 0x80 + +/* Select the McBSP For Audio */ +#if CONFIG_ARCH_OMAP16XX +#define AUDIO_MCBSP OMAP_MCBSP1 +#else +#error "UnSupported Configuration" +#endif + +#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC) +#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME) + +#define SET_VOLUME 1 +#define SET_LINE 2 +#define SET_MIC 3 +#define SET_RECSRC 4 + +#define DEFAULT_VOLUME 93 +#define DEFAULT_INPUT_VOLUME 20 /* An minimal volume */ + +/* Tsc Audio Specific */ +#define NUMBER_SAMPLE_RATES_SUPPORTED 16 +#define OUTPUT_VOLUME_MIN 0x7F +#define OUTPUT_VOLUME_MAX 0x32 +#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) +#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MIN +#define DEFAULT_VOLUME_LEVEL 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 INPUT_VOLUME_MASK INPUT_VOLUME_MAX + +/*********** Debug Macros ********/ +/* To Generate a rather shrill tone -test the entire path */ +//#define TONE_GEN +/* To Generate a tone for each keyclick - test the tsc,spi paths*/ +//#define TEST_KEYCLICK +/* To dump the tsc registers for debug */ +//#define TSC_DUMP_REGISTERS + +#ifdef DPRINTK +#undef DPRINTK +#endif +#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(n) +#endif + +/***************************** Data Structures **********************************/ + +static audio_stream_t output_stream = { + .id = "TSC2101 out", + .dma_dev = OMAP_DMA_MCBSP1_TX, + .input_or_output = FMODE_WRITE +}; + +static audio_stream_t input_stream = { + .id = "TSC2101 in", + .dma_dev = OMAP_DMA_MCBSP1_RX, + .input_or_output = FMODE_READ +}; + +static int audio_dev_id, mixer_dev_id; + +typedef struct { + u8 volume; + u8 line; + u8 mic; + int recsrc; + int mod_cnt; +} tsc2101_local_info; + +static tsc2101_local_info tsc2101_local = { + volume: DEFAULT_VOLUME, + line: DEFAULT_INPUT_VOLUME, + mic: DEFAULT_INPUT_VOLUME, + recsrc: SOUND_MASK_LINE, + mod_cnt: 0 +}; + +struct sample_rate_reg_info { + u16 sample_rate; + u8 divisor; + u8 fs_44kHz; /* if 0 48 khz, if 1 44.1 khz fsref */ +}; + +/* To Store the default sample rate */ +static long audio_samplerate = AUDIO_RATE_DEFAULT; + +static const struct sample_rate_reg_info + reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = { + /* Div 1 */ + {48000, 0, 0}, + {44100, 0, 1}, + /* Div 1.5 */ + {32000, 1, 0}, + {29400, 1, 1}, + /* Div 2 */ + {24000, 2, 0}, + {22050, 2, 1}, + /* Div 3 */ + {16000, 3, 0}, + {14700, 3, 1}, + /* Div 4 */ + {12000, 4, 0}, + {11025, 4, 1}, + /* Div 5 */ + {9600, 5, 0}, + {8820, 5, 1}, + /* Div 5.5 */ + {8727, 6, 0}, + {8018, 6, 1}, + /* Div 6 */ + {8000, 7, 0}, + {7350, 7, 1}, +}; + +static struct omap_mcbsp_reg_cfg initial_config = { + .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), + + /* platform specific initialization */ +#if CONFIG_MACH_OMAP_H2 + .pcr0 = CLKXM | CLKRM | FSXP | FSRP | CLKXP | CLKRP, +#elif CONFIG_MACH_OMAP_H3 + +#ifndef TSC_MASTER + .pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP, +#else + .pcr0 = CLKRM | SCLKME | FSXP | FSRP | CLKXP | CLKRP, +#endif /* tsc Master defs */ + +#endif /* platform specific inits */ +}; + +/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/ + +static void omap_tsc2101_initialize(void *dummy); + +static void omap_tsc2101_shutdown(void *dummy); + +static int omap_tsc2101_ioctl(struct inode *inode, struct file *file, + uint cmd, ulong arg); + +static int omap_tsc2101_probe(void); + +static void omap_tsc2101_remove(void); + +static int omap_tsc2101_suspend(void); + +static int omap_tsc2101_resume(void); + +static void tsc2101_configure(void); + +static int mixer_open(struct inode *inode, struct file *file); + +static int mixer_release(struct inode *inode, struct file *file); + +static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +#ifdef TEST_KEYCLICK +void tsc2101_testkeyclick(void); +#endif + +#ifdef TONE_GEN +void toneGen(void); +#endif + +#ifdef TSC_DUMP_REGISTERS +static void tsc2101_dumpRegisters(void); +#endif + +#ifdef PROC_SUPPORT +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data); + +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data); + +static void tsc2101_start(void); +#endif + +/******************** DATA STRUCTURES USING FUNCTION POINTERS **************************/ + +/* File Op structure for mixer */ +static struct file_operations omap_mixer_fops = { + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .owner = THIS_MODULE +}; + +/* To store characteristic info regarding the codec for the audio driver */ +static audio_state_t tsc2101_state = { + .output_stream = &output_stream, + .input_stream = &input_stream, +/* .need_tx_for_rx = 1, //Once the Full Duplex works */ + .need_tx_for_rx = 0, + .hw_init = omap_tsc2101_initialize, + .hw_shutdown = omap_tsc2101_shutdown, + .client_ioctl = omap_tsc2101_ioctl, + .hw_probe = omap_tsc2101_probe, + .hw_remove = omap_tsc2101_remove, + .hw_suspend = omap_tsc2101_suspend, + .hw_resume = omap_tsc2101_resume, + .sem = __MUTEX_INITIALIZER(tsc2101_state.sem), +}; + +/* This will be defined in the Audio.h */ +static struct file_operations *omap_audio_fops; + +/***************************** MODULES SPECIFIC FUNCTIONs *******************************/ + +/********************************************************************************* + * + * Simplified write for tsc Audio + * + *********************************************************************************/ +static __inline__ void audio_tsc2101_write(u8 address, u16 data) +{ + omap_tsc2101_write(PAGE2_AUDIO_CODEC_REGISTERS, address, data); +} + +/********************************************************************************* + * + * Simplified read for tsc Audio + * + *********************************************************************************/ +static __inline__ u16 audio_tsc2101_read(u8 address) +{ + return (omap_tsc2101_read(PAGE2_AUDIO_CODEC_REGISTERS, address)); +} + +/********************************************************************************* + * + * tsc2101_update() + * Volume Adj etc + * + ********************************************************************************/ +static int tsc2101_update(int flag, int val) +{ + u16 volume; + u16 data; + + FN_IN; + switch (flag) { + case SET_VOLUME: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x7F(min) -> y(max) volume range */ + volume = + ((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MAX; + /* invert the value for getting the proper range 0 min and 100 max */ + volume = OUTPUT_VOLUME_MIN - volume; + data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL); + data &= + ~(DGC_DALVL(OUTPUT_VOLUME_MIN) | + DGC_DARVL(OUTPUT_VOLUME_MIN)); + data |= DGC_DALVL(volume) | DGC_DARVL(volume); + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, data); + data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL); + + break; + + case SET_LINE: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + /* Handset Input not muted, AGC for Handset In off */ + audio_tsc2101_write(TSC2101_HEADSET_GAIN_CTRL, + HGC_ADPGA_HED(volume)); + break; + + case SET_MIC: + if (val < 0 || val > 100) { + printk(KERN_ERR "Trying a bad volume value(%d)!\n", val); + return -EPERM; + } + /* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */ + /* NOTE: 0 is minimum volume and not mute */ + volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN; + /* Handset Input not muted, AGC for Handset In off */ + audio_tsc2101_write(TSC2101_HANDSET_GAIN_CTRL, + HNGC_ADPGA_HND(volume)); + break; + + case SET_RECSRC: + /* + * If more than one recording device selected, + * disable the device that is currently in use. + */ + if (hweight32(val) > 1) + val &= ~tsc2101_local.recsrc; + + data = audio_tsc2101_read(TSC2101_MIXER_PGA_CTRL); + data &= ~MPC_MICSEL(7); /* clear all MICSEL bits */ + + if (val == SOUND_MASK_MIC) { + data |= MPC_MICSEL(1); + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data); + } + else if (val == SOUND_MASK_LINE) { + data |= MPC_MICSEL(0); + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data); + } + else { + printk(KERN_WARNING "omap1610-tsc2101: Wrong RECSRC" + " value specified\n"); + return -EINVAL; + } + tsc2101_local.recsrc = val; + break; + default: + printk(KERN_WARNING "omap1610-tsc2101: Wrong tsc2101_update " + "flag specified\n"); + break; + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * mixer_open() + * + ********************************************************************************/ +static int mixer_open(struct inode *inode, struct file *file) +{ + /* Any mixer specific initialization */ + + /* Initalize the tsc2101 */ + omap_tsc2101_enable(); + + return 0; +} + +/********************************************************************************* + * + * mixer_release() + * + ********************************************************************************/ +static int mixer_release(struct inode *inode, struct file *file) +{ + /* Any mixer specific Un-initialization */ + omap_tsc2101_disable(); + + return 0; +} + +/********************************************************************************* + * + * mixer_ioctl() + * + ********************************************************************************/ +static int +mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + int val; + int gain; + int ret = 0; + int nr = _IOC_NR(cmd); + + /* + * We only accept mixer (type 'M') ioctls. + */ + FN_IN; + if (_IOC_TYPE(cmd) != 'M') + return -EINVAL; + + DPRINTK(" 0x%08x\n", cmd); + + if (cmd == SOUND_MIXER_INFO) { + struct mixer_info mi; + + strncpy(mi.id, "TSC2101", sizeof(mi.id)); + strncpy(mi.name, "TI TSC2101", sizeof(mi.name)); + mi.modify_counter = tsc2101_local.mod_cnt; + FN_OUT(1); + return copy_to_user((void __user *)arg, &mi, sizeof(mi)); + } + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = get_user(val, (int __user *)arg); + if (ret) + goto out; + + /* Ignore separate left/right channel for now, + * even the codec does support it. + */ + gain = val & 255; + + switch (nr) { + case SOUND_MIXER_VOLUME: + tsc2101_local.volume = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_VOLUME, gain); + break; + + case SOUND_MIXER_LINE: + tsc2101_local.line = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_LINE, gain); + break; + + case SOUND_MIXER_MIC: + tsc2101_local.mic = val; + tsc2101_local.mod_cnt++; + ret = tsc2101_update(SET_MIC, gain); + break; + + case SOUND_MIXER_RECSRC: + if ((val & SOUND_MASK_LINE) || + (val & SOUND_MASK_MIC)) { + if (tsc2101_local.recsrc != val) { + tsc2101_local.mod_cnt++; + tsc2101_update(SET_RECSRC, val); + } + } + else { + ret = -EINVAL; + } + break; + + default: + ret = -EINVAL; + } + } + + if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { + ret = 0; + + switch (nr) { + case SOUND_MIXER_VOLUME: + val = tsc2101_local.volume; + val = (tsc2101_local.volume << 8) | + tsc2101_local.volume; + break; + case SOUND_MIXER_LINE: + val = (tsc2101_local.line << 8) | + tsc2101_local.line; + break; + case SOUND_MIXER_MIC: + val = (tsc2101_local.mic << 8) | + tsc2101_local.mic; + break; + case SOUND_MIXER_RECSRC: + val = tsc2101_local.recsrc; + break; + case SOUND_MIXER_RECMASK: + val = REC_MASK; + break; + case SOUND_MIXER_DEVMASK: + val = DEV_MASK; + break; + case SOUND_MIXER_CAPS: + val = 0; + break; + case SOUND_MIXER_STEREODEVS: + val = SOUND_MASK_VOLUME; + break; + default: + val = 0; + printk(KERN_WARNING "omap1610-tsc2101: unknown mixer " + "read ioctl flag specified\n"); + ret = -EINVAL; + break; + } + + if (ret == 0) + ret = put_user(val, (int __user *)arg); + } + out: + FN_OUT(0); + return ret; + +} + +/********************************************************************************* + * + * omap_set_samplerate() + * + ********************************************************************************/ +static int omap_set_samplerate(long sample_rate) +{ + u8 count = 0; + u16 data = 0; + int clkgdv = 0; + /* wait for any frame to complete */ + udelay(125); + + /* Search for the right sample rate */ + while ((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 = audio_tsc2101_read(TSC2101_AUDIO_CTRL_1); + /*Clear prev settings */ + data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07)); + data |= + AC1_DACFS(reg_info[count].divisor) | AC1_ADCFS(reg_info[count]. + divisor); + audio_tsc2101_write(TSC2101_AUDIO_CTRL_1, data); + + /* Set the AC3 */ + data = audio_tsc2101_read(TSC2101_AUDIO_CTRL_3); + /*Clear prev settings */ + data &= ~(AC3_REFFS | AC3_SLVMS); + data |= (reg_info[count].fs_44kHz) ? AC3_REFFS : 0; +#ifdef TSC_MASTER + data |= AC3_SLVMS; +#endif /* #ifdef TSC_MASTER */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_3, data); + + /* program the PLLs */ + if (reg_info[count].fs_44kHz) { + /* 44.1 khz - 12 MHz Mclk */ + audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(7)); /* PVAL 1; I_VAL 7 */ + audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490)); /* D_VAL 5264 */ + } else { + /* 48 khz - 12 Mhz Mclk */ + audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(8)); /* PVAL 1; I_VAL 8 */ + audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780)); /* D_VAL 1920 */ + } + + audio_samplerate = sample_rate; + + /* Set the sample rate */ +#ifndef TSC_MASTER + clkgdv = + DEFAULT_MCBSP_CLOCK / (sample_rate * + (DEFAULT_BITPERSAMPLE * 2 - 1)); + if (clkgdv) + initial_config.srgr1 = + (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + else + return (1); + + /* Stereo Mode */ + initial_config.srgr2 = + (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)); +#else + initial_config.srgr1 = + (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv)); + initial_config.srgr2 = + ((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1))); + +#endif /* end of #ifdef TSC_MASTER */ + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + + return 0; +} + +/********************************************************************************* + * + * omap_tsc2101_initialize() [hw_init() ] + * + ********************************************************************************/ +static void omap_tsc2101_initialize(void *dummy) +{ + + DPRINTK("omap_tsc2101_initialize entry\n"); + + /* initialize with default sample rate */ + audio_samplerate = AUDIO_RATE_DEFAULT; + + omap_mcbsp_request(AUDIO_MCBSP); + + /* if configured, then stop mcbsp */ + omap_mcbsp_stop(AUDIO_MCBSP); + + omap_tsc2101_enable(); + + omap_mcbsp_config(AUDIO_MCBSP, &initial_config); + omap_mcbsp_start(AUDIO_MCBSP); + tsc2101_configure(); + +#ifdef TEST_KEYCLICK + tsc2101_testkeyclick(); +#endif + +#ifdef TONE_GEN + toneGen(); +#endif + + DPRINTK("omap_tsc2101_initialize exit\n"); +} + +/********************************************************************************* + * + * omap_tsc2101_shutdown() [hw_shutdown() ] + * + ********************************************************************************/ +static void omap_tsc2101_shutdown(void *dummy) +{ + /* + Turn off codec after it is done. + Can't do it immediately, since it may still have + buffered data. + + Wait 20ms (arbitrary value) and then turn it off. + */ + + FN_IN; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(2); + + omap_mcbsp_stop(AUDIO_MCBSP); + omap_mcbsp_free(AUDIO_MCBSP); + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + + omap_tsc2101_disable(); + + FN_OUT(0); +} + +/********************************************************************************* + * + * tsc2101_configure + * + ********************************************************************************/ +static void tsc2101_configure(void) +{ + FN_IN; + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000); + + /*Mute Analog Sidetone */ + /*Select MIC_INHED input for headset */ + /*Cell Phone In not connected */ + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, + MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC); + + /* Set record source */ + tsc2101_update(SET_RECSRC, tsc2101_local.recsrc); + + /* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */ + /* 1dB AGC hysteresis */ + /* MICes bias 2V */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0)); + + /* Set codec output volume */ + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000); + + /* DAC left and right routed to SPK2 */ + /* SPK1/2 unmuted */ + audio_tsc2101_write(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 */ + + audio_tsc2101_write(TSC2101_AUDIO_CTRL_6, + AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC | + AC6_VGNDSCPTC); + + /* Headset/Hook switch detect disabled */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000); + + /* Left line input volume control */ + tsc2101_update(SET_LINE, tsc2101_local.line); + + /* mic input volume control */ + tsc2101_update(SET_MIC, tsc2101_local.mic); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + tsc2101_update(SET_VOLUME, tsc2101_local.volume); + + /* clock configuration */ + omap_set_samplerate(audio_samplerate); + +#ifdef TSC_DUMP_REGISTERS + tsc2101_dumpRegisters(); +#endif + + FN_OUT(0); +} + +#ifdef PROC_SUPPORT +static void tsc2101_start(void) +{ + FN_IN; + + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000); + + /*Mute Analog Sidetone */ + /*Select MIC_INHED input for headset */ + /*Cell Phone In not connected */ + audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, + MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC); + + /* Set record source */ + tsc2101_update(SET_RECSRC, tsc2101_local.recsrc); + + /* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */ + /* 1dB AGC hysteresis */ + /* MICes bias 2V */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0)); + + /* Set codec output volume */ + audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000); + + /* DAC left and right routed to SPK2 */ + /* SPK1/2 unmuted */ + audio_tsc2101_write(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 */ + + audio_tsc2101_write(TSC2101_AUDIO_CTRL_6, + AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC | + AC6_VGNDSCPTC); + + /* Headset/Hook switch detect disabled */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000); + + /* Left line input volume control */ + tsc2101_update(SET_LINE, tsc2101_local.line); + + /* mic input volume control */ + tsc2101_update(SET_MIC, tsc2101_local.mic); + + /* Left/Right headphone channel volume control */ + /* Zero-cross detect on */ + tsc2101_update(SET_VOLUME, tsc2101_local.volume); + + FN_OUT(0); + +} +#endif + +/****************************************************************************************** + * + * All generic ioctl's are handled by audio_ioctl() [File: omap-audio.c]. This + * routine handles some platform specific ioctl's + * + ******************************************************************************************/ +static int +omap_tsc2101_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + long val; + int ret = 0; + + DPRINTK(" 0x%08x\n", cmd); + + /* + * These are platform dependent ioctls which are not handled by the + * generic omap-audio module. + */ + switch (cmd) { + case SNDCTL_DSP_STEREO: + ret = get_user(val, (int __user *)arg); + if (ret) + return ret; + /* the AIC23 is stereo only */ + ret = (val == 0) ? -EINVAL : 1; + FN_OUT(1); + return put_user(ret, (int __user *)arg); + + case SNDCTL_DSP_CHANNELS: + case SOUND_PCM_READ_CHANNELS: + /* the AIC23 is stereo only */ + FN_OUT(2); + return put_user(2, (long __user *)arg); + + case SNDCTL_DSP_SPEED: + ret = get_user(val, (long __user *)arg); + if (ret) + break; + ret = omap_set_samplerate(val); + if (ret) + break; + /* fall through */ + + case SOUND_PCM_READ_RATE: + FN_OUT(3); + return put_user(audio_samplerate, (long __user *)arg); + + case SOUND_PCM_READ_BITS: + case SNDCTL_DSP_SETFMT: + case SNDCTL_DSP_GETFMTS: + /* we can do 16-bit only */ + FN_OUT(4); + return put_user(AFMT_S16_LE, (long __user *)arg); + + default: + /* Maybe this is meant for the mixer (As per OSS Docs) */ + FN_OUT(5); + return mixer_ioctl(inode, file, cmd, arg); + } + + FN_OUT(0); + return ret; +} + +/********************************************************************************* + * + * module_probe for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_probe(void) +{ + FN_IN; + + /* Get the fops from audio oss driver */ + if (!(omap_audio_fops = audio_get_fops())) { + printk(KERN_ERR "Unable to Get the FOPs of Audio OSS driver\n"); + audio_unregister_codec(&tsc2101_state); + return -EPERM; + } + + /* register devices */ + audio_dev_id = register_sound_dsp(omap_audio_fops, -1); + mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1); + +#ifdef PROC_SUPPORT + create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_start, NULL /* client data */ ); + + create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ , + NULL /* parent dir */ , + codec_stop, NULL /* client data */ ); +#endif + + /* Announcement Time */ + printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME + " Audio support initialized\n"); + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * Module Remove for TSC2101 + * + ********************************************************************************/ +static void omap_tsc2101_remove(void) +{ + FN_IN; + /* Un-Register the codec with the audio driver */ + unregister_sound_dsp(audio_dev_id); + unregister_sound_mixer(mixer_dev_id); + +#ifdef PROC_SUPPORT + remove_proc_entry(PROC_START_FILE, NULL); + remove_proc_entry(PROC_STOP_FILE, NULL); +#endif + FN_OUT(0); + +} + +/********************************************************************************* + * + * Module Suspend for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_suspend(void) +{ + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * Module Resume for TSC2101 + * + ********************************************************************************/ +static int omap_tsc2101_resume(void) +{ + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * module_init for TSC2101 + * + ********************************************************************************/ +static int __init audio_tsc2101_init(void) +{ + + int err = 0; + FN_IN; + + if (machine_is_omap_osk() || machine_is_omap_innovator()) + return -ENODEV; + + /* register the codec with the audio driver */ + if ((err = audio_register_codec(&tsc2101_state))) { + printk(KERN_ERR + "Failed to register TSC driver with Audio OSS Driver\n"); + } + FN_OUT(err); + return err; +} + +/********************************************************************************* + * + * module_exit for TSC2101 + * + ********************************************************************************/ +static void __exit audio_tsc2101_exit(void) +{ + + FN_IN; + (void)audio_unregister_codec(&tsc2101_state); + FN_OUT(0); + return; +} + +/**************************** DEBUG FUNCTIONS ***********************************/ + +/********************************************************************************* + * TEST_KEYCLICK: + * This is a test to generate various keyclick sound on tsc. + * verifies if the tsc and the spi interfaces are operational. + * + ********************************************************************************/ +#ifdef TEST_KEYCLICK +void tsc2101_testkeyclick(void) +{ + u8 freq = 0; + u16 old_reg_val, reg_val; + u32 uDummyVal = 0; + u32 uTryVal = 0; + + old_reg_val = audio_tsc2101_read(TSC2101_AUDIO_CTRL_2); + + /* Keyclick active, max amplitude and longest key click len(32 period) */ + printk(KERN_INFO " TESTING KEYCLICK\n Listen carefully NOW....\n"); + printk(KERN_INFO " OLD REG VAL=0x%x\n", old_reg_val); + /* try all frequencies */ + for (; freq < 8; freq++) { + /* Keyclick active, max amplitude and longest key click len(32 period) */ + reg_val = old_reg_val | AC2_KCLAC(0x7) | AC2_KCLLN(0xF); + uDummyVal = 0; + uTryVal = 0; + printk(KERN_INFO "\n\nTrying frequency %d reg val= 0x%x\n", + freq, reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN); + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, + reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN); + printk("DONE. Wait 10 ms ...\n"); + /* wait till the kclk bit is auto cleared! time out also to be considered. */ + while (audio_tsc2101_read(TSC2101_AUDIO_CTRL_2) & AC2_KCLEN) { + udelay(3); + uTryVal++; + if (uTryVal > 2000) { + printk(KERN_ERR + "KEYCLICK TIMED OUT! freq val=%d, POSSIBLE ERROR!\n", + freq); + printk(KERN_INFO + "uTryVal == %d: Read back new reg val= 0x%x\n", + uTryVal, + audio_tsc2101_read + (TSC2101_AUDIO_CTRL_2)); + /* clear */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, 0x00); + break; + } + } + } + /* put the old value back */ + audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, old_reg_val); + printk(KERN_INFO " KEYCLICK TEST COMPLETE\n"); + +} /* End of tsc2101_testkeyclick */ + +#endif /* TEST_KEYCLICK */ + +/********************************************************************************* + * TONEGEN: + * This is a test to generate a rather unpleasant sound.. + * verifies if the mcbsp is active (requires MCBSP_DIRECT_RW to be active on McBSP) + * + ********************************************************************************/ +#ifdef TONE_GEN +/* Generates a shrill tone */ +u16 tone[] = { + 0x0ce4, 0x0ce4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000, + 0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000, + 0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE, + 0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C, + 0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2, + 0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C, + 0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A, + 0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676, + 0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83, + 0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E, + 0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94, + 0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03, + 0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000 +}; + +void toneGen(void) +{ + int count = 0; + int ret = 0; + printk(KERN_INFO "TONE GEN TEST :"); + + for (count = 0; count < 5000; count++) { + int bytes; + for (bytes = 0; bytes < sizeof(tone) / 2; bytes++) { + ret = omap_mcbsp_pollwrite(AUDIO_MCBSP, tone[bytes]); + if (ret == -1) { + /* retry */ + bytes--; + } else if (ret == -2) { + printk(KERN_INFO "ERROR:bytes=%d\n", bytes); + return; + } + } + } + printk(KERN_INFO "SUCCESS\n"); +} + +#endif /* End of TONE_GEN */ + +/********************************************************************************* + * + * TSC_DUMP_REGISTERS: + * This will dump the entire register set of Page 2 tsc2101. + * Useful for major goof ups + * + ********************************************************************************/ +#ifdef TSC_DUMP_REGISTERS +static void tsc2101_dumpRegisters(void) +{ + int i = 0; + u16 data = 0; + printk("TSC 2101 Register dump for Page 2 \n"); + for (i = 0; i < 0x27; i++) { + data = audio_tsc2101_read(i); + printk(KERN_INFO "Register[%x]=0x%04x\n", i, data); + + } +} +#endif /* End of #ifdef TSC_DUMP_REGISTERS */ + +#ifdef PROC_SUPPORT +static int codec_start(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + omap_tsc2101_enable(); + tsc2101_start(); + printk("Codec initialization done.\n"); + return 0; +} +static int codec_stop(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + + omap_tsc2101_disable(); + audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, + ~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC)); + printk("Codec shutdown.\n"); + return 0; +} +#endif + +/********************************************************************************* + * + * Other misc management, registration etc + * + ********************************************************************************/ +module_init(audio_tsc2101_init); +module_exit(audio_tsc2101_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION + ("Glue audio driver for the TI OMAP1610/OMAP1710 TSC2101 codec."); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/omap-audio.c b/sound/oss/omap-audio.c new file mode 100644 index 00000000000..bd10ae6a3d4 --- /dev/null +++ b/sound/oss/omap-audio.c @@ -0,0 +1,1167 @@ +/* + * linux/sound/oss/omap-audio.c + * + * Common audio handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004-11-01 Nishanth Menon - modified to support 16xx and 17xx + * platform multi channel chaining. + * + * 2004-11-04 Nishanth Menon - Added support for power management + * + * 2004-12-17 Nishanth Menon - Provided proper module handling support + */ + +/***************************** INCLUDES ************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-audio-dma-intfc.h" +#include "omap-audio.h" + +/***************************** MACROS ************************************/ + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define DPRINTK printk +#define FN_IN printk("[omap_audio.c:[%s] start\n", __FUNCTION__) +#define FN_OUT(n) printk("[omap_audio.c:[%s] end(%d)\n", __FUNCTION__ , n) +#else +#define DPRINTK( x... ) +#define FN_IN +#define FN_OUT(x) +#endif + +#define OMAP_AUDIO_NAME "omap-audio" +#define AUDIO_NBFRAGS_DEFAULT 8 +#define AUDIO_FRAGSIZE_DEFAULT 8192 + +/* HACK ALERT!: These values will bave to be tuned as this is a trade off b/w + * Sampling Rate vs buffer size and delay we are prepared to do before giving up + */ +#define MAX_QUEUE_FULL_RETRIES 1000000 +#define QUEUE_WAIT_TIME 10 + +#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref) + +#define SPIN_ADDR (dma_addr_t)0 +#define SPIN_SIZE 2048 + +/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/ + +static int audio_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos); + +static int audio_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos); + +static int audio_mmap(struct file *file, struct vm_area_struct *vma); + +static unsigned int audio_poll(struct file *file, + struct poll_table_struct *wait); + +static loff_t audio_llseek(struct file *file, loff_t offset, int origin); + +static int audio_ioctl(struct inode *inode, struct file *file, uint cmd, + ulong arg); + +static int audio_open(struct inode *inode, struct file *file); + +static int audio_release(struct inode *inode, struct file *file); + +static int audio_probe(struct device *dev); + +static int audio_remove(struct device *dev); + +static void audio_shutdown(struct device *dev); + +static int audio_suspend(struct device *dev, pm_message_t mesg, u32 level); + +static int audio_resume(struct device *dev, u32 level); + +static void audio_free(struct device *dev); + +/***************************** Data Structures **********************************/ + +/* + * The function pointer set to be registered by the codec. + */ +static audio_state_t audio_state = { NULL }; + +/* DMA Call back function */ +static dma_callback_t audio_dma_callback = NULL; + +/* File Ops structure */ +static struct file_operations omap_audio_fops = { + .open = audio_open, + .release = audio_release, + .write = audio_write, + .read = audio_read, + .mmap = audio_mmap, + .poll = audio_poll, + .ioctl = audio_ioctl, + .llseek = audio_llseek, + .owner = THIS_MODULE +}; + +/* Driver information */ +static struct device_driver omap_audio_driver = { + .name = OMAP_AUDIO_NAME, + .bus = &platform_bus_type, + .probe = audio_probe, + .remove = audio_remove, + .suspend = audio_suspend, + .resume = audio_resume, + .shutdown = audio_shutdown, +}; + +/* Device Information */ +static struct platform_device omap_audio_device = { + .name = OMAP_AUDIO_NAME, + .dev = { + .driver_data = &audio_state, + .release = audio_free, + }, + .id = 0, +}; + +/***************************** GLOBAL FUNCTIONs **********************************/ + +/* Power Management Functions for Linux Device Model */ +/* DEBUG PUPOSES ONLY! */ +#ifdef CONFIG_PM +//#undef CONFIG_PM +#endif + +#ifdef CONFIG_PM +/********************************************************************************* + * + * audio_ldm_suspend(): Suspend operation + * + *********************************************************************************/ +static int audio_ldm_suspend(void *data) +{ + audio_state_t *state = data; + + FN_IN; + + /* + * Reject the suspend request if we are already actively transmitting data + * Rationale: We dont want to be suspended while in the middle of a call! + */ + if (AUDIO_ACTIVE(state) && state->hw_init) { + printk(KERN_ERR "Audio device Active, Cannot Suspend"); + return -EPERM; +#if 0 + /* NOTE: + * This Piece of code is commented out in hope + * That one day we would need to suspend the device while + * audio operations are in progress and resume the operations + * once the resume is done. + * This is just a sample implementation of how it could be done. + * Currently NOT SUPPORTED + */ + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + int stopstate; + if (is && is->buffers) { + printk("IS Suspend\n"); + stopstate = is->stopped; + audio_stop_dma(is); + DMA_CLEAR(is); + is->dma_spinref = 0; + is->stopped = stopstate; + } + if (os && os->buffers) { + printk("OS Suspend\n"); + stopstate = os->stopped; + audio_stop_dma(os); + DMA_CLEAR(os); + os->dma_spinref = 0; + os->stopped = stopstate; + } +#endif + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_ldm_resume(): Resume Operations + * + *********************************************************************************/ +static int audio_ldm_resume(void *data) +{ + audio_state_t *state = data; + + FN_IN; + if (AUDIO_ACTIVE(state) && state->hw_init) { + /* Should never occur - since we never suspend with active state */ + BUG(); + return -EPERM; +#if 0 + /* NOTE: + * This Piece of code is commented out in hope + * That one day we would need to suspend the device while + * audio operations are in progress and resume the operations + * once the resume is done. + * This is just a sample implementation of how it could be done. + * Currently NOT SUPPORTED + */ + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + if (os && os->buffers) { + printk("OS Resume\n"); + audio_reset(os); + audio_process_dma(os); + } + if (is && is->buffers) { + printk("IS Resume\n"); + audio_reset(is); + audio_process_dma(is); + } +#endif + } + FN_OUT(0); + return 0; +} +#endif /* End of #ifdef CONFIG_PM */ + +/********************************************************************************* + * + * audio_free(): The Audio driver release function + * This is a dummy function required by the platform driver + * + *********************************************************************************/ +static void audio_free(struct device *dev) +{ + /* Nothing to Release! */ +} + +/********************************************************************************* + * + * audio_probe(): The Audio driver probe function + * WARNING!!!! : It is expected that the codec would have registered with us by now + * + *********************************************************************************/ +static int audio_probe(struct device *dev) +{ + int ret; + FN_IN; + if (!audio_state.hw_probe) { + printk(KERN_ERR "Probe Function Not Registered\n"); + return -ENODEV; + } + ret = audio_state.hw_probe(); + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_remove() Function to handle removal operations + * + *********************************************************************************/ +static int audio_remove(struct device *dev) +{ + FN_IN; + if (audio_state.hw_remove) { + audio_state.hw_remove(); + } + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_shutdown(): Function to handle shutdown operations + * + *********************************************************************************/ +static void audio_shutdown(struct device *dev) +{ + FN_IN; + if (audio_state.hw_cleanup) { + audio_state.hw_cleanup(); + } + FN_OUT(0); + return; +} + +/********************************************************************************* + * + * audio_suspend(): Function to handle suspend operations + * + *********************************************************************************/ +static int audio_suspend(struct device *dev, pm_message_t mesg, u32 level) +{ + int ret = 0; + +#ifdef CONFIG_PM + void *data = dev->driver_data; + FN_IN; + if (level != SUSPEND_POWER_DOWN) { + return 0; + } + if (audio_state.hw_suspend) { + ret = audio_ldm_suspend(data); + if (ret == 0) + ret = audio_state.hw_suspend(); + } + if (ret) { + printk(KERN_INFO "Audio Suspend Failed \n"); + } else { + printk(KERN_INFO "Audio Suspend Success \n"); + } +#endif /* CONFIG_PM */ + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_resume(): Function to handle resume operations + * + *********************************************************************************/ +static int audio_resume(struct device *dev, u32 level) +{ + int ret = 0; + +#ifdef CONFIG_PM + void *data = dev->driver_data; + FN_IN; + if (level != RESUME_POWER_ON) { + return 0; + } + if (audio_state.hw_resume) { + ret = audio_ldm_resume(data); + if (ret == 0) + ret = audio_state.hw_resume(); + } + if (ret) { + printk(KERN_INFO " Audio Resume Failed \n"); + } else { + printk(KERN_INFO " Audio Resume Success \n"); + } +#endif /* CONFIG_PM */ + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_get_fops(): Return the fops required to get the function pointers of + * OMAP Audio Driver + * + *********************************************************************************/ +struct file_operations *audio_get_fops(void) +{ + FN_IN; + FN_OUT(0); + return &omap_audio_fops; +} + +/********************************************************************************* + * + * audio_register_codec(): Register a Codec fn points using this function + * WARNING!!!!! : Codecs should ensure that they do so! no sanity checks + * during runtime is done due to obvious performance + * penalties. + * + *********************************************************************************/ +int audio_register_codec(audio_state_t * codec_state) +{ + int ret; + FN_IN; + + /* We dont handle multiple codecs now */ + if (audio_state.hw_init) { + printk(KERN_ERR " Codec Already registered\n"); + return -EPERM; + } + + /* Grab the dma Callback */ + audio_dma_callback = audio_get_dma_callback(); + if (!audio_dma_callback) { + printk(KERN_ERR "Unable to get call back function\n"); + return -EPERM; + } + + /* Sanity checks */ + if (!codec_state) { + printk(KERN_ERR "NULL ARGUMENT!\n"); + return -EPERM; + } + + if (!codec_state->hw_probe || !codec_state->hw_init + || !codec_state->hw_shutdown || !codec_state->client_ioctl) { + printk(KERN_ERR + "Required Fn Entry point Missing probe=%p init=%p,down=%p,ioctl=%p!\n", + codec_state->hw_probe, codec_state->hw_init, + codec_state->hw_shutdown, codec_state->client_ioctl); + return -EPERM; + } + + memcpy(&audio_state, codec_state, sizeof(audio_state_t)); + + ret = platform_device_register(&omap_audio_device); + if (ret != 0) { + printk(KERN_ERR "Platform dev_register failed =%d\n", ret); + ret = -ENODEV; + goto register_out; + } + + ret = driver_register(&omap_audio_driver); + if (ret != 0) { + printk(KERN_ERR "Device Register failed =%d\n", ret); + ret = -ENODEV; + platform_device_unregister(&omap_audio_device); + goto register_out; + } + + register_out: + + FN_OUT(ret); + return ret; +} + +/********************************************************************************* + * + * audio_unregister_codec(): Un-Register a Codec using this function + * + *********************************************************************************/ +int audio_unregister_codec(audio_state_t * codec_state) +{ + FN_IN; + + /* We dont handle multiple codecs now */ + if (!audio_state.hw_init) { + printk(KERN_ERR " No Codec registered\n"); + return -EPERM; + } + /* Security check */ + if (audio_state.hw_init != codec_state->hw_init) { + printk(KERN_ERR + " Attempt to unregister codec which was not registered with us\n"); + return -EPERM; + } + + driver_unregister(&omap_audio_driver); + platform_device_unregister(&omap_audio_device); + + memset(&audio_state, 0, sizeof(audio_state_t)); + + FN_OUT(0); + return 0; +} + +/***************************** MODULES SPECIFIC FUNCTION *************************/ + +/********************************************************************************* + * + * audio_write(): Exposed to write() call + * + *********************************************************************************/ +static int +audio_write(struct file *file, const char __user *buffer, + size_t count, loff_t * ppos) +{ + const char __user *buffer0 = buffer; + audio_state_t *state = file->private_data; + audio_stream_t *s = state->output_stream; + int chunksize, ret = 0; + + DPRINTK("audio_write: count=%d\n", count); + if (*ppos != file->f_pos) { + printk("FPOS not ppos ppos=0x%x fpos =0x%x\n", (u32) * ppos, + (u32) file->f_pos); + return -ESPIPE; + } + if (s->mapped) { + printk("s already mapped\n"); + return -ENXIO; + } + if (!s->buffers && audio_setup_buf(s)) { + printk("NO MEMORY\n"); + return -ENOMEM; + } + + while (count > 0) { + audio_buf_t *b = &s->buffers[s->usr_head]; + + /* Wait for a buffer to become free */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + if (down_trylock(&s->sem)) + break; + } else { + ret = -ERESTARTSYS; + if (down_interruptible(&s->sem)) + break; + } + + /* Feed the current buffer */ + chunksize = s->fragsize - b->offset; + if (chunksize > count) + chunksize = count; + DPRINTK("write %d to %d\n", chunksize, s->usr_head); + if (copy_from_user(b->data + b->offset, buffer, chunksize)) { + printk(KERN_ERR "Audio: CopyFrom User failed \n"); + up(&s->sem); + return -EFAULT; + } + + buffer += chunksize; + count -= chunksize; + b->offset += chunksize; + + if (b->offset < s->fragsize) { + up(&s->sem); + break; + } + + /* Update pointers and send current fragment to DMA */ + b->offset = 0; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + /* Add the num of frags pending */ + s->pending_frags++; + s->active = 1; + + audio_process_dma(s); + + } + + if ((buffer - buffer0)) + ret = buffer - buffer0; + DPRINTK("audio_write: return=%d\n", ret); + return ret; +} + +/********************************************************************************* + * + * audio_read(): Exposed as read() function + * + *********************************************************************************/ +static int +audio_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos) +{ + char __user *buffer0 = buffer; + audio_state_t *state = file->private_data; + audio_stream_t *s = state->input_stream; + int chunksize, ret = 0; + unsigned long flags; + + DPRINTK("audio_read: count=%d\n", count); + + if (*ppos != file->f_pos) { + printk("AudioRead - FPOS not ppos ppos=0x%x fpos =0x%x\n", + (u32) * ppos, (u32) file->f_pos); + return -ESPIPE; + } + if (s->mapped) { + printk("AudioRead - s already mapped\n"); + return -ENXIO; + } + + if (!s->active) { + if (!s->buffers && audio_setup_buf(s)) { + printk("AudioRead - No Memory\n"); + return -ENOMEM; + } + audio_prime_rx(state); + } + + while (count > 0) { + audio_buf_t *b = &s->buffers[s->usr_head]; + + /* Wait for a buffer to become full */ + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + if (down_trylock(&s->sem)) + break; + } else { + ret = -ERESTARTSYS; + if (down_interruptible(&s->sem)) + break; + } + + /* Grab data from the current buffer */ + chunksize = s->fragsize - b->offset; + if (chunksize > count) + chunksize = count; + DPRINTK("read %d from %d\n", chunksize, s->usr_head); + if (copy_to_user(buffer, b->data + b->offset, chunksize)) { + up(&s->sem); + return -EFAULT; + } + buffer += chunksize; + count -= chunksize; + b->offset += chunksize; + if (b->offset < s->fragsize) { + up(&s->sem); + break; + } + + /* Update pointers and return current fragment to DMA */ + local_irq_save(flags); + b->offset = 0; + if (++s->usr_head >= s->nbfrags) + s->usr_head = 0; + + s->pending_frags++; + local_irq_restore(flags); + audio_process_dma(s); + + } + + if ((buffer - buffer0)) + ret = buffer - buffer0; + DPRINTK("audio_read: return=%d\n", ret); + return ret; +} + +/********************************************************************************* + * + * audio_mmap(): Exposed as mmap Function + * !!WARNING: Still under development + * + *********************************************************************************/ +static int audio_mmap(struct file *file, struct vm_area_struct *vma) +{ + audio_state_t *state = file->private_data; + audio_stream_t *s; + unsigned long size, vma_addr; + int i, ret; + + FN_IN; + if (vma->vm_pgoff != 0) + return -EINVAL; + + if (vma->vm_flags & VM_WRITE) { + if (!state->wr_ref) + return -EINVAL;; + s = state->output_stream; + } else if (vma->vm_flags & VM_READ) { + if (!state->rd_ref) + return -EINVAL; + s = state->input_stream; + } else + return -EINVAL; + + if (s->mapped) + return -EINVAL; + size = vma->vm_end - vma->vm_start; + if (size != s->fragsize * s->nbfrags) + return -EINVAL; + if (!s->buffers && audio_setup_buf(s)) + return -ENOMEM; + vma_addr = vma->vm_start; + for (i = 0; i < s->nbfrags; i++) { + audio_buf_t *buf = &s->buffers[i]; + if (!buf->master) + continue; + ret = + remap_pfn_range(vma, vma_addr, buf->dma_addr >> PAGE_SHIFT, + buf->master, vma->vm_page_prot); + if (ret) + return ret; + vma_addr += buf->master; + } + s->mapped = 1; + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_poll(): Exposed as poll function + * + *********************************************************************************/ +static unsigned int +audio_poll(struct file *file, struct poll_table_struct *wait) +{ + audio_state_t *state = file->private_data; + audio_stream_t *is = state->input_stream; + audio_stream_t *os = state->output_stream; + unsigned int mask = 0; + + DPRINTK("audio_poll(): mode=%s%s\n", + (file->f_mode & FMODE_READ) ? "r" : "", + (file->f_mode & FMODE_WRITE) ? "w" : ""); + + if (file->f_mode & FMODE_READ) { + /* Start audio input if not already active */ + if (!is->active) { + if (!is->buffers && audio_setup_buf(is)) + return -ENOMEM; + audio_prime_rx(state); + } + poll_wait(file, &is->wq, wait); + } + + if (file->f_mode & FMODE_WRITE) { + if (!os->buffers && audio_setup_buf(os)) + return -ENOMEM; + poll_wait(file, &os->wq, wait); + } + + if (file->f_mode & FMODE_READ) + if ((is->mapped && is->bytecount > 0) || + (!is->mapped && atomic_read(&is->sem.count) > 0)) + mask |= POLLIN | POLLRDNORM; + + if (file->f_mode & FMODE_WRITE) + if ((os->mapped && os->bytecount > 0) || + (!os->mapped && atomic_read(&os->sem.count) > 0)) + mask |= POLLOUT | POLLWRNORM; + + DPRINTK("audio_poll() returned mask of %s%s\n", + (mask & POLLIN) ? "r" : "", (mask & POLLOUT) ? "w" : ""); + + FN_OUT(mask); + return mask; +} + +/********************************************************************************* + * + * audio_llseek(): Exposed as lseek() function. + * + *********************************************************************************/ +static loff_t audio_llseek(struct file *file, loff_t offset, int origin) +{ + FN_IN; + FN_OUT(0); + return -ESPIPE; +} + +/********************************************************************************* + * + * audio_ioctl(): Handles generic ioctls. If there is a request for something this + * fn cannot handle, its then given to client specific ioctl routine, that will take + * up platform specific requests + * + *********************************************************************************/ +static int +audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) +{ + audio_state_t *state = file->private_data; + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + long val; + + DPRINTK(__FILE__ " audio_ioctl 0x%08x\n", cmd); + + /* dispatch based on command */ + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int __user *)arg); + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) + return put_user(os->fragsize, (int __user *)arg); + else + return put_user(is->fragsize, (int __user *)arg); + + case SNDCTL_DSP_GETCAPS: + val = DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP; + if (is && os) + val |= DSP_CAP_DUPLEX; + FN_OUT(1); + return put_user(val, (int __user *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, (long __user *)arg)) { + FN_OUT(2); + return -EFAULT; + } + if (file->f_mode & FMODE_READ) { + int ret = audio_set_fragments(is, val); + if (ret < 0) { + FN_OUT(3); + return ret; + } + ret = put_user(ret, (int __user *)arg); + if (ret) { + FN_OUT(4); + return ret; + } + } + if (file->f_mode & FMODE_WRITE) { + int ret = audio_set_fragments(os, val); + if (ret < 0) { + FN_OUT(5); + return ret; + } + ret = put_user(ret, (int __user *)arg); + if (ret) { + FN_OUT(6); + return ret; + } + } + FN_OUT(7); + return 0; + + case SNDCTL_DSP_SYNC: + FN_OUT(8); + return audio_sync(file); + + case SNDCTL_DSP_SETDUPLEX: + FN_OUT(9); + return 0; + + case SNDCTL_DSP_POST: + FN_OUT(10); + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && is->active && !is->stopped) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && os->active && !os->stopped) + val |= PCM_ENABLE_OUTPUT; + FN_OUT(11); + return put_user(val, (int __user *)arg); + + case SNDCTL_DSP_SETTRIGGER: + if (get_user(val, (int __user *)arg)) { + FN_OUT(12); + return -EFAULT; + } + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + unsigned long flags; + if (!is->active) { + if (!is->buffers && audio_setup_buf(is)) { + FN_OUT(13); + return -ENOMEM; + } + audio_prime_rx(state); + } + local_irq_save(flags); + is->stopped = 0; + local_irq_restore(flags); + audio_process_dma(is); + + } else { + audio_stop_dma(is); + } + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + unsigned long flags; + if (!os->buffers && audio_setup_buf(os)) { + FN_OUT(14); + return -ENOMEM; + } + local_irq_save(flags); + if (os->mapped && !os->pending_frags) { + os->pending_frags = os->nbfrags; + sema_init(&os->sem, 0); + os->active = 1; + } + os->stopped = 0; + local_irq_restore(flags); + audio_process_dma(os); + + } else { + audio_stop_dma(os); + } + } + FN_OUT(15); + return 0; + + case SNDCTL_DSP_GETOPTR: + case SNDCTL_DSP_GETIPTR: + { + count_info inf = { 0, }; + audio_stream_t *s = + (cmd == SNDCTL_DSP_GETOPTR) ? os : is; + int bytecount, offset; + unsigned long flags; + + if ((s == is && !(file->f_mode & FMODE_READ)) || + (s == os && !(file->f_mode & FMODE_WRITE))) { + FN_OUT(16); + return -EINVAL; + } + if (s->active) { + local_irq_save(flags); + offset = audio_get_dma_pos(s); + inf.ptr = s->dma_tail * s->fragsize + offset; + bytecount = s->bytecount + offset; + s->bytecount = -offset; + inf.blocks = s->fragcount; + s->fragcount = 0; + local_irq_restore(flags); + if (bytecount < 0) + bytecount = 0; + inf.bytes = bytecount; + } + FN_OUT(17); + return copy_to_user((void __user *)arg, &inf, sizeof(inf)); + } + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info inf = { 0, }; + audio_stream_t *s = + (cmd == SNDCTL_DSP_GETOSPACE) ? os : is; + + if ((s == is && !(file->f_mode & FMODE_READ)) || + (s == os && !(file->f_mode & FMODE_WRITE))) { + FN_OUT(18); + return -EINVAL; + } + if (!s->buffers && audio_setup_buf(s)) { + FN_OUT(19); + return -ENOMEM; + } + inf.bytes = atomic_read(&s->sem.count) * s->fragsize; + + inf.fragments = inf.bytes / s->fragsize; + inf.fragsize = s->fragsize; + inf.fragstotal = s->nbfrags; + FN_OUT(20); + return copy_to_user((void __user *)arg, &inf, sizeof(inf)); + } + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + FN_OUT(21); + return 0; + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_READ) { + audio_reset(is); + if (state->need_tx_for_rx) { + unsigned long flags; + local_irq_save(flags); + os->spin_idle = 0; + local_irq_restore(flags); + } + } + if (file->f_mode & FMODE_WRITE) { + audio_reset(os); + } + FN_OUT(22); + return 0; + + default: + /* + * Let the client of this module handle the + * non generic ioctls + */ + FN_OUT(23); + return state->client_ioctl(inode, file, cmd, arg); + } + + FN_OUT(0); + return 0; +} + +/********************************************************************************* + * + * audio_open(): Exposed as open() function + * + *********************************************************************************/ +static int audio_open(struct inode *inode, struct file *file) +{ + audio_state_t *state = (&audio_state); + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + int err, need_tx_dma; + static unsigned char tsc2101_init_flag = 0; + + FN_IN; + + /* Lock the module */ + if (!try_module_get(THIS_MODULE)) { + printk(KERN_CRIT "Failed to get module\n"); + return -ESTALE; + } + /* Lock the codec module */ + if (!try_module_get(state->owner)) { + printk(KERN_CRIT "Failed to get codec module\n"); + module_put(THIS_MODULE); + return -ESTALE; + } + + down(&state->sem); + + /* access control */ + err = -ENODEV; + if ((file->f_mode & FMODE_WRITE) && !os) + goto out; + if ((file->f_mode & FMODE_READ) && !is) + goto out; + err = -EBUSY; + if ((file->f_mode & FMODE_WRITE) && state->wr_ref) + goto out; + if ((file->f_mode & FMODE_READ) && state->rd_ref) + goto out; + err = -EINVAL; + if ((file->f_mode & FMODE_READ) && state->need_tx_for_rx && !os) + goto out; + + /* request DMA channels */ + need_tx_dma = ((file->f_mode & FMODE_WRITE) || + ((file->f_mode & FMODE_READ) && state->need_tx_for_rx)); + if (state->wr_ref || (state->rd_ref && state->need_tx_for_rx)) + need_tx_dma = 0; + if (need_tx_dma) { + DMA_REQUEST(err, os, audio_dma_callback); + if (err < 0) + goto out; + } + if (file->f_mode & FMODE_READ) { + DMA_REQUEST(err, is, audio_dma_callback); + if (err < 0) { + if (need_tx_dma) + DMA_FREE(os); + goto out; + } + } + + /* now complete initialisation */ + if (!AUDIO_ACTIVE(state)) { + if (state->hw_init && !tsc2101_init_flag) { + state->hw_init(state->data); + tsc2101_init_flag = 0; + + } + + } + + if ((file->f_mode & FMODE_WRITE)) { + state->wr_ref = 1; + audio_reset(os); + os->fragsize = AUDIO_FRAGSIZE_DEFAULT; + os->nbfrags = AUDIO_NBFRAGS_DEFAULT; + os->mapped = 0; + init_waitqueue_head(&os->wq); + } + + if (file->f_mode & FMODE_READ) { + state->rd_ref = 1; + audio_reset(is); + is->fragsize = AUDIO_FRAGSIZE_DEFAULT; + is->nbfrags = AUDIO_NBFRAGS_DEFAULT; + is->mapped = 0; + init_waitqueue_head(&is->wq); + } + + file->private_data = state; + err = 0; + + out: + up(&state->sem); + if (err) { + module_put(state->owner); + module_put(THIS_MODULE); + } + FN_OUT(err); + return err; +} + +/********************************************************************************* + * + * audio_release(): Exposed as release function() + * + *********************************************************************************/ +static int audio_release(struct inode *inode, struct file *file) +{ + audio_state_t *state = file->private_data; + audio_stream_t *os = state->output_stream; + audio_stream_t *is = state->input_stream; + + FN_IN; + + down(&state->sem); + + if (file->f_mode & FMODE_READ) { + audio_discard_buf(is); + DMA_FREE(is); + is->dma_spinref = 0; + if (state->need_tx_for_rx) { + os->spin_idle = 0; + if (!state->wr_ref) { + DMA_FREE(os); + os->dma_spinref = 0; + } + } + state->rd_ref = 0; + } + + if (file->f_mode & FMODE_WRITE) { + audio_sync(file); + audio_discard_buf(os); + if (!state->need_tx_for_rx || !state->rd_ref) { + DMA_FREE(os); + os->dma_spinref = 0; + } + state->wr_ref = 0; + } + + if (!AUDIO_ACTIVE(state)) { + if (state->hw_shutdown) + state->hw_shutdown(state->data); + } + + up(&state->sem); + + module_put(state->owner); + module_put(THIS_MODULE); + + FN_OUT(0); + return 0; +} + +EXPORT_SYMBOL(audio_register_codec); +EXPORT_SYMBOL(audio_unregister_codec); +EXPORT_SYMBOL(audio_get_fops); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Common audio handling for OMAP processors"); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/omap-audio.h b/sound/oss/omap-audio.h new file mode 100644 index 00000000000..42adef8bcf6 --- /dev/null +++ b/sound/oss/omap-audio.h @@ -0,0 +1,121 @@ +/* + * linux/sound/oss/omap-audio.h + * + * Common audio handling for the OMAP processors + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * Copyright (C) 2000, 2001 Nicolas Pitre + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History + * ------- + * 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms + * + * 2004/04/04 Nishanth menon - Added hooks for power management + */ + +#ifndef __OMAP_AUDIO_H +#define __OMAP_AUDIO_H + +/* Requires dma.h */ +#include + +/* + * Buffer Management + */ +typedef struct { + int offset; /* current offset */ + char *data; /* points to actual buffer */ + dma_addr_t dma_addr; /* physical buffer address */ + int dma_ref; /* DMA refcount */ + int master; /* owner for buffer allocation, contain size when true */ +} audio_buf_t; + +/* + * Structure describing the data stream related information + */ +typedef struct { + char *id; /* identification string */ + audio_buf_t *buffers; /* pointer to audio buffer structures */ + u_int usr_head; /* user fragment index */ + u_int dma_head; /* DMA fragment index to go */ + u_int dma_tail; /* DMA fragment index to complete */ + u_int fragsize; /* fragment i.e. buffer size */ + u_int nbfrags; /* nbr of fragments i.e. buffers */ + u_int pending_frags; /* Fragments sent to DMA */ + int dma_dev; /* device identifier for DMA */ + +#ifdef OMAP_DMA_CHAINING_SUPPORT + lch_chain *dma_chain; + dma_regs_t *dma_regs; /* points to our DMA registers */ +#else + char started; /* to store if the chain was started or not */ + int dma_q_head; /* DMA Channel Q Head */ + int dma_q_tail; /* DMA Channel Q Tail */ + char dma_q_count; /* DMA Channel Q Count */ + char in_use; /* Is this is use? */ + int *lch; /* Chain of channels this stream is linked to */ +#endif + int input_or_output; /* Direction of this data stream */ + int bytecount; /* nbr of processed bytes */ + int fragcount; /* nbr of fragment transitions */ + struct semaphore sem; /* account for fragment usage */ + wait_queue_head_t wq; /* for poll */ + int dma_spinref; /* DMA is spinning */ + unsigned mapped:1; /* mmap()'ed buffers */ + unsigned active:1; /* actually in progress */ + unsigned stopped:1; /* might be active but stopped */ + unsigned spin_idle:1; /* have DMA spin on zeros when idle */ + unsigned linked:1; /* dma channels linked */ +} audio_stream_t; + +/* + * State structure for one instance + */ +typedef struct { + struct module *owner; /* Codec module ID */ + audio_stream_t *output_stream; + audio_stream_t *input_stream; + unsigned rd_ref:1; /* open reference for recording */ + unsigned wr_ref:1; /* open reference for playback */ + unsigned need_tx_for_rx:1; /* if data must be sent while receiving */ + void *data; + void (*hw_init) (void *); + void (*hw_shutdown) (void *); + int (*client_ioctl) (struct inode *, struct file *, uint, ulong); + int (*hw_probe) (void); + void (*hw_remove) (void); + void (*hw_cleanup) (void); + int (*hw_suspend) (void); + int (*hw_resume) (void); + struct pm_dev *pm_dev; + struct semaphore sem; /* to protect against races in attach() */ +} audio_state_t; + +#ifdef AUDIO_PM +void audio_ldm_suspend(void *data); + +void audio_ldm_resume(void *data); + +#endif + +/* Register a Codec using this function */ +extern int audio_register_codec(audio_state_t * codec_state); +/* Un-Register a Codec using this function */ +extern int audio_unregister_codec(audio_state_t * codec_state); +/* Function to provide fops of omap audio driver */ +extern struct file_operations *audio_get_fops(void); +/* Function to initialize the device info for audio driver */ +extern int audio_dev_init(void); +/* Function to un-initialize the device info for audio driver */ +void audio_dev_uninit(void); + +#endif /* End of #ifndef __OMAP_AUDIO_H */