在Linux源码里,Aduio这一部分现在是一个独立文件夹叫sound,在2.x的版本时,sound这个目录是在drivers里的,后来从这个里面剥离出来了,很多人不知道其中的原因,我也不知道,我们先回顾一下这段历史,当时Linux主线的audio还在使用OSS架构,一开始是在drivers里,后来就把OSS架构这个audio剥离出来了,可能是因为OSS走向了商业化的原因,后来因为商业化导致版权问题,ALSA就出现了,同样代码都在sound里,就这样一直延续了下来.
下边我们先来介绍一下Audio的基础知识,先从接口开始,常见接口有3种(IIS/ PCM/ AC'97):
IIS
Integrated Interchip Sound,这种接口跟IIC差不多,因为都是同一个公司:Philips做的,用于数字音频数据在系统内器件之间传输,例如编解码器CODEC、DSP、数字输入/输出接口、ADC、DAC和数字滤波器等。其与IIC无关联,这一点是需要注意的,IIS是个相对来说简单的接口协议,没有地址和片选机制,在总线上,只能同时存在一个主设备和发射设备;提供时钟的设备为主设备,可以是发射设备也可以是接收设备,或者是协调两者的其他控制设备。
IIS协议定义三根信号线:时钟信号SCK、数据信号SD和左右声道选择信号WS,如下如:
SCK:模块内的同步信号,从模式时由外部提供,主模式时由内部产生;
SD:串行数据,以二进制补码形式在数据线上传输;在WS变化后的第一个SCK脉冲,MSB先行;
WS:帧时钟,其电平变化频率等于声音采样率,高电平和低电平状态就是左右声道区分的状态;
IIS的操作模式有3种: 标准模式, 左对齐模式和右对齐模式.
标准模式:是左对齐模式延迟一个时钟位变化来的,左右通道的数据MSB均是在WS变化后第二个SCK/BCLK上升沿有效.
左对齐模式:标准左对齐格式的数据的MSB没有相对于BCLK延迟一个时钟。左对齐格式的左右声道数据的MSB在WS边沿变化后SCK/BCLK的第一个上升沿有效,支持16~32bit字长格式.
右对齐模式:接收设备必须事先知道待传数据的字长,目前是索尼公司采用这种格式.
这3种模式下,WS线的意义是不同的,在标准模式下的WS时钟高电平为右声道,低电平为左声道,左右对齐模式刚好相反.
SCK = 采样率(48K、44.1K、16K等) x 字长(16bit、24bit、32bit) x 2(左右两通道);
PCM
Pulse Code Modulation,是通过等时间隔(即采样率时钟周期)采样将模拟信号数字化的方法.接口传输的音频数据是通过PCM方式采样得到的,区别于PDM形式;IIS传输的也是PCM类型数据,属于其一个特例。相比于IIS,PCM接口更加灵活,通过时分复用TDM方式,PCM接口支持多大N个声道的数据.TDM不像IIS有统一标准,不同厂家TDM时有差异。
PCM Interface
IIS Interface
PCM_OUT
SD_OUT
PCM_IN
SD_IN
PCM_SYNC
WS
PCM_CLK
SCK
接口名称有如下规律:
PCM:传输单声道数据,比如麦克风;
IIS:传输双声道数据,比如喇叭;
TDM:传输两个以上声道数据,同时区别于IIS特定格式。
根据SD相对于FSYNC的位置,TDM分两种基本模式:
Mode A: 数据在FSYNC有效后,BCLK的第二个上升沿有效;
Mode B: 数据在FSYNC有效后,BCLK的第一个上升沿有效;
不同厂商对于两种模式的定义可能有点差别。
FSYNC的高电平等于一个BCLK的周期,其频率就等于采样率,与通道数无关。
BCLK的频率会随通道数的增加成倍数增加:8 × 32 × 48kHz = 12.288 MHz。
其中又分为长帧同步和短帧同步:
>短帧同步:一个脉冲宽度等于一个BCLK的周期长度
>长帧同步:一个脉冲宽度等于一个slot的长度
AC'97
AC97是以Intel为首的5个PC厂商,在1997年共同提出的规格标准。与PCM/I2S不同:AC97不只是一种数据格式,它还具有控制功能。AC'97采用AC-Link与外部的编解码器相连,AC-Link接口包括:
(1)位时钟(BITCLK)
(2)同步信号校正(SYNC)
(3)从编码到处理器及从处理器中解码(SDATDIN与SDATAOUT)的数据队列
AC'97数据帧以SYNC脉冲开始,包括12个20位时间段(时间段为标准中定义的不同的目的服务)及16位“tag”段,共计256个数据序列。下图是AC97的接口图:
在全志A33上,支持PCM,IIS,所以,这里也只讨论这两种接口,下面,我们先通过外围电路了解A33上audio相关内容,先看一下电路图:
CPU部分:
耳机部分:
扬声器部分:
MIC部分:
剩下还有一些电路的供电,稳压等这里就不贴出来了,我们这里以CPU部分来说明,因为这部分比较抽象,容易说,每个管脚的意义如下面这个表格所示:
Signal Description
Signal Name
Type
Description
HBIAS
OUT
耳机偏置电压
MBIAS
OUT
主麦克偏置电压
PHONEOUTP
OUT
听筒正极输出
PHONEOUTN
OUT
听筒负极输出
MICIN1P
IN
主麦克正极输入
MICIN1N
IN
主麦克负极输入
MICIN2P
IN
副麦克正极输入
MICIN2N
IN
副麦克负极输入
PHONEP
IN
听筒正极输入
PHONEN
IN
听筒负极输入
LINEINL
IN
左输入行
LINEINR
IN
右输入行
HPCOMFB
IN
耳机基准电压反馈
HPCOM
OUT
耳机基准电压
HPOUTL
OUT
耳机左声道
HPOUTR
OUT
耳机右声道
Power Description
VRA1
OUT
参考电压
VRA2
OUT
参考电压
VRP
OUT
参考电压
AVCC
IN
模拟电压
HPVCCIN
IN
耳机放大器电源
HPVCCBP
OUT
耳机放大器功率旁路
AGND
GND
模拟电压地线
其中标注蓝 {MOD}字体的是实际硬件用到的,这里扬声器和耳机共用耳机声道,硬件上通过PA-SHDN引脚(PH9)进行切换,以上内容就是SoC给我们暴露的实际引脚,情况是这个样子的,就是音频编解码器是在SoC内部的,我们用的是片上外设,比买一个音频芯片要省出来一些钱,然后SoC内部,音频编解码器对SoC暴露PCM/IIS接口(因为IIS是PCM的特例,硬件结构类似,通过软件配置,即可实现切换接口),对外部直接暴露驱动外设的接口,本质上外部接口就是ADC,DAC,我们看一下datasheet上音频编解码器的大致框架图,因为寄存器比较多,有100多个,这里就不列举出来了:
有了基本的一个audio硬件的概念之后,我们就来学习ALSA框架,之后,后边结合代码看一下,下面说一下ALSA的一些基本知识,推荐一下这个人写的音频系列教程,特详细,此处就是借鉴他的:
https://blog.csdn.net/DroidPhone
概述
上图展现了在Linux上Audio的ALSA架构,用户空间的alsa-lib对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度。内核空间中,alsa-soc其实是对alsa-driver的进一步封装,它针对嵌入式设备提供了一些列增强的功能.
ALSA设备文件结构
cd /dev/snd
ls -l
crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq
crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer
它们代表的意义是:
controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
midiC0D0 --> 用于播放midi音频
pcmC0D0c --> 用于录音的pcm设备
pcmC0D0p --> 用于播放的pcm设备
seq --> 音序器
timer --> 定时器
其中,C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。在我这用的这个平板上,设备没有这么多,只有下面这几个:
crw-rw---- system audio 116, 0 1970-01-11 10:04 controlC0
crwxrwxrwx media media 116, 24 1970-01-11 10:04 pcmC0D0c
crwxrwxrwx media media 116, 16 1970-01-11 10:04 pcmC0D0p
crw-rw---- system audio 116, 33 1970-01-11 10:04 timer
目录树大概是这么一种情况:
下面对这些文件做一个大概的介绍:
core
该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分
core/oss
包含模拟旧的OSS架构的PCM和Mixer模块
core/seq
有关音序器相关的代码
include
ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里
drivers
放置一些与CPU、BUS架构无关的公用代码
i2c
ALSA自己的I2C控制代码
pci
pci声卡的顶层目录,子目录包含各种pci声卡的代码
isa
isa声卡的顶层目录,子目录包含各种isa声卡的代码
soc
针对system-on-chip体系的中间层代码
soc/codecs
针对soc体系的各种codec的代码,与平台无关
我们要操作的代码就在soc目录中,这部分是和厂商相关的,全志的是soc/sunxi.
下面我们分析一下代码结构以及实际的代码,通过分析menuconfig, Makefile, Kconfig以及.o文件,绘制了下面这张图:
其中,core部分是通用的,我们主要移植内容是SoC部分,此部分一般有厂商提供,此处全志是提供了的,全志提供的这部分内容,大多也是通用于全志平台的芯片,不同芯片区别就是类似于sun8iw5_sndcodec.c这种文件,sun8iw5代表A33,sun8iw7代表H3,这个根据实际情况配置的,其它部分大多数情况下通用,因为时间原因,这里不能深入探讨具体实现框架,后边,真正有空了,会对整个子系统进行深入研究,也会分享出来,我们这里就贴一下差异部分代码,sun8iw5_sndcodec.c
其它代码可参考这里:
https://github.com/orangepi-xunlong/orangepi_h3_linux
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "sunxi_codecdma.h"
#include "sun8iw5_sndcodec.h"
#define sndpcm_RATES (SNDRV_PCM_RATE_8000_192000|SNDRV_PCM_RATE_KNOT)
#define sndpcm_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct regulator* hp_ldo = NULL;
static char *hp_ldo_str = NULL;
static int play_running = 0;
static int cap_running = 0;
static volatile int current_running = -1;
/*for pa gpio ctrl*/
static script_item_u item;
//static script_item_value_type_e type;
/*for phone call flag*/
static bool codec_analog_phonein_en = false;
static bool codec_analog_mainmic_en = false;
static bool codec_analog_phoneout_en = false;
static bool codec_lineinin_en = false;
static bool codec_lineincap_en = false;
static bool codec_analog_headsetmic_en = false;
static bool codec_speakerout_en = false;
static bool codec_earpieceout_en = false;
static bool codec_voice_record_en = false;
static bool codec_headphoneout_en = false;
/*Digital_bb*/
static bool codec_digital_headsetmic_en = false;
static bool codec_digital_mainmic_en = false;
static bool codec_digital_phoneout_en = false;
static bool codec_digital_phonein_en = false;
static bool codec_digital_bb_clk_format_init = false;
/*bluetooth*/
static bool codec_bt_clk_format = false;
static bool codec_bt_out_en = false;
static bool bt_bb_button_voice = false;
static bool codec_analog_btmic_en = false;
static bool codec_analog_btphonein_en = false;
static bool codec_digital_btmic_en = false;
static bool codec_digital_btphonein_en = false;
static bool codec_digital_bb_bt_clk_format = false;
static bool codec_system_bt_capture_en = false;
static bool codec_analog_bb_capture_mic = false;
static int codec_speaker_headset_earpiece_en=0;
static int pa_vol = 0;
static int cap_vol = 0;
static int earpiece_vol = 0;
static int headphone_vol = 0;
static int pa_double_used = 0;
static int phone_main_mic_vol = 0;
static int headphone_direct_used = 0;
static int phone_headset_mic_vol = 0;
static int aif2_used = 0;
static int aif3_used = 0;
static int dac_vol_ctrl_spk =0x9e9e;
static int dac_vol_ctrl_headphone =0xa0a0;
static int agc_used = 0;
static int drc_used = 0;
static struct label reg_labels[]={
LABEL(DAC_Digital_Part_Control),
LABEL(DAC_FIFO_Control),
LABEL(DAC_FIFO_Status),
LABEL(DAC_TX_DATA),
LABEL(ADC_FIFO_Control),
LABEL(ADC_FIFO_Status),
LABEL(ADC_RX_DATA),
LABEL(DAC_TX_Counter),
LABEL(ADC_RX_Counter),
LABEL(DAC_Debug),
LABEL(ADC_Debug),
LABEL(Headphone_Volume_Control),
LABEL(Left_Output_Mixer_Source_Control),
LABEL(Right_Output_Mixer_Source_Control),
LABEL(DAC_Analog_Enable_and_PA_Source_Control),
LABEL(Phonein_Stereo_Gain_Control),
LABEL(Linein_and_Phone_P_N_Gain_Control),
LABEL(MIC1_and_MIC2_GAIN_Control),
LABEL(PA_Enable_and_HP_Control),
LABEL(Phoneout_Control),
LABEL(Lineout_Volume_Control),
LABEL(Mic2_Boost_and_Lineout_Enable_Control),
LABEL(Mic1_Boost_and_MICBIAS_Control),
LABEL(Left_ADC_Mixer_Source_Control),
LABEL(Right_ADC_Mixer_Source_Control),
LABEL(PA_UPTIME_CTRL),
LABEL(ADC_ANALIG_PART_ENABLE_REG),
LABEL_END,
};
static struct clk *codec_pll2clk,*codec_moduleclk,*codec_srcclk;
static unsigned int read_prcm_wvalue(unsigned int addr)
{
unsigned int reg;
reg = readl(ADDA_PR_CFG_REG);
reg |= (0x1<<28);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= ~(0x1<<24);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= ~(0x1f<<16);
reg |= (addr<<16);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= (0xff<<0);
return reg;
}
static void write_prcm_wvalue(unsigned int addr, unsigned int val)
{
unsigned int reg;
reg = readl(ADDA_PR_CFG_REG);
reg |= (0x1<<28);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= ~(0x1f<<16);
reg |= (addr<<16);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= ~(0xff<<8);
reg |= (val<<8);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg |= (0x1<<24);
writel(reg, ADDA_PR_CFG_REG);
reg = readl(ADDA_PR_CFG_REG);
reg &= ~(0x1<<24);
writel(reg, ADDA_PR_CFG_REG);
}
/**
* codec_wrreg_bits - update codec register bits
* @reg: codec register
* @mask: register mask
* @value: new value
* Writes new register value.
* Return 1 for change else 0.
*/
static int codec_wrreg_prcm_bits(unsigned short reg, unsigned int mask, unsigned int value)
{
unsigned int old, new;
old = read_prcm_wvalue(reg);
new = (old & ~mask) | value;
write_prcm_wvalue(reg,new);
return 0;
}
static int codec_wr_prcm_control(u32 reg, u32 mask, u32 shift, u32 val)
{
u32 reg_val;
reg_val = val << shift;
mask = mask << shift;
codec_wrreg_prcm_bits(reg, mask, reg_val);
return 0;
}
/**
* codec_wrreg_bits - update codec register bits
* @reg: codec register
* @mask: register mask
* @value: new value
* Writes new register value.
* Return 1 for change else 0.
*/
int codec_wrreg_bits(unsigned short reg, unsigned int mask, unsigned int value)
{
unsigned int old, new;
old = codec_rdreg(reg);
new = (old & ~mask) | value;
codec_wrreg(reg,new);
return 0;
}
/**
* snd_codec_info_volsw - single mixer info callback
* @kcontrol: mixer control
* @uinfo: control element information
* Callback to provide information about a single mixer control
* Returns 0 for success
*/
int snd_codec_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct codec_mixer_control *mc = (struct codec_mixer_control*)kcontrol->private_value;
int max = mc->max;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
if (max == 1)
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;//the info of type
else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = shift == rshift ? 1: 2; //the info of elem count
uinfo->value.integer.min = 0; //the info of min value
uinfo->value.integer.max = max; //the info of max value
return 0;
}
/**
* snd_codec_get_volsw - single mixer get callback
* @kcontrol: mixer control
* @ucontrol: control element information
* Callback to get the value of a single mixer control
* return 0 for success.
*/
int snd_codec_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct codec_mixer_control *mc= (struct codec_mixer_control*)kcontrol->private_value;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
/*fls(7) = 3,fls(1)=1,fls(0)=0,fls(15)=4,fls(3)=2,fls(23)=5*/
unsigned int mask = (1 << fls(max)) -1;
unsigned int invert = mc->invert;
unsigned int reg = mc->reg;
ucontrol->value.integer.value[0] =
(read_prcm_wvalue(reg)>> shift) & mask;
if (shift != rshift)
ucontrol->value.integer.value[1] =
(read_prcm_wvalue(reg) >> rshift) & mask;
if (invert) {
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if(shift != rshift)
ucontrol->value.integer.value[1] =
max - ucontrol->value.integer.value[1];
}
return 0;
}
/**
* snd_codec_put_volsw - single mixer put callback
* @kcontrol: mixer control
* @ucontrol: control element information
* Callback to put the value of a single mixer control
* return 0 for success.
*/
int snd_codec_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct codec_mixer_control *mc= (struct codec_mixer_control*)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
unsigned int mask = (1<invert;
unsigned int val, val2, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
if(invert)
val = max - val;
val <<= shift;
val_mask = mask << shift;
if(shift != rshift){
val2 = (ucontrol->value.integer.value[1] & mask);
if(invert)
val2 = max - val2;
val_mask |= mask <private_value;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
/*fls(7) = 3,fls(1)=1,fls(0)=0,fls(15)=4,fls(3)=2,fls(23)=5*/
unsigned int mask = (1 << fls(max)) -1;
unsigned int invert = mc->invert;
unsigned int reg = mc->reg;
ucontrol->value.integer.value[0] =
(codec_rdreg(reg)>> shift) & mask;
if (shift != rshift)
ucontrol->value.integer.value[1] =
(codec_rdreg(reg) >> rshift) & mask;
if (invert) {
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if(shift != rshift)
ucontrol->value.integer.value[1] =
max - ucontrol->value.integer.value[1];
}
return 0;
}
/**
* snd_codec_put_volsw_digital - single mixer put callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Callback to put the value of a single mixer control
*
* return 0 for success.
*/
int snd_codec_put_volsw_digital(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct codec_mixer_control *mc= (struct codec_mixer_control*)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
unsigned int mask = (1<invert;
unsigned int val, val2, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
if(invert)
val = max - val;
val <<= shift;
val_mask = mask << shift;
if(shift != rshift){
val2 = (ucontrol->value.integer.value[1] & mask);
if(invert)
val2 = max - val2;
val_mask |= mask <value.integer.value[0];
if (codec_speaker_headset_earpiece_en == 1) {
ret = codec_pa_play_open();
} else if (codec_speaker_headset_earpiece_en == 0) {
ret = codec_headphone_play_open();
} else if (codec_speaker_headset_earpiece_en == 2) {
ret = codec_pa_and_headset_play_open();
} else if (codec_speaker_headset_earpiece_en == 3) {
ret = codec_earpiece_play_open();
}else if(codec_speaker_headset_earpiece_en == 4) {
pr_debug("%s,line:%d
",__func__,__LINE__);
ret = codec_system_btout_open();
}else if(codec_speaker_headset_earpiece_en == 5){
ret = codec_system_bt_buttonvoice_open();
}
return 0;
}
static int codec_get_spk_headset_earpiece(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = codec_speaker_headset_earpiece_en;
return 0;
}
static int sndpcm_unmute(struct snd_soc_dai *dai, int mute)
{
if (current_running == 0) {/*play stream*/
int i = 0;
int reg_val = 0;
if(codec_analog_phonein_en){
msleep(10);
codec_wr_prcm_control(ROMIXSC, 0x1, RMIXMUTEDACL, 0x1);
codec_wr_prcm_control(LOMIXSC, 0x1, LMIXMUTEDACL, 0x1);
}
if(mute == 0) {
switch (codec_speaker_headset_earpiece_en) {
case 0:
pr_debug("%s,line:%d
",__func__,__LINE__);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LHPPAMUTE, 0x1);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, RHPPAMUTE, 0x1);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if (!reg_val) {
for(i=0; i < headphone_vol; i++) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if ((i%2==0))
usleep_range(1000,2000);
}
}
break;
case 1:
pr_debug("%s,line:%d
",__func__,__LINE__);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if (!reg_val) {
for(i=0; i < pa_vol; i++) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if ((i%2==0))
usleep_range(1000,2000);
}
}
usleep_range(2000, 3000);
gpio_set_value(item.gpio.gpio, 1);
msleep(62);
break;
case 2:
pr_debug("%s,line:%d
",__func__,__LINE__);
if (!pa_double_used) {/*single speaker*/
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LHPPAMUTE, 0x1);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, RHPPAMUTE, 0x0);
} else {/*double speaker*/
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LHPPAMUTE, 0x1);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, RHPPAMUTE, 0x1);
}
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if (!reg_val) {
for(i=0; i < pa_vol; i++) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if ((i%2==0))
usleep_range(1000,2000);
}
}
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, pa_vol);
usleep_range(2000, 3000);
gpio_set_value(item.gpio.gpio, 1);
msleep(62);
break;
case 3:
pr_debug("%s,line:%d
",__func__,__LINE__);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LHPPAMUTE, 0x1);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if (!reg_val) {
for(i=0; i < earpiece_vol; i++) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
reg_val = read_prcm_wvalue(HP_VOLC);
reg_val &= 0x3f;
if ((i%2==0))
usleep_range(1000,2000);
}
}
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, earpiece_vol);
break;
default:
break;
}
} else {
}
}
return 0;
}
static int sndpcm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
/*enble aif1 clk */
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, AIF1CLK_ENA, 0x1);
/*enble sys clk */
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, SYSCLK_ENA, 0x1);
/**for test loopback******/
//codec_wr_control(SUNXI_DA_CTL , 0x1, 3, 0x1);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
play_running = 1;
current_running = 0;
/*enable module AIF1,DAC*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, AIF1_MOD_CLK_EN, 0x1);
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, DAC_DIGITAL_MOD_CLK_EN, 0x1);
/*reset module AIF1, DAC*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, AIF1_MOD_RST_CTL, 0x1);
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, DAC_DIGITAL_MOD_RST_CTL, 0x1);
} else {
current_running = 1;
/*enable module AIF1,ADC*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, AIF1_MOD_CLK_EN, 0x1);
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, ADC_DIGITAL_MOD_CLK_EN, 0x1);
/*reset module AIF1, ADC*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, AIF1_MOD_RST_CTL, 0x1);
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, ADC_DIGITAL_MOD_RST_CTL, 0x1);
}
/*global enable*/
codec_wr_control(SUNXI_DA_CTL , 0x1, GEN, 0x1);
return 0;
}
static void sndpcm_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int i = 0;
int cur_vol = 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
codec_wr_prcm_control(ADDA_APT2, 0x1, ZERO_CROSS_EN, 0x0);
cur_vol = read_prcm_wvalue(HP_VOLC);
cur_vol &= 0x3f;
if (!(codec_analog_mainmic_en || codec_analog_headsetmic_en ||codec_digital_headsetmic_en ||
codec_digital_mainmic_en ||codec_analog_btmic_en || codec_digital_btmic_en)){
gpio_set_value(item.gpio.gpio, 0);
if (cur_vol > 48) {
for (i = cur_vol; i > 52 ; i--) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
usleep_range(9000, 10000);
}
for (i = 52; i > 48 ; i--) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
usleep_range(6000,7000);
}
}
for (i = 48; i > 32 ; i--) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
usleep_range(1000,2000);
}
for (i = 32; i > 0 ; i--) {
/*set HPVOL volume*/
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, i);
usleep_range(100,200);
}
codec_wr_prcm_control(HP_VOLC, 0x3f, HPVOL, 0);
/*disable analog part*/
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LHPPAMUTE, 0x0);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, RHPPAMUTE, 0x0);
/*by xzd left select dacl&dacr*/
codec_wr_prcm_control(ROMIXSC, 0x7f, RMIXMUTE, 0x0);
/*by xzd right don't select src*/
codec_wr_prcm_control(LOMIXSC, 0x7f, LMIXMUTE, 0x0);
/*enable output mixer */
codec_wr_prcm_control(DAC_PA_SRC, 0x1, LMIXEN, 0x0);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, RMIXEN, 0x0);
codec_wr_prcm_control(PAEN_HP_CTRL, 0x1, LTRNMUTE, 0x0);
/*by xzd only right negative left*/
codec_wr_prcm_control(PAEN_HP_CTRL, 0x1, RTLNMUTE, 0x0);
/*enable dac analog*/
codec_wr_prcm_control(DAC_PA_SRC, 0x1, DACALEN, 0x0);
codec_wr_prcm_control(DAC_PA_SRC, 0x1, DACAREN, 0x0);
/*disable dac digital*/
codec_wr_control(SUNXI_DAC_DIG_CTRL, 0x1, ENDA, 0x0);
codec_wr_prcm_control(HP_VOLC, 0x1, PA_CLK_GC, 0x0);
/*confige dac digital mixer source */
codec_wr_control(SUNXI_DAC_MXR_SRC, 0xf, DACL_MXR_SRC, 0x0);
codec_wr_control(SUNXI_DAC_MXR_SRC, 0xf, DACR_MXR_SRC, 0x0);
/*confige AIF1 DAC Timeslot0 left channel data source*/
codec_wr_control(SUNXI_AIF1_DACDAT_CTRL, 0x3, AIF1_DA0L_SRC, 0);
/*confige AIF1 DAC Timeslot0 right channel data source*/
codec_wr_control(SUNXI_AIF1_DACDAT_CTRL, 0x3, AIF1_DA0R_SRC, 0);
/*disable AIF1 DAC Timeslot0 channel*/
codec_wr_control(SUNXI_AIF1_DACDAT_CTRL, 0x1, AIF1_DA0L_ENA, 0x0);
codec_wr_control(SUNXI_AIF1_DACDAT_CTRL, 0x1, AIF1_DA0R_ENA, 0x0);
/*diable clk parts*/
if (1 == cap_running ){
pr_debug("[audio stream] capture is running!!!!
");
}else{
/*disable aif1clk*/
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, AIF1CLK_ENA, 0);
/*disble sys clk */
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, SYSCLK_ENA, 0);
/*disable module AIF1*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, AIF1_MOD_CLK_EN, 0x0);
/*reset module AIF1*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, AIF1_MOD_RST_CTL, 0x0);
/*global disable*/
codec_wr_control(SUNXI_DA_CTL , 0x1, GEN, 0x0);
}
/*disable module DAC*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, DAC_DIGITAL_MOD_CLK_EN, 0x0);
/*reset module DAC*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, DAC_DIGITAL_MOD_RST_CTL, 0x0);
/* I2S0 TX DISABLE */
codec_wr_control(SUNXI_DA_CTL , 0x1, TXEN,0);
/*SDO ON*/
codec_wr_control(SUNXI_DA_CTL , 0x1, SDO_EN, 0);
}
if (drc_used){
codec_wr_control(0x4d4, 0xffff, 0, 0x0);
codec_wr_control(0x210, 0x1, 6, 0x0);
codec_wr_control(0x214, 0x1, 6, 0x0);
codec_wr_control(0x480, 0x7, 0, 0x0);
}
play_running = 0;
}else{
if (!(codec_analog_mainmic_en || codec_analog_headsetmic_en ||codec_digital_headsetmic_en ||
codec_digital_mainmic_en ||codec_analog_btmic_en || codec_digital_btmic_en)){
codec_wr_prcm_control(MIC1G_MICBIAS_CTRL, 0x1, MIC1AMPEN, 0x0);
/*disable Master microphone bias*/
codec_wr_prcm_control(MIC1G_MICBIAS_CTRL, 0x1, MMICBIASEN, 0x0);
codec_wr_prcm_control(MIC2G_LINEEN_CTRL, 0x1, MIC2AMPEN, 0x0);
/*disable Left MIC1 Boost stage*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEMIC1BOOST, 0x0);
/*disable Right MIC1 Boost stage*/
codec_wr_prcm_control(RADCMIXSC, 0x1, RADCMIXMUTEMIC1BOOST, 0x0);
/*disable Left MIC1 Boost stage*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEMIC2BOOST, 0x0);
/*disable Right MIC1 Boost stage*/
codec_wr_prcm_control(RADCMIXSC, 0x1, RADCMIXMUTEMIC2BOOST, 0x0);
/*disable PHONEP-PHONEN Boost stage*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEPHONEPN, 0x0);
/*disable PHONEP-PHONEN Boost stage*/
codec_wr_prcm_control(RADCMIXSC, 0x1, RADCMIXMUTEPHONEPN, 0x0);
/*disable LINEINL ADC*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTELINEINL, 0x0);
/*disable LINEINR ADC*/
codec_wr_prcm_control(RADCMIXSC, 0x1, RADCMIXMUTELINEINR, 0x0);
/*disable adc_r adc_l analog*/
codec_wr_prcm_control(ADC_AP_EN, 0x1, ADCREN, 0x0);
codec_wr_prcm_control(ADC_AP_EN, 0x1, ADCLEN, 0x0);
/*disable AIF1 adc Timeslot0 channel*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x1, AIF1_AD0L_ENA, 0x0);
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x1, AIF1_AD0R_ENA, 0x0);
/*confige AIF1 Digital Mixer Source*/
codec_wr_control(SUNXI_AIF1_MXR_SRC, 0xf, AIF1_AD0L_MXL_SRC, 0x0);
codec_wr_control(SUNXI_AIF1_MXR_SRC, 0xf, AIF1_AD0R_MXR_SRC, 0x0);
/*disable ADC Digital part*/
codec_wr_control(SUNXI_ADC_DIG_CTRL, 0x1, ENAD, 0);
/*diable clk parts*/
if (play_running == 1 ) {
pr_debug("[audio stream]playback is running!!!!
");
} else {
/*disable aif1clk*/
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, AIF1CLK_ENA, 0);
/*disble sys clk */
codec_wr_control(SUNXI_SYSCLK_CTL , 0x1, SYSCLK_ENA, 0);
/*disable module AIF1*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, AIF1_MOD_CLK_EN, 0x0);
/*reset module AIF1*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, AIF1_MOD_RST_CTL, 0x0);
/*global disable*/
codec_wr_control(SUNXI_DA_CTL , 0x1, GEN, 0x0);
}
/*disable module ADC*/
codec_wr_control(SUNXI_MOD_CLK_ENA , 0x1, ADC_DIGITAL_MOD_CLK_EN, 0x0);
/*reset module ADC*/
codec_wr_control(SUNXI_MOD_RST_CTL , 0x1, ADC_DIGITAL_MOD_RST_CTL, 0x0);
}
/* I2S0 RX DISABLE */
codec_wr_control(SUNXI_DA_CTL , 0x1, RXEN, 0);
if (agc_used) {
codec_wr_control(0x210, 0x1, 7, 0x0);
codec_wr_control(0x214, 0x1, 7, 0x0);
codec_wr_control(0x408, 0xf, 0, 0x0);
codec_wr_control(0x408, 0x7, 12, 0x0);
codec_wr_control(0x40c, 0xf, 0, 0x0);
codec_wr_control(0x40c, 0x7, 12, 0x0);
}
cap_running = 0;
}
}
static int sndpcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return 0;
}
/*
* phone record from main mic + phone in(digital bb) .
* or
* phone record from sub mic + phone in(digital bb) .
* mic1 uses as main mic. mic2 uses as sub mic
*/
static int codec_digital_voice_mic_bb_capture_open(void)
{
/*select phonein source */
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_AIF2DACL, 1);
/*select mic source*/
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_ADCL, 1);
/*aif1 adc left channel source select */
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x3, AIF1_AD0L_SRC, 0);
/*aif1 adc left channel enable */
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x1, AIF1_AD0L_ENA, 1);
msleep(200);
return 0;
}
static int codec_digital_voice_bb_bt_capture_open(void)
{
/*select AIF1 input mixer source */
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_AIF2DACR, 1);
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_AIF2DACL, 1);
/*AIF1 ADC Timeslot0 left channel data source select*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x3, AIF1_AD0L_SRC, 0);
/*open ADC channel slot0 switch*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x1, AIF1_AD0L_ENA, 1);
msleep(200);
return 0;
}
static int codec_analog_voice_bb_bt_capture_open(void)
{
/*select AIF1 input mixer source */
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0R_MXR_SRC_AIF2DACL, 1);
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0R_MXR_SRC_ADCR, 1);
/*AIF1 ADC Timeslot0 left channel data source select*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x3, AIF1_AD0L_SRC, 1);
/*open ADC channel slot0 switch*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x1, AIF1_AD0L_ENA, 1);
msleep(200);
return 0;
}
/*
* use for phone record from main/headset mic + phone in.
* .
*/
static int codec_analog_voice_capture_open(void)
{
if (codec_analog_mainmic_en) {
/*select mic source*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEMIC1BOOST, 0x1);
} else {
/*select mic2 source*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEMIC2BOOST, 0x1);
}
/*select phonein source*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTEPHONEPN, 0x1);
/*enable adc analog*/
codec_wr_prcm_control(ADC_AP_EN, 0x1, ADCLEN, 0x1);
/*enable dac digital*/
codec_wr_control(SUNXI_ADC_DIG_CTRL , 0x1, ENAD, 1);
codec_wr_control(SUNXI_ADC_DIG_CTRL , 0x1, ENDM, 0);
/*select aif1 adc left channel mixer source */
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_ADCL,1);
/*select aif1 adc left channel source */
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x3, AIF1_AD0L_SRC,0);
/*enable aif1 adc left channel */
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x1, AIF1_AD0L_ENA,1);
msleep(200);
return 0;
}
/*
* use for the line_in record
*/
static int codec_voice_linein_capture_open(void)
{
/*enable AIF1 adc Timeslot0 channel enable*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x1, AIF1_AD0L_ENA, 0x1);
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x1, AIF1_AD0R_ENA, 0x1);
/*confige AIF1 adc Timeslot0 left channel data source*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x3, AIF1_AD0L_SRC, 0);
/*confige AIF1 adc Timeslot0 right channel data source*/
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL, 0x3, AIF1_AD0R_SRC, 0);
/*confige AIF1 Digital Mixer Source*/
codec_wr_control(SUNXI_AIF1_MXR_SRC, 0xf, AIF1_AD0L_MXL_SRC, 0x2);
codec_wr_control(SUNXI_AIF1_MXR_SRC, 0xf, AIF1_AD0R_MXR_SRC, 0x2);
/*enable dac digital*/
codec_wr_control(SUNXI_ADC_DIG_CTRL , 0x1, ENAD, 1);
codec_wr_control(SUNXI_ADC_DIG_CTRL , 0x1, ENDM, 0);
/*enable adc_r adc_l analog*/
codec_wr_prcm_control(ADC_AP_EN, 0x1, ADCREN, 0x1);
codec_wr_prcm_control(ADC_AP_EN, 0x1, ADCLEN, 0x1);
/*enable Right MIC2 Boost stage*/
codec_wr_prcm_control(RADCMIXSC, 0x1, RADCMIXMUTELINEINR, 0x1);
/*enable Left MIC2 Boost stage*/
codec_wr_prcm_control(LADCMIXSC, 0x1, LADCMIXMUTELINEINL, 0x1);
msleep(200);
return 0;
}
static int codec_system_bt_capture_open(void)
{
/*config clk fmt*/
/*config aif2 from pll2*/
codec_wr_control(SUNXI_SYSCLK_CTL, 0x1, AIF2CLK_ENA, 0x1);
/*aif2 clk source select*/
codec_wr_control(SUNXI_SYSCLK_CTL, 0x3, AIF2CLK_SRC, 0x3);
codec_wr_control(SUNXI_MOD_CLK_ENA, 0x1, AIF2_MOD_CLK_EN, 0x1);
codec_wr_control(SUNXI_MOD_RST_CTL, 0x1, AIF2_MOD_RST_CTL, 0x1);
/*config aif2 fmt :pcm mono 16 lrck=8k,blck/lrck = 64*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x1, AIF2_MSTR_MOD, 0x0);/*master*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x1, AIF2_BCLK_INV, 0x0);/*bclk:normal*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x1, AIF2_LRCK_INV, 0x0);/*lrck:normal*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0xf, AIF2_BCLK_DIV, 0x9);/*aif2/bclk=48*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x7, AIF2_LRCK_DIV, 0x2);/*bclk/lrck=64*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x3, AIF2_WORD_SIZ, 0x1);/*sr=16*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x3, AIF2_DATA_FMT, 0x3);/*dsp mode*/
codec_wr_control(SUNXI_AIF2_CLK_CTRL, 0x1, AIF2_MONO_PCM, 1);/*dsp mode*/
/*aif3 mode enable*/
codec_wr_control(SUNXI_MOD_CLK_ENA, 0x1, AIF3_MOD_CLK_EN, 0x1);
codec_wr_control(SUNXI_MOD_RST_CTL, 0x1, AIF3_MOD_RST_CTL, 0x1);
/*config aif3: clk ,fmt*/
codec_wr_control(SUNXI_AIF3_CLK_CTRL, 0x1, AIF3_BCLK_INV, 0x0);
codec_wr_control(SUNXI_AIF3_CLK_CTRL, 0x1, AIF3_LRCK_INV, 0x0);
codec_wr_control(SUNXI_AIF3_CLK_CTRL, 0x3, AIF3_WORD_SIZ, 0x1);/*sr = 16*/
codec_wr_control(SUNXI_AIF3_CLK_CTRL, 0x3, AIF3_CLOC_SRC, 0x1);/*clk form aif2*/
/*select aif2 dac input source*/
codec_wr_control(SUNXI_AIF3_SGP_CTRL, 0x3, AIF2_DAC_SRC, 1);
/*aif2 adc right channel enable*/
codec_wr_control(SUNXI_AIF2_ADCDAT_CTRL, 0x1, AIF2_ADCR_EN,1);
/*aif2 adc right channel enable*/
//codec_wr_control(SUNXI_AIF2_MXR_SRC, 0x1, AIF2_ADCL_MXR_SRC_AIF2DACR,1);
/*select bt source*/
codec_wr_control(SUNXI_AIF1_MXR_SRC , 0x1, AIF1_AD0L_MXL_SRC_AIF2DACL, 1);
/*aif1 adc left channel source select */
codec_wr_control(SUNXI_AIF1_ADCDAT_CTRL , 0x3, AIF1_AD0L_SRC, 0);
/*aif1 adc left channel ena