全部博文(82)
分类: LINUX
2013-02-28 14:09:33
WM9714的耳机跟喇叭通道切换由寄存器软件控制,跟之前电视里面插入耳机让功放直接拉低关掉外音的方式有点差别。通过设置WM9714的0X1C寄存器的值来切换,其中设置耳机时为0X00A0,设置喇叭时为0X1200。
电路中有外部上拉固定EINT11为高电平,通过插入耳机将这个外中断EINT11拉低来触发中断,再进行WM9714的寄存器控制。首先在WM9713.C中wm9713_soc_probe()中,添加初始化通道设置和注册中断。
#define HdPhone_EintPin IRQ_EINT(11)
#define HdPhone_GpioPin S3C64XX_GPN(11)
#define OUTPUT_VALUE_HP (0x00a0)
#define OUTPUT_VALUE_SPK (0x1200)
static int wm9713_soc_probe(struct platform_device *pdev)
{
......
s3c_gpio_cfgpin(HdPhone_GpioPin, S3C_GPIO_SFN(2)); //配置GPIO11为外中断
s3c_gpio_setpull(HdPhone_GpioPin, S3C_GPIO_PULL_NONE); //不上拉也不下拉,硬件默认拉高
set_irq_type(HdPhone_EintPin, IRQ_TYPE_EDGE_BOTH); //插入跟拔出耳机都会触发中断
if(readl(S3C64XX_GPNDAT)&(1<<11)) //如果上电默认是喇叭,设置通道;否则设置耳机
{
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_SPK);
}
else
{
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_HP);
}
ret = request_irq(HdPhone_EintPin, s3c6400_9713HdSp_irq, //给EINT11注册中断和中断处理函数,参数包括中断方式,
IRQF_DISABLED, "HdphonSpeak", NULL); //中断名
.........
irq_err:
free_irq(HdPhone_EintPin,NULL); //释放中断
.........
}
中断handler如下:
static irqreturn_t s3c6400_9713HdSp_irq(int irq, void *dev_id)
{
struct snd_soc_codec *codec = wm9713_dai->codec; //获取系统的codec
writel(readl(S3C64XX_EINT0MASK)|(1<<11),S3C64XX_EINT0MASK); //disable EINT0 Interrupt before deal it
writel(1<<11,S3C64XX_EINT0PEND); //disable Interrupt
if(readl(S3C64XX_GPNDAT)&(1<<11)) //如果是喇叭,否则是耳机
{
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_SPK);
}
else
{
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_HP);
}
writel(readl(S3C64XX_EINT0MASK)&(~(1<<11)),S3C64XX_EINT0MASK); //Enable Interrupt after deal it
return IRQ_HANDLED;
}
以上即OK,在实际中还发现一个问题,插拔耳机切通道一直正常,但是第一次开机和仅仅用喇叭播放时喇叭不出声,也就是说一直需要耳机触发后喇叭跟耳机才会出声?通过测试波形,发现有耳机输入波形跟切换正常;但是功放输入会没有波形,估计还是切换通道的问题,查找后原因在于播放声音时,Entered s3c6400_ac97_hifi_prepare()中,判断SNDRV_PCM_STREAM_PLAYBACK会将0X1C寄存器写成0XAA,把喇叭所需的0X1200覆盖,屏蔽这句即可。可通过在s3c6400_ac97_trigger()中,设置打印语句:
for (temp =0x00;temp<=0x7e;temp+=0x02)
s3c6400_ac97_read(0,temp);
将CMD=1开始播放音频时9714的寄存器值全部读出,作出判断依据。
==========================================================================================================================
但是以上的做法是在中断处理函数中添加了过多的操作,直接带来的影响是开机重启时出现直指耳机喇叭切换中断处理函数的堆栈错误,所以同事的方法是这样做的,通过添加工作队列来执行。
(1)首先定义一个工作任务
static void wm9713_work(struct work_struct *work)
{
struct snd_soc_device *socdev = container_of(work, struct snd_soc_device, work);
struct snd_soc_codec *codec = wm9713_dai->codec;
writel(readl(S3C64XX_EINT0MASK)|(1<<11),S3C64XX_EINT0MASK); //disable Interrupt
writel(1<<11,S3C64XX_EINT0PEND); //disable Interrupt
if(readl(S3C64XX_GPNDAT)&(1<<11))
{
printk("OUTPUT_VALUE_SPK/n");
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_SPK);
}
else
{
printk("OUTPUT_VALUE_HP/n");
ac97_write(codec, AC97_REC_GAIN, OUTPUT_VALUE_HP);
}
writel(readl(S3C64XX_EINT0MASK)&(~(1<<11)),S3C64XX_EINT0MASK); //Enable Interrupt
enable_irq(socdev->irq); //一旦任务启动,使能那个GPIO中断
}
为此需要对snd_soc_device这个结构体进行改进:添加了 int irq;struct work_struct work;两个结构体成员。
(2)定义关联的中断处理函数:
static irqreturn_t s3c6410_wm9713_HPDET_irq(int irq, void *handle)
{
struct snd_soc_device *socdev = handle;
disable_irq_nosync(irq);
schedule_work(&socdev->work); //中断发生后,开始这个任务
return IRQ_HANDLED;
}
(3)在wm9713_soc_probe()中添加:
INIT_WORK(&socdev->work, wm9713_work);这样做的目的是初始化,因为在就算没有中断,在第一次开机时也有检测到底有没有接喇叭还是耳机,所以这个任务一开始就要执行,只不过到后来中断发生后就再执行。
(4)注册中断处理过程:
ret = request_irq(socdev->irq, s3c6410_wm9713_HPDET_irq,
IRQF_DISABLED, dev_name(socdev->dev), socdev);
因为snd_soc_device 内建了中断号,所以完全用一个结构体就可以把中断,任务,设备名等全部囊括进来。