Commit 179bf109 authored by Kevin Hilman's avatar Kevin Hilman

[PATCH] ARM: DaVinci: add audio support

parent 9d7fa5ee
...@@ -137,4 +137,12 @@ config SENSORS_MAX6875 ...@@ -137,4 +137,12 @@ config SENSORS_MAX6875
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called max6875. will be called max6875.
config SENSORS_TLV320AIC33
tristate "Texas Instruments TLV320AIC33 Codec"
depends on I2C && I2C_DAVINCI
select SENSORS_TLV320AIC23
help
If you say yes here you get support for the I2C control
interface for Texas Instruments TLV320AIC33 audio codec.
endmenu endmenu
...@@ -96,6 +96,18 @@ int aic23_write_value(u8 reg, u16 value) ...@@ -96,6 +96,18 @@ int aic23_write_value(u8 reg, u16 value)
return 0; return 0;
} }
#ifdef CONFIG_SENSORS_TLV320AIC33
int tlv320aic33_write_value(u8 reg, u16 value)
{
static struct i2c_client *client;
u8 val = value & 0xff;
client = new_client;
return i2c_smbus_write_byte_data(client, reg, val);
}
#endif /* CONFIG_SENSORS_TLV320AIC33 */
static int aic23_detect_client(struct i2c_adapter *adapter, int address, static int aic23_detect_client(struct i2c_adapter *adapter, int address,
int kind) int kind)
{ {
...@@ -163,6 +175,7 @@ static struct i2c_driver aic23_driver = { ...@@ -163,6 +175,7 @@ static struct i2c_driver aic23_driver = {
.detach_client = aic23_detach_client, .detach_client = aic23_detach_client,
}; };
#ifdef CONFIG_ARCH_OMAP
/* /*
* Configures the McBSP3 which is used to send clock to the AIC23 codec. * Configures the McBSP3 which is used to send clock to the AIC23 codec.
* The input clock rate from DSP is 12MHz. * The input clock rate from DSP is 12MHz.
...@@ -194,6 +207,7 @@ static int omap_mcbsp3_aic23_clock_init(void) ...@@ -194,6 +207,7 @@ static int omap_mcbsp3_aic23_clock_init(void)
return 0; return 0;
} }
#endif
static void update_volume_left(int volume) static void update_volume_left(int volume)
{ {
...@@ -640,10 +654,12 @@ static int __init aic23_init(void) ...@@ -640,10 +654,12 @@ static int __init aic23_init(void)
selftest = -ENODEV; selftest = -ENODEV;
return selftest; return selftest;
} }
#ifdef CONFIG_ARCH_OMAP
/* FIXME: Do in board-specific file */ /* FIXME: Do in board-specific file */
omap_mcbsp3_aic23_clock_init(); omap_mcbsp3_aic23_clock_init();
if (!aic23_info_l.power_down) if (!aic23_info_l.power_down)
aic23_power_up(); aic23_power_up();
#endif
aic23_info_l.initialized = 1; aic23_info_l.initialized = 1;
printk("TLV320AIC23 I2C version %s (%s)\n", printk("TLV320AIC23 I2C version %s (%s)\n",
TLV320AIC23_VERSION, TLV320AIC23_DATE); TLV320AIC23_VERSION, TLV320AIC23_DATE);
......
/*
* linux/include/asm-arm/arch-omap/aic23.h
*
* Hardware definitions for TI TLV320AIC23 audio codec
*
* Copyright (C) 2002 RidgeRun, Inc.
* Author: Steve Johnson
*
* 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_ARCH_AIC23_H
#define __ASM_ARCH_AIC23_H
// Codec TLV320AIC23
#define LEFT_LINE_VOLUME_ADDR 0x00
#define RIGHT_LINE_VOLUME_ADDR 0x01
#define LEFT_CHANNEL_VOLUME_ADDR 0x02
#define RIGHT_CHANNEL_VOLUME_ADDR 0x03
#define ANALOG_AUDIO_CONTROL_ADDR 0x04
#define DIGITAL_AUDIO_CONTROL_ADDR 0x05
#define POWER_DOWN_CONTROL_ADDR 0x06
#define DIGITAL_AUDIO_FORMAT_ADDR 0x07
#define SAMPLE_RATE_CONTROL_ADDR 0x08
#define DIGITAL_INTERFACE_ACT_ADDR 0x09
#define RESET_CONTROL_ADDR 0x0F
// Left (right) line input volume control register
#define LRS_ENABLED 0x0100
#define LIM_MUTED 0x0080
#define LIV_DEFAULT 0x0017
#define LIV_MAX 0x001f
#define LIV_MIN 0x0000
// Left (right) channel headphone volume control register
#define LZC_ON 0x0080
#define LHV_DEFAULT 0x0079
#define LHV_MAX 0x007f
#define LHV_MIN 0x0000
// Analog audio path control register
#define STA_REG(x) ((x)<<6)
#define STE_ENABLED 0x0020
#define DAC_SELECTED 0x0010
#define BYPASS_ON 0x0008
#define INSEL_MIC 0x0004
#define MICM_MUTED 0x0002
#define MICB_20DB 0x0001
// Digital audio path control register
#define DACM_MUTE 0x0008
#define DEEMP_32K 0x0002
#define DEEMP_44K 0x0004
#define DEEMP_48K 0x0006
#define ADCHP_ON 0x0001
// Power control down register
#define DEVICE_POWER_OFF 0x0080
#define CLK_OFF 0x0040
#define OSC_OFF 0x0020
#define OUT_OFF 0x0010
#define DAC_OFF 0x0008
#define ADC_OFF 0x0004
#define MIC_OFF 0x0002
#define LINE_OFF 0x0001
// Digital audio interface register
#define MS_MASTER 0x0040
#define LRSWAP_ON 0x0020
#define LRP_ON 0x0010
#define IWL_16 0x0000
#define IWL_20 0x0004
#define IWL_24 0x0008
#define IWL_32 0x000C
#define FOR_I2S 0x0002
#define FOR_DSP 0x0003
// Sample rate control register
#define CLKOUT_HALF 0x0080
#define CLKIN_HALF 0x0040
#define BOSR_384fs 0x0002 // BOSR_272fs when in USB mode
#define USB_CLK_ON 0x0001
#define SR_MASK 0xf
#define CLKOUT_SHIFT 7
#define CLKIN_SHIFT 6
#define SR_SHIFT 2
#define BOSR_SHIFT 1
// Digital interface register
#define ACT_ON 0x0001
#define TLV320AIC23ID1 (0x1a) // cs low
#define TLV320AIC23ID2 (0x1b) // cs high
void tlv320aic23_power_up(void);
void tlv320aic23_power_down(void);
#endif /* __ASM_ARCH_AIC23_H */
/*
* include/sound/davincisound.h
*
* Copyright (C) 2006 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:
* --------
* 2006-03-29 Sudhakar - Created
*/
#include <linux/soundcard.h>
#define SOUND_MIXER_MICBIAS _IOC_NR(SOUND_MIXER_PRIVATE1)
#define SOUND_MIXER_READ_MICBIAS _SIOR ('M', SOUND_MIXER_MICBIAS, int)
#define SOUND_MIXER_WRITE_MICBIAS SOUND_MIXER_PRIVATE1
...@@ -872,3 +872,36 @@ config SOUND_SH_DAC_AUDIO_CHANNEL ...@@ -872,3 +872,36 @@ config SOUND_SH_DAC_AUDIO_CHANNEL
int " DAC channel" int " DAC channel"
default "1" default "1"
depends on SOUND_SH_DAC_AUDIO depends on SOUND_SH_DAC_AUDIO
config SOUND_DAVINCI
tristate "DaVinci Sound Driver"
depends on ARCH_DAVINCI && SOUND_PRIME!=n && SOUND
select DAVINCI_MCBSP
---help---
DaVinci Sound driver
config SOUND_DAVINCI_AIC33
tristate "AIC33 Stereo Codec"
depends on SOUND_DAVINCI
select SENSORS_TLV320AIC33
---help---
If you say yes here you get support for the I2C control
interface for Texas Instruments TLV320AIC33 audio codec.
menu "DaVinci Audio Options"
depends on SOUND_DAVINCI
choice
prompt "Mono/Stereo Jack Support"
default MONOSTEREO_SAMEJACK
config MONOSTEREO_DIFFJACK
bool "Mono and Stereo on different jacks"
config MONOSTEREO_SAMEJACK
bool "Mono and Stereo on same jacks"
endchoice
endmenu
...@@ -12,6 +12,9 @@ obj-$(CONFIG_SOUND_OMAP) += omap-audio-dma-intfc.o omap-audio.o ...@@ -12,6 +12,9 @@ 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_TSC2101)+= omap-audio-tsc2101.o
obj-$(CONFIG_SOUND_OMAP_AIC23) += omap-audio-aic23.o obj-$(CONFIG_SOUND_OMAP_AIC23) += omap-audio-aic23.o
obj-$(CONFIG_SOUND_DAVINCI) += davinci-audio-dma-intfc.o davinci-audio.o
obj-$(CONFIG_SOUND_DAVINCI_AIC33) += davinci-audio-aic33.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/davinci-aic33.h
*
* Glue driver for AIC33 for Davinci processors
*
* Copyright (C) 2006 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:
* -------
* 2005-10-18 Rishi Bhattacharya - Support for AIC33 codec and Davinci DM644x Processor
*/
#ifndef __ASM_ARCH_AIC33_H
#define __ASM_ARCH_AIC33_H
/* Codec TLV320AIC33 */
#define REGISTER_ADDR0 0x00
#define REGISTER_ADDR1 0x01
#define REGISTER_ADDR2 0x02
#define REGISTER_ADDR3 0x03
#define REGISTER_ADDR4 0x04
#define REGISTER_ADDR5 0x05
#define REGISTER_ADDR6 0x06
#define REGISTER_ADDR7 0x07
#define REGISTER_ADDR8 0x08
#define REGISTER_ADDR9 0x09
#define REGISTER_ADDR10 0x0A
#define REGISTER_ADDR11 0x0B
#define REGISTER_ADDR12 0x0C
#define REGISTER_ADDR15 0x0F
#define REGISTER_ADDR16 0x10
#define REGISTER_ADDR17 0x11
#define REGISTER_ADDR18 0x12
#define REGISTER_ADDR19 0x13
#define REGISTER_ADDR20 0x14
#define REGISTER_ADDR21 0x15
#define REGISTER_ADDR22 0x16
#define REGISTER_ADDR23 0x17
#define REGISTER_ADDR24 0x18
#define REGISTER_ADDR25 0x19
#define REGISTER_ADDR26 0x1A
#define REGISTER_ADDR27 0x1B
#define REGISTER_ADDR28 0x1C
#define REGISTER_ADDR29 0x1D
#define REGISTER_ADDR30 0x1E
#define REGISTER_ADDR31 0x1F
#define REGISTER_ADDR32 0x20
#define REGISTER_ADDR33 0x21
#define REGISTER_ADDR37 0x25
#define REGISTER_ADDR38 0x26
#define REGISTER_ADDR40 0x28
#define REGISTER_ADDR41 0x29
#define REGISTER_ADDR43 0x2B
#define REGISTER_ADDR44 0x2C
#define REGISTER_ADDR45 0x2D
#define REGISTER_ADDR46 0x2E
#define REGISTER_ADDR47 0x2F
#define REGISTER_ADDR51 0x33
#define REGISTER_ADDR58 0x3A
#define REGISTER_ADDR64 0x40
#define REGISTER_ADDR65 0x41
#define REGISTER_ADDR73 0x49
#define REGISTER_ADDR74 0x4A
#define REGISTER_ADDR75 0x4B
#define REGISTER_ADDR76 0x4C
#define REGISTER_ADDR77 0x4D
#define REGISTER_ADDR78 0x4E
#define REGISTER_ADDR79 0x4F
#define REGISTER_ADDR80 0x50
#define REGISTER_ADDR81 0x51
#define REGISTER_ADDR82 0x52
#define REGISTER_ADDR83 0x53
#define REGISTER_ADDR84 0x54
#define REGISTER_ADDR85 0x55
#define REGISTER_ADDR86 0x56
#define REGISTER_ADDR87 0x57
#define REGISTER_ADDR88 0x58
#define REGISTER_ADDR89 0x59
#define REGISTER_ADDR90 0x5A
#define REGISTER_ADDR91 0x5B
#define REGISTER_ADDR92 0x5C
#define REGISTER_ADDR93 0x5D
#define REGISTER_ADDR94 0x5E
// Page Select register 0
#define PAGE_SELECT0 0
#define PAGE_SELECT1 1
// Software reset register 1
#define SOFT_RESET 0x80
// Codec sample rate select register 2
#define ADC_FS_MAX 0xA0
#define ADC_FS_MIN 0x00
#define DAC_FS_MAX 0x0A
#define DAC_FS_MIN 0x00
// PLL Programming registerA 3
#define PLL_ENABLE 0x80
// Codec Datapath setup register 7
#define FS_REF_44_1 0x80
#define FS_REF_DEFAULT_48 0x00
#define ADC_DUAL_RATE_MODE 0x40
#define DAC_DUAL_RATE_MODE 0x20
#define LDAC_LCHAN 0x08
#define LDAC_RCHAN 0x10
#define LDAC_MONO_MIX 0x18
#define RDAC_RCHAN 0x02
#define RDAC_LCHAN 0x04
#define RDAC_MONO_MIX 0x06
//Audio serial data interface control registerA 8
#define BIT_CLK_MASTER 0x80
#define WORD_CLK_MASTER 0x40
#define DOUT_TRI_STATE 0x20
#define CLK_TRANS_MASTER 0x10
#define ENABLE_3D 0x04
#define DM_ENABLE_128 0x01
#define DM_ENABLE_64 0x02
#define DM_ENABLE_32 0x03
//Audio serial data interface control registerB 9
#define DSP_MODE 0x40
#define RJ_MODE 0x80
#define LJ_MODE 0xC0
#define WORD_LENGTH20 0x10
#define WORD_LENGTH24 0x20
#define WORD_LENGTH32 0x30
#define BITCLOCK_256CLK_FRAME 0x08
//Left/Right ADC PGA gain control register 15 & 16
#define ADC_PGA_MUTE 0x80
#define ADC_PGA_GAIN_MAX 0x78
#define ADC_PGA_GAIN_MIN 0x00
// MIC3L/R to left/right ADC control register 17 & 18
#define ADCPGA_GAIN_MAX 0x00
#define MIC3L_ADCPGA_GAIN_MIN 0x80
#define MIC3L_ADCPGA_DISCONNECT 0xF0
#define MIC3R_ADCPGA_GAIN_MIN 0x08
#define MIC3R_ADCPGA_DISCONNECT 0x0F
//LINE1L to left ADC Control Register 19
#define DIFF_MODE 0x80
#define LINE_ADCPGA_GAIN_MIN 0x40
#define LINE_ADCPGA_DISCONNECT 0x78
#define ADC_CHAN_ON 0x04
#define ADCPGA_SOFT_STEP2FS 0x01
#define ADCPGA_SOFT_STEP_OFF 0x03
//LINE2L to left ADC Control Register 20
#define ADC_WEAK_INPUT_BIAS 0x04
//MICBIAS control register 25
#define MICBIAS_OUTPUT_2_0V 0x40
#define MICBIAS_OUTPUT_2_5V 0x80
#define MICBIAS_OUTPUT_AVDD 0xC0
//LEFT/RIGHT AGC Control registerA 26 & 29
#define AGC_ENABLE 0x80
#define AGC_TARGET_GAIN_MAX 0x00
#define AGC_TARGET_GAIN_MIN 0x70
#define AGC_ATTACK_TIME_11 0x04
#define AGC_ATTACK_TIME_16 0x08
#define AGC_ATTACK_TIME_20 0x0C
#define AGC_DECAY_TIME_200 0x01
#define AGC_DECAY_TIME_400 0x02
#define AGC_DECAY_TIME_500 0x03
//LEFT AGC Control registerB 27 & 30
#define AGC_GAIN_ALLOWED_MAX 0xEE
#define AGC_GAIN_ALLOWED_MIN 0x00
//DAC Power and output driver control register 37
#define LEFT_DAC_POWER_ON 0x80
#define RIGHT_DAC_POWER_ON 0x40
//High Power Output Stage Control Register 40
#define LINE2L_BYPASS_DISABLE_DEFAULT 0x00
#define LINE2LP_BYPASS_SINGLE 0x10
#define LINE2LM_BYPASS_SINGLE 0x20
#define LINE2LPM_BYPASS_DIFFERENTIAL 0x30
#define LINE2R_BYPASS_DISABLE_DEFAULT 0x00
#define LINE2RP_BYPASS_SINGLE 0x04
#define LINE2RM_BYPASS_SINGLE 0x08
#define LINE2RPM_BYPASS_DIFFERENTIAL 0x0C
//DAC Output Switching Control Register 41
#define LEFT_DAC_DEFAULT_L1 0x00
#define LEFT_DAC_L2 0x80
#define LEFT_DAC_L3 0x40
#define RIGHT_DAC_DEFAULT_R1 0x00
#define RIGHT_DAC_R2 0x08
#define RIGHT_DAC_R3 0x04
//LEFT/RIGHT DAC Digital volume control register 43 & 44
#define DAC_CHAN_MUTE 0x80
#define DAC_DIG_VOL_GAIN_MAX 0x00 // 0.0db
#define DAC_DIG_VOL_GAIN_MIN 0x7F // -63.5db
//LINE2L to HPLOUT Volume Control Register 45
#define LINE2L_HPLOUT_ROUTED 0x80
//PGA_L to HPLOUT Volume Control Register 46
#define PGAL_HPLOUT_ROUTED 0x80
//any to LOP/M Volume control
#define LOPM_ON 0x80
#define LOPM_VOL_GAIN_MAX 0x00 //0 db
#define LOPM_VOL_GAIN_MIN 0x76 //-78.3 db is MUTE
//MONO_LOP/M output level volume control register 79
#define LOPM_POWER_ON 0x01
#define LOPM_MUTE_OFF 0x08
#define LOPM_OUTPUT_LEVEL_MIN 0x00
#define LOPM_OUTPUT_LEVEL_MAX 0x90
//Module Power Status Register 94
#define HPROUT_DRIVER_POWER_ON 0x02
#define LIV_MAX 0x0077
#define LIV_MIN 0x0000
#define LHV_MAX 0x0077
#define LHV_MIN 0x0000
#define LIG_MAX 0x0077
#define LIG_MIN 0x0000
#define LOG_MAX 0x007f
#define LOG_MIN 0x0000
#endif /* __ASM_ARCH_AIC33_H */
/*
* linux/sound/oss/davinci-audio-aic33.c
*
* Glue driver for AIC33 for Davinci processors
*
* Copyright (C) 2006 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:
* -------
* 2005-10-18 Rishi Bhattacharya - Support for AIC33 codec and Davinci DM644x Processor
*/
#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 <linux/clk.h>
#include <sound/davincisound.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/arch/mcbsp.h>
#include "davinci-aic33.h"
#include "davinci-audio.h"
#include "davinci-audio-dma-intfc.h"
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#define PROC_START_FILE "driver/aic33-audio-start"
#define PROC_STOP_FILE "driver/aic33-audio-stop"
#endif
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(ARGS...) do { \
printk("<%s>: ",__FUNCTION__);printk(ARGS); \
} while (0)
#else
#define DPRINTK( x... )
#endif
#define CODEC_NAME "AIC33"
#define PLATFORM_NAME "DAVINCI"
/* Define to set the AIC33 as the master w.r.t McBSP */
#define AIC33_MASTER
/* codec clock frequency */
#define MCLK 22
/*
* AUDIO related MACROS
*/
#define DEFAULT_BITPERSAMPLE 16
#define AUDIO_RATE_DEFAULT 48000
#define DEFAULT_MCBSP_CLOCK 81000000
/* Select the McBSP For Audio */
#define AUDIO_MCBSP DAVINCI_MCBSP1
#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC)
#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME)
#define MONO 1
#define STEREO 2
#define SET_VOLUME 1
#define SET_LINE 2
#define SET_MIC 3
#define SET_RECSRC 4
#define SET_IGAIN 5
#define SET_OGAIN 6
#define SET_BASS 7
#define SET_TREBLE 8
#define SET_MICBIAS 9
#define DEFAULT_OUTPUT_VOLUME 50
#define DEFAULT_INPUT_VOLUME 20 /* 0 ==> mute line in */
#define DEFAULT_INPUT_IGAIN 20
#define DEFAULT_INPUT_OGAIN 100
#define OUTPUT_VOLUME_MIN LHV_MIN
#define OUTPUT_VOLUME_MAX LHV_MAX
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN)
#define INPUT_VOLUME_MIN LIV_MIN
#define INPUT_VOLUME_MAX LIV_MAX
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define INPUT_GAIN_MIN LIG_MIN
#define INPUT_GAIN_MAX LIG_MAX
#define INPUT_GAIN_RANGE (INPUT_GAIN_MAX - INPUT_GAIN_MIN)
#define OUTPUT_GAIN_MIN LOG_MIN
#define OUTPUT_GAIN_MAX LOG_MAX
#define OUTPUT_GAIN_RANGE (INPUT_GAIN_MAX - INPUT_GAIN_MIN)
#define NUMBER_SAMPLE_RATES_SUPPORTED 11
static audio_stream_t output_stream = {
.id = "AIC33 out",
.dma_dev = DAVINCI_DMA_MCBSP1_TX,
.input_or_output = FMODE_WRITE
};
static audio_stream_t input_stream = {
.id = "AIC33 in",
.dma_dev = DAVINCI_DMA_MCBSP1_RX,
.input_or_output = FMODE_READ
};
static int audio_dev_id, mixer_dev_id;
static struct aic33_local_info {
u8 volume;
u16 volume_reg;
u8 line;
u8 mic;
int recsrc;
int nochan;
u8 igain;
u8 ogain;
u8 micbias;
u8 bass;
u8 treble;
u16 input_volume_reg;
int mod_cnt;
} aic33_local;
struct sample_rate_reg_info {
u32 sample_rate;
u32 Fsref;
float divider;
u8 data;
};
/* To Store the default sample rate */
static long audio_samplerate = AUDIO_RATE_DEFAULT;
extern struct clk *davinci_mcbsp_get_clock(void);
/* DAC USB-mode sampling rates*/
static const struct sample_rate_reg_info
reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
/* {sample_rate, Fsref, divider, data}*/
{96000, 96000, 1, 0x00},
{88200, 88200, 1, 0x00},
{48000, 48000, 1, 0x00},
{44100, 44100, 1, 0x00},
{32000, 48000, 1.5, 0x11},
{24000, 96000, 4, 0x66},
{22050, 44100, 2, 0x22},
{16000, 48000, 3, 0x44},
{12000, 48000, 4, 0x66},
{11025, 44100, 4, 0x66},
{8000, 48000, 6, 0xAA},
};
static struct davinci_mcbsp_reg_cfg initial_config = {
.spcr2 = FREE | XINTM(3),
.spcr1 = RINTM(3),
.rcr2 = RWDLEN2(DAVINCI_MCBSP_WORD_16) | RDATDLY(1),
.rcr1 = RFRLEN1(1) | RWDLEN1(DAVINCI_MCBSP_WORD_16),
.xcr2 = XWDLEN2(DAVINCI_MCBSP_WORD_16) | XDATDLY(1) | XFIG,
.xcr1 = XFRLEN1(1) | XWDLEN1(DAVINCI_MCBSP_WORD_16),
.srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1),
.srgr2 = FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1),
#ifndef AIC33_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 /* AIC33_MASTER */
};
static void davinci_aic33_initialize(void *dummy);
static void davinci_aic33_shutdown(void *dummy);
static int davinci_aic33_ioctl(struct inode *inode, struct file *file,
uint cmd, ulong arg);
static int davinci_aic33_probe(void);
#ifdef MODULE
static void davinci_aic33_remove(void);
#endif
static int davinci_aic33_suspend(void);
static int davinci_aic33_resume(void);
static inline void aic33_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 davinci_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 aic33_state = {
.owner = THIS_MODULE,
.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 = davinci_aic33_initialize,
.hw_shutdown = davinci_aic33_shutdown,
.client_ioctl = davinci_aic33_ioctl,
.hw_probe = davinci_aic33_probe,
.hw_remove = __exit_p(davinci_aic33_remove),
.hw_suspend = davinci_aic33_suspend,
.hw_resume = davinci_aic33_resume,
.sem = __SEMAPHORE_INIT(aic33_state.sem, 1),
};
/* This will be defined in the audio.h */
static struct file_operations *davinci_audio_fops;
extern int tlv320aic33_write_value(u8 reg, u16 value);
/* TLV320AIC33 write */
static __inline__ void audio_aic33_write(u8 address, u16 data)
{
if (tlv320aic33_write_value(address, data) < 0)
printk(KERN_INFO "aic33 write failed for reg = %d\n", address);
}
static int aic33_update(int flag, int val)
{
u16 volume;
u16 gain;
/* Ignore separate left/right channel for now,
even the codec does support it. */
val &= 0xff;
switch (flag) {
case SET_VOLUME:
if (val < 0 || val > 100) {
DPRINTK("Trying a bad volume value(%d)!\n", val);
return -EPERM;
}
// Convert 0 -> 100 volume to 0x77 (LHV_MIN) -> 0x00 (LHV_MAX)
volume =
((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN;
aic33_local.volume_reg = OUTPUT_VOLUME_MAX - volume;
if (aic33_local.nochan == STEREO) {
audio_aic33_write(47, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(64, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(82, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(92, LOPM_ON | aic33_local.volume_reg);
} else if (aic33_local.nochan == MONO) {
#ifdef CONFIG_MONOSTEREO_DIFFJACK
/* DACL1 to MONO_LOP/M routing and volume control */
audio_aic33_write(75, LOPM_ON | aic33_local.volume_reg);
/* DACR1 to MONO_LOP/M routing and volume control */
audio_aic33_write(78, LOPM_ON | aic33_local.volume_reg);
#else
audio_aic33_write(47, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(64, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(82, LOPM_ON | aic33_local.volume_reg);
audio_aic33_write(92, LOPM_ON | aic33_local.volume_reg);
#endif
}
break;
case SET_LINE:
case SET_MIC:
if (val < 0 || val > 100) {
DPRINTK("Trying a bad volume value(%d)!\n", val);
return -EPERM;
}
volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
aic33_local.input_volume_reg = volume;
audio_aic33_write(15, aic33_local.input_volume_reg);
audio_aic33_write(16, aic33_local.input_volume_reg);
break;
case SET_RECSRC:
if (val == SOUND_MASK_MIC) {
/* enable the mic input*/
DPRINTK("Enabling mic\n");
audio_aic33_write(17, 0x0);
audio_aic33_write(18, 0x0);
/* enable ADC's and disable the line input*/
audio_aic33_write(19, 0x7C);
audio_aic33_write(22, 0x7C);
}
else if (val == SOUND_MASK_LINE) {
/* enable ADC's, enable line iput */
DPRINTK(" Enabling line in\n");
audio_aic33_write(19, 0x4);
audio_aic33_write(22, 0x4);
/* disable the mic input */
audio_aic33_write(17, 0xff);
audio_aic33_write(18, 0xff);
}
else {
/* do nothing */
}
aic33_local.recsrc = val;
break;
case SET_IGAIN:
if (val < 0 || val > 100) {
DPRINTK("Trying a bad igain value(%d)!\n", val);
return -EPERM;
}
gain = ((val * INPUT_GAIN_RANGE) / 100) + INPUT_GAIN_MIN;
DPRINTK("gain reg val = 0x%x", gain << 1);
/* Left AGC control */
audio_aic33_write(26, 0x80);
audio_aic33_write(27, gain << 1);
audio_aic33_write(28, 0x0);
/* Right AGC control */
audio_aic33_write(29, 0x80);
audio_aic33_write(30, gain << 1);
audio_aic33_write(31, 0x0);
break;
case SET_OGAIN:
if (val < 0 || val > 100) {
DPRINTK("Trying a bad igain value(%d)!\n", val);
return -EPERM;
}
gain = ((val * OUTPUT_GAIN_RANGE) / 100) + OUTPUT_GAIN_MIN;
gain = OUTPUT_GAIN_MAX - gain;
/* Left/Right DAC digital volume gain */
audio_aic33_write(43, gain);
audio_aic33_write(44, gain);
break;
case SET_MICBIAS:
if (val < 0 || val > 3) {
DPRINTK
("Request for non supported mic bias level(%d)!\n",
val);
return -EPERM;
}
if (val == 0)
audio_aic33_write(25, 0x00);
else if (val == 1)
audio_aic33_write(25, MICBIAS_OUTPUT_2_0V);
else if (val == 2)
audio_aic33_write(25, MICBIAS_OUTPUT_2_5V);
else if (val == 3)
audio_aic33_write(25, MICBIAS_OUTPUT_AVDD);
break;
case SET_BASS:
break;
case SET_TREBLE:
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, "AIC33", sizeof(mi.id));
strncpy(mi.name, "TI AIC33", sizeof(mi.name));
mi.modify_counter = aic33_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:
aic33_local.mod_cnt++;
ret = aic33_update(SET_VOLUME, val);
if (!ret)
aic33_local.volume = val;
break;
case SOUND_MIXER_LINE:
aic33_local.mod_cnt++;
ret = aic33_update(SET_LINE, val);
if (!ret)
aic33_local.line = val;
break;
case SOUND_MIXER_MIC:
aic33_local.mod_cnt++;
ret = aic33_update(SET_MIC, val);
if (!ret)
aic33_local.mic = val;
break;
case SOUND_MIXER_RECSRC:
if (hweight32(val) > 1)
ret = -EINVAL;
if ((val & SOUND_MASK_LINE) ||
(val & SOUND_MASK_MIC)) {
if (aic33_local.recsrc != val) {
aic33_local.mod_cnt++;
aic33_update(SET_RECSRC, val);
}
}
else {
ret = -EINVAL;
}
break;
case SOUND_MIXER_BASS:
aic33_local.mod_cnt++;
ret = aic33_update(SET_BASS, val);
if (!ret)
aic33_local.bass = val;
break;
case SOUND_MIXER_TREBLE:
aic33_local.mod_cnt++;
ret = aic33_update(SET_TREBLE, val);
if (!ret)
aic33_local.treble = val;
break;
case SOUND_MIXER_IGAIN:
aic33_local.mod_cnt++;
ret = aic33_update(SET_IGAIN, val);
if (!ret)
aic33_local.igain = val;
break;
case SOUND_MIXER_OGAIN:
aic33_local.mod_cnt++;
ret = aic33_update(SET_OGAIN, val);
if (!ret)
aic33_local.ogain = val;
break;
case SOUND_MIXER_MICBIAS:
aic33_local.mod_cnt++;
ret = aic33_update(SET_MICBIAS, val);
if (!ret)
aic33_local.micbias = val;
break;
default:
ret = -EINVAL;
}
}
if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
ret = 0;
switch (nr) {
case SOUND_MIXER_VOLUME:
val = aic33_local.volume;
break;
case SOUND_MIXER_LINE:
val = aic33_local.line;
break;
case SOUND_MIXER_MIC:
val = aic33_local.mic;
break;
case SOUND_MIXER_RECSRC:
val = aic33_local.recsrc;
break;
case SOUND_MIXER_RECMASK:
val = REC_MASK;
break;
case SOUND_MIXER_IGAIN:
val = aic33_local.igain;
break;
case SOUND_MIXER_OGAIN:
val = aic33_local.ogain;
break;
case SOUND_MIXER_DEVMASK:
val = DEV_MASK;
break;
case SOUND_MIXER_BASS:
val = aic33_local.bass;
break;
case SOUND_MIXER_TREBLE:
val = aic33_local.treble;
break;
case SOUND_MIXER_CAPS:
val = 0;
break;
case SOUND_MIXER_STEREODEVS:
val = SOUND_MASK_VOLUME;
break;
case SOUND_MIXER_MICBIAS:
val = aic33_local.micbias;
break;
default:
val = 0;
ret = -EINVAL;
break;
}
if (ret == 0)
ret = put_user(val, (int *)arg);
}
out:
return ret;
}
int davinci_set_samplerate(long sample_rate)
{
u8 count = 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) {
DPRINTK("Invalid Sample Rate %d requested\n", (int)sample_rate);
return -EPERM;
}
/* CODEC DATAPATH SETUP */
/* Fsref to 48kHz, dual rate mode upto 96kHz */
if (reg_info[count].Fsref == 96000)
audio_aic33_write(7,
FS_REF_DEFAULT_48 | ADC_DUAL_RATE_MODE |
DAC_DUAL_RATE_MODE | LDAC_LCHAN | RDAC_RCHAN);
/* Fsref to 44.1kHz, dual rate mode upto 88.2kHz */
else if (reg_info[count].Fsref == 88200)
audio_aic33_write(7,
FS_REF_44_1 | ADC_DUAL_RATE_MODE |
DAC_DUAL_RATE_MODE | LDAC_LCHAN | RDAC_RCHAN);
/* Fsref to 48kHz */
else if (reg_info[count].Fsref == 48000)
audio_aic33_write(7,
FS_REF_DEFAULT_48 | LDAC_LCHAN | RDAC_RCHAN);
/* Fsref to 44.1kHz */
else if (reg_info[count].Fsref == 44100)
audio_aic33_write(7, FS_REF_44_1 | LDAC_LCHAN | RDAC_RCHAN);
/* Codec sample rate select */
audio_aic33_write(2, reg_info[count].data);
/* If PLL is to be used for generation of Fsref
Generate the Fsref using the PLL */
#if(MCLK==33)
if ((reg_info[count].Fsref == 96000) | (reg_info[count].Fsref == 48000)) {
/* For MCLK = 33.8688 MHz and to get Fsref = 48kHz
Fsref = (MCLK * k * R)/(2048 * p);
Select P = 2, R= 1, K = 5.8049, which results in J = 5, D = 8049 */
/*Enable the PLL | Q-value | P-value */
audio_aic33_write(3, PLL_ENABLE | 0x10 | 0x02);
audio_aic33_write(4, 0x14); /* J-value */
audio_aic33_write(5, 0x7D); /* D-value 8-MSB's */
audio_aic33_write(6, 0x04); /* D-value 6-LSB's */
//audio_aic33_write (11, 0x01); /* R-value, Default is 0x01 */
}
else if ((reg_info[count].Fsref == 88200) | (reg_info[count].Fsref ==
44100)) {
/* MCLK = 33.8688 MHz and to get Fsref = 44.1kHz
Fsref = (MCLK * k * R)/(2048 * p);
Select P = 2, R =1, K = 5.3333, which results in J = 5, D = 3333 */
/*Enable the PLL | Q-value | P-value */
audio_aic33_write(3, PLL_ENABLE | 0x10 | 0x02);
audio_aic33_write(4, 0x14); /* J-value */
audio_aic33_write(5, 0x34); /* D-value 8-MSB's */
audio_aic33_write(6, 0x14); /* D-value 6-LSB's */
//audio_aic33_write(11, 0x01); /* R-value, Default is 0x01 */
}
#elif(MCLK==22)
if ((reg_info[count].Fsref == 96000) | (reg_info[count].Fsref == 48000)) {
/* For MCLK = 22.5 MHz and to get Fsref = 48kHz
Fsref = (MCLK * k * R)/(2048 * p);
Select P = 2, R= 1, K = 8.7381, which results in J = 8, D = 7381 */
/*Enable the PLL | Q-value | P-value */
audio_aic33_write(3, PLL_ENABLE | 0x10 | 0x02);
audio_aic33_write(4, (8 << 2)); /* J-value */
audio_aic33_write(5, (unsigned char)(7381 >> 6)); /* D-value 8-MSB's */
audio_aic33_write(6, (unsigned char)(7381 << 2)); /* D-value 6-LSB's */
//audio_aic33_write (11, 0x01); /* R-value, Default is 0x01 */
}
else if ((reg_info[count].Fsref == 88200) | (reg_info[count].Fsref ==
44100)) {
/* MCLK = 22.5 MHz and to get Fsref = 44.1kHz
Fsref = (MCLK * k * R)/(2048 * p);
Select P = 2, R =1, K = 8.0281, which results in J = 8, D = 0281 */
/*Enable the PLL | Q-value | P-value */
audio_aic33_write(3, PLL_ENABLE | 0x10 | 0x02);
audio_aic33_write(4, (8 << 2)); /* J-value */
audio_aic33_write(5, (unsigned char)(281 >> 6)); /* D-value 8-MSB's */
audio_aic33_write(6, (unsigned char)(281 << 2)); /* D-value 6-LSB's */
//audio_aic33_write(11, 0x01); /* R-value, Default is 0x01*/
}
#else
#error "unknown audio codec frequency"
#endif
audio_samplerate = sample_rate;
#ifndef AIC33_MASTER
{
int clkgdv = 0;
unsigned long clkval = 0;
struct clk *mbspclk;
/*
Set Sample Rate at McBSP
Formula :
Codec System Clock = Input clock to McBSP;
clkgdv = ((Codec System Clock / (SampleRate * BitsPerSample * 2)) - 1);
FWID = BitsPerSample - 1;
FPER = (BitsPerSample * 2) - 1;
*/
mbspclk = davinci_mcbsp_get_clock();
if (mbspclk == NULL) {
DPRINTK(" Failed to get internal clock to MCBSP");
return -EPERM;
}
clkval = clk_get_rate(mbspclk);
DPRINTK("mcbsp_clk = %ld\n", clkval);
if (clkval)
clkgdv =
(clkval /
(sample_rate * DEFAULT_BITPERSAMPLE * 2)) - 1;
else {
DPRINTK(" Failed to get the MCBSP clock\n");
return -EPERM;
}
DPRINTK("clkgdv = %d\n", clkgdv);
if (clkgdv > 255 || clkgdv < 0) {
/* For requested sampling rate, the input clock to MCBSP cant be devided
down to get the in range clock devider value for 16 bits sample */
DPRINTK("Invalid Sample Rate %d requested\n",
(int)sample_rate);
return -EPERM;
}
initial_config.srgr1 =
(FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
initial_config.srgr2 =
(CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1));
davinci_mcbsp_stop(AUDIO_MCBSP);
davinci_mcbsp_config(AUDIO_MCBSP, &initial_config);
}
#endif /* AIC33_MASTER */
return 0;
}
static void davinci_aic33_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);
davinci_mcbsp_stop(AUDIO_MCBSP);
davinci_mcbsp_free(AUDIO_MCBSP);
/* Self clearing aic33 software reset */
audio_aic33_write(1, 0x80);
}
static void davinci_set_mono_stereo(int mode)
{
if (mode == MONO) {
#ifdef CONFIG_MONOSTEREO_DIFFJACK
/* MONO_LOP/M Output level control register */
audio_aic33_write(79, 0x99);
#else
/* HPLOUT/HPROUT output level control */
audio_aic33_write(51, 0x99);
audio_aic33_write(65, 0x99);
/* LEFT_LOP/M, RIGHT_LOP/M output level control */
audio_aic33_write(86, 0x99);
audio_aic33_write(93, 0x99);
#endif
/* Left DAC power up, Right DAC power down */
audio_aic33_write(37, 0xa0);
} else if (mode == STEREO) {
/* HPLOUT/HPROUT output level control */
audio_aic33_write(51, 0x99);
audio_aic33_write(65, 0x99);
/* LEFT_LOP/M, RIGHT_LOP/M output level control */
audio_aic33_write(86, 0x99);
audio_aic33_write(93, 0x99);
/* Left/Right DAC power up */
audio_aic33_write(37, 0xe0);
} else
DPRINTK(" REQUEST FOR INVALID MODE\n");
}
static inline void aic33_configure()
{
DPRINTK(" CONFIGURING AIC33\n");
/* Page select register */
audio_aic33_write(0, 0x0);
//audio_aic33_write(38, 0x10);
davinci_set_mono_stereo(aic33_local.nochan);
#ifdef AIC33_MASTER
/* Enable bit and word clock as Master mode, 3-d disabled */
audio_aic33_write(8, 0xc0 /*0xc4 */ );
#endif
aic33_update(SET_LINE, aic33_local.line);
aic33_update(SET_VOLUME, aic33_local.volume);
aic33_update(SET_RECSRC, aic33_local.recsrc);
aic33_update(SET_IGAIN, aic33_local.igain);
aic33_update(SET_OGAIN, aic33_local.ogain);
aic33_update(SET_MICBIAS, aic33_local.micbias);
}
static void davinci_aic33_initialize(void *dummy)
{
DPRINTK("entry\n");
/* initialize with default sample rate */
audio_samplerate = AUDIO_RATE_DEFAULT;
if (davinci_mcbsp_request(AUDIO_MCBSP) < 0) {
DPRINTK("MCBSP request failed\n");
return;
}
/* if configured, then stop mcbsp */
davinci_mcbsp_stop(AUDIO_MCBSP);
/* configure aic33 with default params */
aic33_configure();
/* set initial (default) sample rate */
davinci_set_samplerate(audio_samplerate);
davinci_mcbsp_config(AUDIO_MCBSP, &initial_config);
DPRINTK("exit\n");
}
static int
davinci_aic33_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 davinci-audio module.
*/
switch (cmd) {
case SNDCTL_DSP_STEREO:
ret = get_user(val, (int *)arg);
if (ret)
return ret;
/* the Davinci supports AIC33 as stereo, mono on stereo jack */
ret = (val == 0) ? -EINVAL : 1;
return put_user(ret, (int *)arg);
case SNDCTL_DSP_CHANNELS:
ret = get_user(val, (long *)arg);
if (ret) {
DPRINTK("get_user failed\n");
break;
}
if (val == STEREO) {
DPRINTK("Driver support for AIC33 as stereo\n");
aic33_local.nochan = STEREO;
davinci_set_mono_stereo(aic33_local.nochan);
} else if (val == MONO) {
DPRINTK("Driver support for AIC33 as mono\n");
aic33_local.nochan = MONO;
davinci_set_mono_stereo(aic33_local.nochan);
} else {
DPRINTK
("Driver support for AIC33 as stereo/mono mode\n");
return -EPERM;
}
case SOUND_PCM_READ_CHANNELS:
/* the Davinci supports AIC33 as stereo, mono on stereo jack */
if (aic33_local.nochan == MONO)
return put_user(MONO, (long *)arg);
else
return put_user(STEREO, (long *)arg);
case SNDCTL_DSP_SPEED:
ret = get_user(val, (long *)arg);
if (ret) {
DPRINTK("get_user failed\n");
break;
}
ret = davinci_set_samplerate(val);
if (ret) {
DPRINTK("davinci_set_samplerate failed\n");
break;
}
/* fall through */
case SOUND_PCM_READ_RATE:
return put_user(audio_samplerate, (long *)arg);
case SNDCTL_DSP_SETFMT: /* set Format */
ret = get_user(val, (long *)arg);
if (ret) {
DPRINTK("get_user failed\n");
break;
}
if (val != AFMT_S16_LE) {
DPRINTK
("Driver supports only AFMT_S16_LE audio format\n");
return -EPERM;
}
case SOUND_PCM_READ_BITS:
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 davinci_aic33_probe(void)
{
/* Get the fops from audio oss driver */
if (!(davinci_audio_fops = audio_get_fops())) {
DPRINTK
("Unable to get the file operations for AIC33 OSS driver\n");
audio_unregister_codec(&aic33_state);
return -EPERM;
}
aic33_local.volume = DEFAULT_OUTPUT_VOLUME;
aic33_local.line = DEFAULT_INPUT_VOLUME;
aic33_local.recsrc = SOUND_MASK_LINE; /* either of SOUND_MASK_LINE/SOUND_MASK_MIC */
aic33_local.igain = DEFAULT_INPUT_IGAIN;
aic33_local.ogain = DEFAULT_INPUT_OGAIN;
aic33_local.nochan = STEREO;
aic33_local.micbias = 1;
aic33_local.mod_cnt = 0;
/* register devices */
audio_dev_id = register_sound_dsp(davinci_audio_fops, -1);
mixer_dev_id = register_sound_mixer(&davinci_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 */
DPRINTK(PLATFORM_NAME " " CODEC_NAME " audio support initialized\n");
return 0;
}
#ifdef MODULE
static void __exit davinci_aic33_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 davinci_aic33_suspend(void)
{
/* Empty for the moment */
return 0;
}
static int davinci_aic33_resume(void)
{
/* Empty for the moment */
return 0;
}
static int __init audio_aic33_init(void)
{
int err = 0;
/* register the codec with the audio driver */
if ((err = audio_register_codec(&aic33_state))) {
DPRINTK
("Failed to register AIC33 driver with Audio OSS Driver\n");
} else {
DPRINTK("codec driver register success\n");
}
return err;
}
static void __exit audio_aic33_exit(void)
{
(void)audio_unregister_codec(&aic33_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;
davinci_aic33_initialize(foo);
DPRINTK("AIC33 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;
davinci_aic33_shutdown(foo);
DPRINTK("AIC33 codec shutdown.\n");
return 0;
}
#endif /* CONFIG_PROC_FS */
module_init(audio_aic33_init);
module_exit(audio_aic33_exit);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("Glue audio driver for the TI AIC33 codec.");
MODULE_LICENSE("GPL");
/*
* linux/sound/oss/davinci-audio-dma-intfc.c
*
* Common audio DMA handling for the Davinci processors
*
* Copyright (C) 2006 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
*
* 2005-10-01 Rishi Bhattacharya / Sharath Kumar - Added support for TI Davinci DM644x processor
*/
#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 <linux/completion.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#include <asm/delay.h>
#include <asm/arch/mcbsp.h>
#include <asm/arch/edma.h>
#include <asm/arch/memory.h>
#include "davinci-audio-dma-intfc.h"
#include "davinci-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 "davinci-audio"
#define MCBSP_DXR 0x01E02004
#define MCBSP_DRR 0x01E02000
#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref)
#define SPIN_ADDR (dma_addr_t)0
#define SPIN_SIZE 2048
#define NUMBER_OF_CHANNELS_TO_LINK 2
/* 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) (NUMBER_OF_CHANNELS_TO_LINK == 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)%NUMBER_OF_CHANNELS_TO_LINK)
#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 (0xffff*2)
#define CUT_DMA_SIZE MAX_DMA_SIZE
/**************************** DATA STRUCTURES *********************************/
struct audio_isr_work_item {
int current_lch;
u16 ch_status;
audio_stream_t *s;
};
static char work_item_running = 0;
static struct audio_isr_work_item work1, work2;
/*********************** MODULE SPECIFIC FUNCTIONS PROTOTYPES ****************/
static void audio_dsr_handler(unsigned long);
DECLARE_TASKLET(audio_isr_work1, audio_dsr_handler, (unsigned long)&work1);
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 davinci_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;
}
/* Allocate memory for all buffer fragments */
s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);
if (!s->buffers)
goto err;
/* Initialise all the memory to 0 */
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 {
/* allocate consistent memory for DMA
dmaphys(handle)= device viewed address.
dmabuf = CPU-viewed address */
dmabuf =
dma_alloc_coherent(NULL, dmasize, &dmaphys,
0);
/* For allocating the IRAM memory */
//dmaphys = (dma_addr_t)(DAVINCI_IRAM_BASE + 0x1000);
//dmabuf = (DAVINCI_IRAM_VIRT + 0x1000);
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->dma_started = 0;
s->bytecount = 0;
s->fragcount = 0;
s->prevbuf = 0;
init_completion(&s->wfc);
s->wfc.done = 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
*
******************************************************************************/
int
davinci_request_sound_dma(int device_id, const char *device_name, void *data,
int *master_ch, int **channels)
{
int i, err = 0;
int *chan = NULL;
int tcc;
FN_IN;
if (unlikely((NULL == channels) || (NULL == device_name))) {
BUG();
return -EPERM;
}
/* Try allocate memory for the num channels */
*channels = (int *)kmalloc(sizeof(int) * NUMBER_OF_CHANNELS_TO_LINK,
GFP_KERNEL);
chan = *channels;
if (NULL == chan) {
ERR("No Memory for channel allocs!\n");
FN_OUT(-ENOMEM);
return -ENOMEM;
}
/* request for the Master channel and setup the params */
i = 0;
err = davinci_request_dma(device_id, device_name,
sound_dma_irq_handler, data, master_ch, &tcc,
EVENTQ_0);
/* Handle Failure condition here */
if (err < 0) {
ERR("Error in requesting Master channel %d = 0x%x\n", device_id,
err);
FN_OUT(err);
return err;
}
DPRINTK("Master chan = %d\n", *master_ch);
for (i = 0; i < NUMBER_OF_CHANNELS_TO_LINK; i++) {
err = davinci_request_dma(DAVINCI_EDMA_PARAM_ANY, device_name,
sound_dma_irq_handler, data, &chan[i],
&tcc, EVENTQ_0);
/* Handle Failure condition here */
if (err < 0) {
int j;
for (j = 0; j < i; j++)
davinci_free_dma(chan[j]);
kfree(chan);
*channels = NULL;
ERR("Error in requesting channel %d=0x%x\n", i, err);
FN_OUT(err);
return err;
}
}
/* Chain the channels together */
for (i = 0; i < NUMBER_OF_CHANNELS_TO_LINK; i++) {
int cur_chan = chan[i];
int nex_chan = ((NUMBER_OF_CHANNELS_TO_LINK - 1 ==
i) ? chan[0] : chan[i + 1]);
davinci_dma_link_lch(cur_chan, nex_chan);
}
FN_OUT(0);
return 0;
}
/******************************************************************************
*
* DMA channel requests Freeing
*
******************************************************************************/
int davinci_free_sound_dma(int master_ch, 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);
/* release the Master channel */
davinci_free_dma(master_ch);
for (i = 0; i < NUMBER_OF_CHANNELS_TO_LINK; i++) {
int cur_chan = chan[i];
int nex_chan = ((NUMBER_OF_CHANNELS_TO_LINK - 1 == i) ?
chan[0] : chan[i + 1]);
davinci_dma_unlink_lch(cur_chan, nex_chan);
davinci_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);
DPRINTK("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) {
DPRINTK("dma_size > MAX_DMA_SIZE\n");
dma_size = CUT_DMA_SIZE;
}
ret = davinci_start_sound_dma(s, b->dma_addr + b->offset,
dma_size);
if (ret) {
DPRINTK("error\n");
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;
DPRINTK("we are spinning\n");
while (davinci_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;
init_completion(&is->wfc);
is->wfc.done = 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) {
/*wait for a buffer to become free */
if (wait_for_completion_interruptible(&s->wfc))
return 0;
/*
* 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.
*/
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 || (s->wfc.done < 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)
{
unsigned long flags;
FN_IN;
DPRINTK("audio_stop_dma\n");
if (s->dma_spinref > 0 || !s->buffers)
return;
local_irq_save(flags);
davinci_mcbsp_stop(0);
s->started = 0;
if (s->spin_idle) {
#if 0
DMA_START(s, SPIN_ADDR, SPIN_SIZE);
DMA_START(s, SPIN_ADDR, SPIN_SIZE);
#endif
s->dma_spinref = 2;
} else
s->dma_spinref = 0;
local_irq_restore(flags);
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 = 0;
FN_IN;
if (b->dma_ref) {
edmacc_paramentry_regs temp;
davinci_get_dma_params(s->master_ch, &temp);
if (s->input_or_output == FMODE_WRITE)
offset = temp.src - b->dma_addr;
else if (s->input_or_output == FMODE_READ)
offset = temp.dst - 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)
{
audio_buf_t *b;
FN_IN;
if (s->buffers) {
audio_stop_dma(s);
/* back up pointers to be ready to restart from the same spot */
while (s->dma_head != s->dma_tail) {
b = &s->buffers[s->dma_head];
if (b->dma_ref) {
b->dma_ref = 0;
b->offset = 0;
}
s->pending_frags++;
if (s->dma_head == 0)
s->dma_head = s->nbfrags;
s->dma_head--;
}
b = &s->buffers[s->dma_head];
if (b->dma_ref) {
b->offset = 0;
b->dma_ref = 0;
}
s->buffers[s->dma_head].offset = 0;
s->buffers[s->usr_head].offset = 0;
s->usr_head = s->dma_head;
s->pending_frags = 0;
init_completion(&s->wfc);
s->wfc.done = s->nbfrags;
}
AUDIO_QUEUE_INIT(s);
s->active = 0;
s->stopped = 0;
s->started = 0;
s->dma_started = 0;
davinci_stop_dma(s->master_ch);
FN_OUT(0);
return;
}
/*******************************************************************************
*
* Clear any pending transfers
*
******************************************************************************/
void davinci_clear_sound_dma(audio_stream_t * s)
{
#if 0
FN_IN;
davinci_clear_dma(s->lch[s->dma_q_head]);
FN_OUT(0);
#endif
return;
}
/*******************************************************************************
*
* DMA related functions
*
******************************************************************************/
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
FN_IN;
DPRINTK("audio_set_dma_params_play channel = %d dma_ptr = %x \
dma_size=%x\n", channel, dma_ptr, dma_size);
davinci_set_dma_src_params(channel, (unsigned long)(dma_ptr), 0, 0);
davinci_set_dma_dest_params(channel, (unsigned long)MCBSP_DXR, 0, 0);
davinci_set_dma_src_index(channel, 2, 0);
davinci_set_dma_dest_index(channel, 0, 0);
davinci_set_dma_transfer_params(channel, 2, dma_size / 2, 1, 0, ASYNC);
FN_OUT(0);
return 0;
}
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
FN_IN;
DPRINTK("audio_set_dma_params_capture channel = %d dma_ptr = %x \
dma_size=%x\n", channel, dma_ptr, dma_size);
davinci_set_dma_src_params(channel, (unsigned long)MCBSP_DRR, 0, 0);
davinci_set_dma_dest_params(channel, (unsigned long)(dma_ptr), 0, 0);
davinci_set_dma_src_index(channel, 0, 0);
davinci_set_dma_dest_index(channel, 2, 0);
davinci_set_dma_transfer_params(channel, 2, dma_size / 2, 1, 0, ASYNC);
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) {
edmacc_paramentry_regs temp;
davinci_get_dma_params(channel, &temp);
davinci_set_dma_params(s->master_ch, &temp);
s->started = 1;
if (!s->dma_started) {
davinci_start_dma(s->master_ch);
s->dma_started = 1;
}
davinci_mcbsp_start(0);
}
/* 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 davinci_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)) {
DPRINTK("queue full\n");
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)) {
DPRINTK("Interrupt(%d) for empty queue(h=%d, T=%d)???\n",
sound_curr_lch, s->dma_q_head, s->dma_q_tail);
DPRINTK("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);
AUDIO_INCREMENT_HEAD(s); /* Empty the queue */
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)
{
audio_stream_t *s = (audio_stream_t *) data;
FN_IN;
if (ch_status == DMA_COMPLETE) {
#ifdef IRQ_TRACE
xyz[h++] = '0' + sound_curr_lch;
if (h == MAX_UP - 1) {
DPRINTK("%s-", xyz);
h = 0;
}
#endif
sound_curr_lch = s->lch[s->dma_q_head];
DPRINTK("lch=%d,status=0x%x, data=%p\n", sound_curr_lch,
ch_status, data);
if (AUDIO_QUEUE_LAST(s)) {
audio_stream_t *s = data;
audio_buf_t *b = &s->buffers[s->dma_tail];
if (s->dma_spinref > 0) {
s->dma_spinref--;
} else if (!s->buffers) {
DPRINTK
("davinci_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) {
complete(&s->wfc);
} else
s->pending_frags++;
wake_up(&s->wq);
}
AUDIO_INCREMENT_HEAD(s);
audio_stop_dma(s);
return;
}
/* 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;
}
} else {
DPRINTK("Error in DMA \n");
}
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) {
DPRINTK
("davinci_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) {
complete(&s->wfc);
} 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;
}
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION
("Common DMA handling for Audio driver on DAVINCI processors");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(davinci_clear_sound_dma);
EXPORT_SYMBOL(davinci_request_sound_dma);
EXPORT_SYMBOL(davinci_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/davinci-audio-dma-intfc.h
*
* Common audio DMA handling for the Davinci processors
*
* Copyright (C) 2006 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:
*
* 2005-10-01 Rishi Bhattacharya / Sharath Kumar - Added support for TI Davinci DM644x processor
*/
#ifndef __DAVINCI_AUDIO_DMA_INTFC_H
#define __DAVINCI_AUDIO_DMA_INTFC_H
/******************************* INCLUDES *************************************/
/* Requires davinci-audio.h */
#include "davinci-audio.h"
/************************** GLOBAL MACROS *************************************/
/* Provide the Macro interfaces common across platforms */
#define DMA_REQUEST(e,s, cb) {e=davinci_request_sound_dma(s->dma_dev, s->id, s, &s->master_ch, &s->lch);}
#define DMA_FREE(s) davinci_free_sound_dma(s->master_ch,&s->lch)
#define DMA_CLEAR(s) davinci_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 davinci_clear_sound_dma(audio_stream_t * s);
int davinci_request_sound_dma(int device_id, const char *device_name,
void *data, int *master_ch, int **channels);
int davinci_free_sound_dma(int master_ch, int **channels);
#endif /* #ifndef __DAVINCI_AUDIO_DMA_INTFC_H */
/*
* linux/sound/oss/davinci-audio.c
*
* Common audio handling for the Davinci processors
*
* Copyright (C) 2006 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
*
* 2005-10-01 Rishi Bhattacharya - Adapted to TI Davinci Family of processors
*/
/***************************** 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/platform_device.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#include "davinci-audio-dma-intfc.h"
#include "davinci-audio.h"
/***************************** MACROS ************************************/
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK printk
#define FN_IN printk("[davinci_audio.c:[%s] start\n", __FUNCTION__)
#define FN_OUT(n) printk("[davinci_audio.c:[%s] end(%d)\n", __FUNCTION__ , n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(x)
#endif
#define DAVINCI_AUDIO_NAME "davinci-audio"
#define AUDIO_NBFRAGS_DEFAULT 4
#define AUDIO_FRAGSIZE_DEFAULT 3072
/*************/
/* 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 *buffer,
size_t count, loff_t * ppos);
static int audio_read(struct file *file, char *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, u32 state, 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 = { 0 };
/* DMA Call back function */
static dma_callback_t audio_dma_callback = 0;
/* File Ops structure */
static struct file_operations davinci_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 davinci_audio_driver = {
.name = DAVINCI_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 davinci_audio_device = {
.name = DAVINCI_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) {
DPRINTK("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) {
DPRINTK("IS Suspend\n");
stopstate = is->stopped;
audio_stop_dma(is);
DMA_CLEAR(is);
is->dma_spinref = 0;
is->stopped = stopstate;
}
if (os && os->buffers) {
DPRINTK("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) {
DPRINTK("OS Resume\n");
audio_reset(os);
audio_process_dma(os);
}
if (is && is->buffers) {
DPRINTK("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) {
DPRINTK("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, u32 state, u32 level)
{
int ret = 0;
#ifdef CONFIG_PM
void *data = dev->driver_data;
FN_IN;
if (level != 3) {
return 0;
}
if (audio_state.hw_suspend) {
ret = audio_ldm_suspend(data);
if (ret == 0)
ret = audio_state.hw_suspend();
}
if (ret) {
DPRINTK("Audio Suspend Failed \n");
} else {
DPRINTK("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 != 0) {
return 0;
}
if (audio_state.hw_resume) {
ret = audio_ldm_resume(data);
if (ret == 0)
ret = audio_state.hw_resume();
}
if (ret) {
DPRINTK(" Audio Resume Failed \n");
} else {
DPRINTK(" 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
* DAVINCI Audio Driver
*
******************************************************************************/
struct file_operations *audio_get_fops(void)
{
FN_IN;
FN_OUT(0);
return &davinci_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) {
DPRINTK(" Codec Already registered\n");
return -EPERM;
}
/* Grab the dma Callback */
audio_dma_callback = audio_get_dma_callback();
if (!audio_dma_callback) {
DPRINTK("Unable to get call back function\n");
return -EPERM;
}
/* Sanity checks */
if (!codec_state) {
DPRINTK("NULL ARGUMENT!\n");
return -EPERM;
}
if (!codec_state->hw_probe || !codec_state->hw_init
|| !codec_state->hw_shutdown || !codec_state->client_ioctl) {
DPRINTK
("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));
sema_init(&audio_state.sem, 1);
ret = platform_device_register(&davinci_audio_device);
if (ret != 0) {
DPRINTK("Platform dev_register failed =%d\n", ret);
ret = -ENODEV;
goto register_out;
}
ret = driver_register(&davinci_audio_driver);
if (ret != 0) {
DPRINTK("Device Register failed =%d\n", ret);
ret = -ENODEV;
platform_device_unregister(&davinci_audio_device);
goto register_out;
}
DPRINTK("audio driver register success\n");
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) {
DPRINTK(" No Codec registered\n");
return -EPERM;
}
/* Security check */
if (audio_state.hw_init != codec_state->hw_init) {
DPRINTK
("Attempt to unregister codec which was not registered with us\n");
return -EPERM;
}
driver_unregister(&davinci_audio_driver);
platform_device_unregister(&davinci_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 *buffer, size_t count, loff_t * ppos)
{
const char *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) {
DPRINTK("FPOS not ppos ppos=0x%x fpos =0x%x\n", (u32) * ppos,
(u32) file->f_pos);
return -ESPIPE;
}
if (s->mapped) {
DPRINTK("s already mapped\n");
return -ENXIO;
}
if (!s->buffers && audio_setup_buf(s)) {
DPRINTK("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 (!s->wfc.done)
break;
} else {
ret = -ERESTARTSYS;
if (wait_for_completion_interruptible(&s->wfc))
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)) {
DPRINTK("Audio: CopyFrom User failed \n");
complete(&s->wfc);
return -EFAULT;
}
buffer += chunksize;
count -= chunksize;
b->offset += chunksize;
if (b->offset < s->fragsize) {
complete(&s->wfc);
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 *buffer, size_t count, loff_t * ppos)
{
char *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) {
DPRINTK("AudioRead - FPOS not ppos ppos=0x%x fpos =0x%x\n",
(u32) * ppos, (u32) file->f_pos);
return -ESPIPE;
}
if (s->mapped) {
DPRINTK("AudioRead - s already mapped\n");
return -ENXIO;
}
if (!s->active) {
if (!s->buffers && audio_setup_buf(s)) {
DPRINTK("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 (!s->wfc.done)
break;
} else {
ret = -ERESTARTSYS;
if (wait_for_completion_interruptible(&s->wfc))
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)) {
complete(&s->wfc);
return -EFAULT;
}
buffer += chunksize;
count -= chunksize;
b->offset += chunksize;
if (b->offset < s->fragsize) {
complete(&s->wfc);
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);
DPRINTK(KERN_INFO
"calling audio_process_dma from audio_read\n");
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 = 0;
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;
#if 0
ret =
remap_pfn_range(vma, vma_addr, buf->dma_addr >> PAGE_SHIFT,
buf->master, vma->vm_page_prot);
#endif
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 && is->wfc.done > 0))
mask |= POLLIN | POLLRDNORM;
if (file->f_mode & FMODE_WRITE)
if ((os->mapped && os->bytecount > 0) ||
(!os->mapped && os->wfc.done > 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 *)arg);
case SNDCTL_DSP_GETBLKSIZE:
if (file->f_mode & FMODE_WRITE)
return put_user(os->fragsize, (int *)arg);
else
return put_user(is->fragsize, (int *)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 *)arg);
case SNDCTL_DSP_SETFRAGMENT:
if (get_user(val, (long *)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 *)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 *)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 *)arg);
case SNDCTL_DSP_SETTRIGGER:
if (get_user(val, (int *)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 {
is->stopped = 1;
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;
init_completion(&os->wfc);
os->wfc.done = 0;
os->active = 1;
}
os->stopped = 0;
local_irq_restore(flags);
audio_process_dma(os);
} else {
os->stopped = 1;
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 *)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;
audio_buf_t *b = &s->buffers[s->usr_head];
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 = s->wfc.done * s->fragsize;
inf.bytes -= b->offset;
if(inf.bytes < 0)
inf.bytes = 0;
inf.fragments = inf.bytes / s->fragsize;
inf.fragsize = s->fragsize;
inf.fragstotal = s->nbfrags;
FN_OUT(20);
return copy_to_user((void *)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 aic33_init_flag = 0;
FN_IN;
/* Lock the module */
if (!try_module_get(THIS_MODULE)) {
DPRINTK("Failed to get module\n");
return -ESTALE;
}
/* Lock the codec module */
if (!try_module_get(state->owner)) {
DPRINTK("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) {
DPRINTK("DMA REQUEST FOR playback\n");
DMA_REQUEST(err, os, audio_dma_callback);
if (err < 0)
goto out;
}
if (file->f_mode & FMODE_READ) {
DPRINTK("DMA REQUEST FOR record\n");
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 && !aic33_init_flag) {
state->hw_init(state->data);
aic33_init_flag = 0;
}
}
if ((file->f_mode & FMODE_WRITE)) {
DPRINTK("SETUP FOR PLAYBACK\n");
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) {
DPRINTK("SETUP FOR RECORD\n");
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 DAVINCI processors");
MODULE_LICENSE("GPL");
/*
* linux/sound/oss/davinci-audio.h
*
* Common audio handling for the Davinci processors
*
* Copyright (C) 2006 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:
*
* 2005-10-01 Rishi Bhattacharya - Adapted to TI Davinci Family of processors
*/
#ifndef __DAVINCI_AUDIO_H
#define __DAVINCI_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 */
u_int prevbuf; /* Prev pending frag size sent to DMA */
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 master_ch;
int *lch; /* Chain of channels this stream is linked to */
int input_or_output; /* Direction of this data stream */
int bytecount; /* nbr of processed bytes */
int fragcount; /* nbr of fragment transitions */
struct completion wfc; /* wait for "nbfrags" fragment completion */
wait_queue_head_t wq; /* for poll */
int dma_spinref; /* DMA is spinning */
int mapped:1; /* mmap()'ed buffers */
int active:1; /* actually in progress */
int stopped:1; /* might be active but stopped */
int spin_idle:1; /* have DMA spin on zeros when idle */
int dma_started; /* to store if DMA was started or not */
} 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;
int rd_ref:1; /* open reference for recording */
int wr_ref:1; /* open reference for playback */
int 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 davinci 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 __DAVINCI_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