Commit 4ed2ff0a authored by Tony Lindgren's avatar Tony Lindgren

Add OMAP audio driver

Adds OMAP audio driver.
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent 33a2ee4d
/*
*
* 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 */
...@@ -4,6 +4,33 @@ ...@@ -4,6 +4,33 @@
# More hacking for modularisation. # More hacking for modularisation.
# #
# Prompt user for primary drivers. # 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 config SOUND_BT878
tristate "BT878 audio dma" tristate "BT878 audio dma"
depends on SOUND_PRIME!=n && SOUND depends on SOUND_PRIME!=n && SOUND
......
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
obj-$(CONFIG_SOUND_OSS) += sound.o obj-$(CONFIG_SOUND_OSS) += sound.o
obj-$(CONFIG_SOUND_CS4232) += cs4232.o ad1848.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 ! # Please leave it as is, cause the link order is significant !
obj-$(CONFIG_SOUND_SH_DAC_AUDIO) += sh_dac_audio.o obj-$(CONFIG_SOUND_SH_DAC_AUDIO) += sh_dac_audio.o
......
/*
* linux/sound/oss/omap-audio-aic23.c
*
* Glue audio driver for TI TLV320AIC23 codec
*
* Copyright (c) 2000 Nicolas Pitre <nico@cam.org>
* Copyright (C) 2001, Steve Johnson <stevej@ridgerun.com>
* Copyright (C) 2004 Texas Instruments, Inc.
* Copyright (C) 2005 Dirk Behme <dirk.behme@de.bosch.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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/arch/mcbsp.h>
#include <asm/arch/dsp_common.h>
#include <asm/arch/fpga.h>
#include <asm/arch/aic23.h>
#include <asm/hardware/clock.h>
#include "omap-audio.h"
#include "omap-audio-dma-intfc.h"
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#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 <dirk.behme@de.bosch.com>");
MODULE_DESCRIPTION("Glue audio driver for the TI AIC23 codec.");
MODULE_LICENSE("GPL");
/*
* 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 <nico@cam.org>
*
* 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 <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#include <asm/arch/dma.h>
#include "omap-audio-dma-intfc.h"
#include <asm/arch/mcbsp.h>
#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);
/*
* 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 <nico@cam.org>
*
* 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 */
/*
* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/arch/dma.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/arch/mux.h>
#include <asm/arch/io.h>
#include <asm/mach-types.h>
#include "omap-audio.h"
#include "omap-audio-dma-intfc.h"
#include <asm/arch/mcbsp.h>
#if CONFIG_ARCH_OMAP16XX
#include <../drivers/ssi/omap-uwire.h>
#include <asm/arch/dsp_common.h>
#else
#error "Unsupported configuration"
#endif
#include <asm/hardware/tsc2101.h>
#include <../drivers/ssi/omap-tsc2101.h>
/***************************** MACROS ************************************/
#define PROC_SUPPORT
#ifdef PROC_SUPPORT
#include <linux/proc_fs.h>
#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");
/*
* 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 <nico@cam.org>
*
* 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 <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#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");
/*
* 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 <nico@cam.org>
*
* 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 <asm/arch/dma.h>
/*
* 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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment