Jasonangel

V1

2022/03/28阅读:47主题:默认主题

RK 耳机方案

RK 有线耳机方案

1、硬件

  1. HPOL、HPOR 耳机左右声道。
  2. PHONE_DET 检测耳机是否插入。
  3. MIC_IN2P、MIC_IN2N MIC输入。
  4. I2C 音频控制线
  5. I2S 5根线:两根音频数据线、三根时钟线

I2S_LRCK 是指示当前数据线传输的是左声道还是右声道

I2S_BCLK 是协调数据线上的数据传输(一个时钟周期,主控、音频编解码芯片从音频线取 1 bit 音频数据)

I2S_MCLK 是主控供给音频编解码芯片保持正常工作用的时钟

I2S(Inter IC Sound)总线, 又称集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真。

一般来说,调试音频需要注意几个参数:采样率、采样位数、采样通道。

2、软件

耳机检测驱动:/kernel/drivers/headset_observe/...

rockchip_headset_core.c:读取 dts 中的配置,根据配置不同决定使用adc还是普通的 headset 探测。

rk_headset.c:普通方式headset驱动

rk_headset_irq_hook_adc.c:adc方式headset驱动

声卡驱动:/kernel/sound/soc/codec/rt5651.c

设备树耳机检测节点

//RK3399_kernel4.4 adc检测模式:
 rk_headset {
  compatible = "rockchip_headset";
  headset_gpio = <&gpio4 RK_PD4 GPIO_ACTIVE_LOW>;
  pinctrl-names = "default";
  pinctrl-0 = <&hp_det>;
  io-channels = <&saradc 2>;
 };
  
  &saradc {
   status = "okay";
  };

  headphone {
  hp_det: hp-det {
   rockchip,pins = <4 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>;
  };

设备树声卡(音频)ALC5651节点

 rt5651-sound {
       compatible = "simple-audio-card";
       simple-audio-card,format = "i2s";
       simple-audio-card,name = "realtek,rt5651-codec";
       simple-audio-card,mclk-fs = <256>;
       simple-audio-card,widgets =
                 "Microphone""Mic Jack",
                 "Headphone""Headphone Jack";
       simple-audio-card,routing =
                 "Mic Jack""MICBIAS1",
                 "IN1P""Mic Jack",
                 "Headphone Jack""HPOL",
                 "Headphone Jack""HPOR";
       simple-audio-card,cpu {
                  sound-dai = <&i2s0>;
         };
       simple-audio-card,codec {
               sound-dai = <&rt5651>;
       };
    };

rockchip_headset_core.c 核心驱动

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/debugfs.h>
#include "rk_headset.h"
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/iio/consumer.h>

/* Debug */
#if 0
#define DBG(x...) printk(x)
#else
#define DBG(x...) do { } while (0)
#endif

struct rk_headset_pdata *pdata_info;

static int rockchip_headset_probe(struct platform_device *pdev)
{
 struct device_node *node = pdev->dev.of_node;
 struct rk_headset_pdata *pdata;
 int ret;
 enum of_gpio_flags flags;

 pdata = kzalloc(sizeof(struct rk_headset_pdata), GFP_KERNEL);
 if (pdata == NULL) {
  printk("%s failed to allocate driver data\n",__FUNCTION__);
  return -ENOMEM;
 }
 memset(pdata,0,sizeof(struct rk_headset_pdata));
 pdata_info = pdata;

 //headset
 ret = of_get_named_gpio_flags(node, "headset_gpio"0, &flags);
 if (ret < 0) {
  printk("%s() Can not read property headset_gpio\n", __FUNCTION__);
  goto err;
 } else {
  pdata->headset_gpio = ret;
  ret = devm_gpio_request(&pdev->dev, pdata->headset_gpio, "headset_gpio");
  if(ret < 0){
   printk("%s() devm_gpio_request headset_gpio request ERROR\n", __FUNCTION__);
   goto err;
  }

  ret = gpio_direction_input(pdata->headset_gpio); 
  if(ret < 0){
   printk("%s() gpio_direction_input headset_gpio set ERROR\n", __FUNCTION__);
   goto err;
  }

  pdata->headset_insert_type = (flags & OF_GPIO_ACTIVE_LOW) ? HEADSET_IN_LOW : HEADSET_IN_HIGH;
 }

 //hook
 ret = of_get_named_gpio_flags(node, "hook_gpio"0, &pdata->hook_gpio);
 if (ret < 0) {
  DBG("%s() Can not read property hook_gpio\n", __FUNCTION__);
  pdata->hook_gpio = 0;
  //adc mode
  pdata->chan = iio_channel_get(&pdev->dev, NULL);
     if (IS_ERR(pdata->chan))
          {
   pdata->chan = NULL;
   printk("%s() have not set adc chan\n", __FUNCTION__);
  }
 } else {
  ret = of_property_read_u32(node, "hook_down_type", &pdata->hook_down_type);
  if (ret < 0) {
   DBG("%s() have not set hook_down_type,set >hook< insert type low level default\n", __FUNCTION__);
   pdata->hook_down_type = 0;
  }
  ret = devm_gpio_request(&pdev->dev, pdata->hook_gpio, "hook_gpio");
  if(ret < 0){
   printk("%s() devm_gpio_request hook_gpio request ERROR\n", __FUNCTION__);
   goto err;
  }
  ret = gpio_direction_input(pdata->hook_gpio); 
  if(ret < 0){
   printk("%s() gpio_direction_input hook_gpio set ERROR\n", __FUNCTION__);
   goto err;
  }
 }

 #ifdef CONFIG_MODEM_MIC_SWITCH
 //mic
 ret = of_get_named_gpio_flags(node, "mic_switch_gpio"0, &flags);
 if (ret < 0) {
  DBG("%s() Can not read property mic_switch_gpio\n", __FUNCTION__);
 } else {
  pdata->headset_gpio = ret;
  ret = of_property_read_u32(node, "hp_mic_io_value", &pdata->hp_mic_io_value);
  if (ret < 0) {
   DBG("%s() have not set hp_mic_io_value ,so default set pull down low level\n", __FUNCTION__);
   pdata->hp_mic_io_value = 0;
  }
  ret = of_property_read_u32(node, "main_mic_io_value", &pdata->main_mic_io_value);
  if (ret < 0) {
   DBG("%s() have not set main_mic_io_value ,so default set pull down low level\n", __FUNCTION__);
   pdata->main_mic_io_value = 1;
  }
 }
 #endif

 ret = of_property_read_u32(node, "rockchip,headset_wakeup", &pdata->headset_wakeup);
 if (ret < 0)
  pdata->headset_wakeup = 1;

 if(pdata->chan != NULL)
 {//hook adc mode
  printk("%s() headset have hook adc mode\n",__FUNCTION__);
  ret = rk_headset_adc_probe(pdev,pdata);
  if(ret < 0)
  {
   goto err;
  } 

 }
 else
 {//hook interrupt mode and not hook
  printk("%s() headset have %s mode\n",__FUNCTION__,pdata->hook_gpio?"interrupt hook":"no hook");
  ret = rk_headset_probe(pdev,pdata);
  if(ret < 0)
  {
   goto err;
  }
 }

 return 0;
err:
 kfree(pdata);
 return ret;
}

static int rockchip_headset_remove(struct platform_device *pdev)
{
 if(pdata_info)
  kfree(pdata_info);
 return 0;
}

static int rockchip_headset_suspend(struct platform_device *pdev, pm_message_t state)
{
 if(pdata_info->chan != 0)
 {
  return rk_headset_adc_suspend(pdev,state);
 }
 return 0;
}

static int rockchip_headset_resume(struct platform_device *pdev)
{
 if(pdata_info->chan != 0)
 {
  return rk_headset_adc_resume(pdev);
 } 
 return 0;
}

static const struct of_device_id rockchip_headset_of_match[] = {
        { .compatible = "rockchip_headset", },
        {},
};
MODULE_DEVICE_TABLE(of, rockchip_headset_of_match);

static struct platform_driver rockchip_headset_driver = {
 .probe = rockchip_headset_probe,
 .remove = rockchip_headset_remove,
 .resume =  rockchip_headset_resume, 
 .suspend =  rockchip_headset_suspend, 
 .driver = {
  .name = "rockchip_headset",
  .owner = THIS_MODULE,
  .of_match_table = of_match_ptr(rockchip_headset_of_match),  
 },
};

static int __init rockchip_headset_init(void)
{
 platform_driver_register(&rockchip_headset_driver);
 return 0;
}

static void __exit rockchip_headset_exit(void)
{
 platform_driver_unregister(&rockchip_headset_driver);
}
late_initcall(rockchip_headset_init);
MODULE_DESCRIPTION("Rockchip Headset Core Driver");
MODULE_LICENSE("GPL");

该驱动代码非常精简,只有四个函数:

rockchip_headset_probe
rockchip_headset_remove
rockchip_headset_suspend
rockchip_headset_resume

以及驱动加载和卸载函数

rockchip_headset_init
rockchip_headset_exit

该驱动框架是 platform 驱动框架。

检测流程

RK 公版 SDK 包含两种耳机检测处理:

Hook ADC 模式:硬件上 HOOK 直接接到 ADC ,根据 ADC 获取的电压值来区分三段四段耳机类型。流程框图:

Hook Interrupt 模式:硬件上 HOOK 直接接 RK 芯片端的一个 GPIO 口,通过中断的方式来区分三段四段耳机类型。流程框图:

注:受限于文章开头的耳机座子的结构,无法做耳机按键检测。

声卡

集成声卡是指芯片组支持整合的声卡类型,比较常见的是AC'97和HD Audio,使用集成声卡的芯片组的主板就可以在比较低的成本上实现声卡的完整功能。 声卡是一台 多媒体电脑 的主要设备之一,现在的声卡一般有板载声卡和 独立声卡 之分。

https://www.cnblogs.com/usxjsj115/p/13157592.html

VS1053B、WM8960、ALC5651等。

分类:

移动端开发

标签:

移动端开发

作者介绍

Jasonangel
V1