Android 10平台向wm8524添加耳机插入检测功能和分析

硬件电路分析:

  1. 框架:
    在这里插入图片描述
  2. 分析:
    由于wm8524 codec无MIC输入接口,所以只有输出的SAI_TX通道。而且8524只有Line out输出,并没有区分headphone和speaker,所以这里的mute逻辑是耳机插入后,产生Audio_det信号,主芯片接收后,变换Audio mute极性,切换AP。实际上的codec具备HP_OUT和SPEAK_OUT两种输出,mute只是在音频播放和休眠时,防止爆破音使用的。如wm8960,音频输出通路由android上层控制,通过I2C写寄存器来实现。
    对于此项目,考虑在插入事件上报时,通过中断来实现切换。

驱动代码分析:

  1. 定位文件:
    查看DTS文件,通过compatible定位驱动文件为wm8524.c
wm8524: audio-codec {
		#sound-dai-cells = <0>;
		compatible = "wlf,wm8524";
		wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
	};
  1. 分析:

平台总线driver,与device匹配后调用wm8524_codec_probe:

static struct platform_driver wm8524_codec_driver = {
	.probe		= wm8524_codec_probe,
	.driver		= {
		.name	= "wm8524-codec",
		.of_match_table = wm8524_of_match,
	},
};
module_platform_driver(wm8524_codec_driver);

snd_soc_component_driver结构体,ALSA框架的东西,匹配后调用,这里还需要看一下,里面定义了widgets和routes:
这里简单说一下widgets和routes的作用:
widget是DAPM控制的最小单元,route为各个不同widgets之间的连接方式,目的是为了音频能从起始地(source)到目的地(sink),最后DAPM系统自动生成PATH结构,明确音频传输路径

static const struct snd_soc_component_driver soc_component_dev_wm8524 = {
	.probe			= wm8524_probe,
	.dapm_widgets		= wm8524_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(wm8524_dapm_widgets),
	.dapm_routes		= wm8524_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(wm8524_dapm_routes),
	.idle_bias_on		= 1,
	.use_pmdown_time	= 1,
	.endianness		= 1,
	.non_legacy_dai_naming	= 1,
};

看一下snd_soc_dapm_widget和snd_soc_dapm_route结构体:
route的结构: {source name, control, sink name}; 这里表示音频路径由LINEVOUT到DAC

static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
};

static const struct snd_soc_dapm_route wm8524_dapm_routes[] = {
	{ "LINEVOUTL", NULL, "DAC" },
	{ "LINEVOUTR", NULL, "DAC" },
};

snd_soc_dai_driver结构体,同样是ALSA框架的东西,类似平台总线,定义了ops:

static struct snd_soc_dai_driver wm8524_dai = {
	.name = "wm8524-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = WM8524_RATES,
		.formats = WM8524_FORMATS,
	},
	.ops = &wm8524_dai_ops,
};

snd_soc_dai_ops结构体,定义了codec的操作函数:

static const struct snd_soc_dai_ops wm8524_dai_ops = {
	.startup	= wm8524_startup,
	.shutdown	= wm8524_shutdown,
	.set_sysclk	= wm8524_set_dai_sysclk,
	.set_fmt	= wm8524_set_fmt,
	.mute_stream	= wm8524_mute_stream,
};

wm8524_startup函数,每次播放声音时,如果设备休眠则会调用startup和mute函数:

static int wm8524_startup(struct snd_pcm_substream *substream,
			  struct snd_soc_dai *dai)
{
	struct snd_soc_component *component = dai->component;
	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	/* The set of sample rates that can be supported depends on the
	 * MCLK supplied to the CODEC - enforce this.
	 */
	if (!wm8524->sysclk) {
		dev_err(component->dev,
			"No MCLK configured, call set_sysclk() on init\n");
		return -EINVAL;
	}

	if (!rtd->dai_link->be_hw_params_fixup)
		snd_pcm_hw_constraint_list(substream->runtime, 0,
					   SNDRV_PCM_HW_PARAM_RATE,
					   &wm8524->rate_constraint);
	
	gpiod_set_value_cansleep(wm8524->mute, 1);
	return 0;
}

wm8524_shutdown函数,外放在休眠时会调用shutdown和mute完成防爆破音操作

static void wm8524_shutdown(struct snd_pcm_substream *substream,
			  struct snd_soc_dai *dai)
{
	struct snd_soc_component *component = dai->component;
	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);

	gpiod_set_value_cansleep(wm8524->mute, 0);
}

wm8524_mute_stream 防爆破音调用该函数

static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
{
	struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(dai->component);
	if (wm8524->mute)
		gpiod_set_value_cansleep(wm8524->mute, mute);
	return 0;
}

wm8524_set_fmt,DAI格式设置,如I2S等:

static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
	fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK |
		SND_SOC_DAIFMT_MASTER_MASK);

	if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
		    SND_SOC_DAIFMT_CBS_CFS)) {
		dev_err(codec_dai->dev, "Invalid DAI format\n");
		return -EINVAL;
	}
	return 0;
}

耳机检测功能添加

  1. 耳机检测部分:要在正确的位置添加中断操作功能:
    对比其他codec得HP_DET功能由machine控制,这里使用的是simple-audio-card,
    找到源码文件:sound/soc/generic/simple-card.c 查看probe:
static int simple_soc_probe(struct snd_soc_card *card)
{
	struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card);
	int ret;
	//下面是对耳机和mic的初始化,跟踪此函数:
	ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX);
	if (ret < 0){
		return ret;
	}
	ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX);
	if (ret < 0){
		return ret;
	}
	return 0;
}

include/sound/simple_card_utils.h:
这里都调用asoc_simple_init_jack,只是传入参数不一样,继续跟踪

#define asoc_simple_init_hp(card, sjack, prefix) \
	asoc_simple_init_jack(card, sjack, 1, prefix)
#define asoc_simple_init_mic(card, sjack, prefix) \
	asoc_simple_init_jack(card, sjack, 0, prefix)
	

sound/soc/generic/simple-card-utils.c:

int asoc_simple_init_jack(struct snd_soc_card *card,
			  struct asoc_simple_jack *sjack,
			  int is_hp, char *prefix)
{
	struct device *dev = card->dev;
	enum of_gpio_flags flags;
	char prop[128];
	char *pin_name;
	char *gpio_name;
	int mask;
	int det;
	mdelay(100);

	if (!prefix)
		prefix = "";
	sjack->gpio.gpio = -ENOENT;
	
//下面通过参数来判断传入的是hp还是mic
	if (is_hp) {
	//这里做了字符串拼接,所以DTS中要按照这样设置:simple-audio-card,hp-det-gpio
		snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix);
		//pin_name	= "Headphones";
		pin_name	= "Headphone Jack";	//为避免DTS中名称冲突,这里改名
		gpio_name	= "Headphone detection";
		mask		= SND_JACK_HEADPHONE;

	} else {
		snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix);
		pin_name	= "Mic Jack";
		gpio_name	= "Mic detection";
		mask		= SND_JACK_MICROPHONE;
	}

	det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags);
	if (det == -EPROBE_DEFER){
		return -EPROBE_DEFER;
	}

	if (gpio_is_valid(det)) {
		sjack->pin.pin		= pin_name;
		sjack->pin.mask		= mask;

		sjack->gpio.name	= gpio_name;
		sjack->gpio.report	= mask;
		sjack->gpio.gpio	= det;
		sjack->gpio.invert	= !!(flags & OF_GPIO_ACTIVE_LOW);
		sjack->gpio.debounce_time = 150;
		
	//拿到相关gpio信息后添加耳机信息:
		snd_soc_card_jack_new(card, pin_name, mask,
				      &sjack->jack,
				      &sjack->pin, 1);

		snd_soc_jack_add_gpios(&sjack->jack, 1,
				       &sjack->gpio);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(asoc_simple_init_jack);

sound/soc/soc-jack.c:对gpio的操作:

int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
			struct snd_soc_jack_gpio *gpios)
{
	int i, ret;
	struct jack_gpio_tbl *tbl;
	
	tbl = devres_alloc(jack_devres_free_gpios, sizeof(*tbl), GFP_KERNEL);
	if (!tbl)
		return -ENOMEM;
	tbl->jack = jack;
	tbl->count = count;
	tbl->gpios = gpios;

	for (i = 0; i < count; i++) {
		if (!gpios[i].name) {
			dev_err(jack->card->dev,
				"ASoC: No name for gpio at index %d\n", i);
			ret = -EINVAL;
			goto undo;
		}
		if (gpios[i].desc) {
			/* Already have a GPIO descriptor. */
			goto got_gpio;
		} else if (gpios[i].gpiod_dev) {
			/* Get a GPIO descriptor */
			gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev,
							gpios[i].name,
							gpios[i].idx, GPIOD_IN);
			if (IS_ERR(gpios[i].desc)) {
				ret = PTR_ERR(gpios[i].desc);
				dev_err(gpios[i].gpiod_dev,
					"ASoC: Cannot get gpio at index %d: %d",
					i, ret);
				goto undo;
			}
		} else {
			/* legacy GPIO number */
			if (!gpio_is_valid(gpios[i].gpio)) {
				dev_err(jack->card->dev,
					"ASoC: Invalid gpio %d\n",
					gpios[i].gpio);
				ret = -EINVAL;
				goto undo;
			}

			ret = gpio_request_one(gpios[i].gpio, GPIOF_IN,
					       gpios[i].name);
			if (ret){
				goto undo;
			}
			gpios[i].desc = gpio_to_desc(gpios[i].gpio);
		}
got_gpio:
		INIT_DELAYED_WORK(&gpios[i].work, gpio_work);
		gpios[i].jack = jack;
		
	//重要的在这里:插入耳机中断
		ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc),
					      gpio_handler,
					      IRQF_TRIGGER_RISING |
					      IRQF_TRIGGER_FALLING,
					      gpios[i].name,
					      &gpios[i]);
		if (ret < 0)
			goto err;

		if (gpios[i].wake) {
			ret = irq_set_irq_wake(gpiod_to_irq(gpios[i].desc), 1);
			if (ret != 0)
				dev_err(jack->card->dev,
					"ASoC: Failed to mark GPIO at index %d as wake source: %d\n",
					i, ret);
		}

		/*
		 * Register PM notifier so we do not miss state transitions
		 * happening while system is asleep.
		 */
		gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier;
		register_pm_notifier(&gpios[i].pm_notifier);

		/* Expose GPIO value over sysfs for diagnostic purposes */
		gpiod_export(gpios[i].desc, false);

		/* Update initial jack status */
		schedule_delayed_work(&gpios[i].work,
				      msecs_to_jiffies(gpios[i].debounce_time));	  
	}
	devres_add(jack->card->dev, tbl);
	return 0;
err:

	gpio_free(gpios[i].gpio);
undo:

	jack_free_gpios(jack, i, gpios);
	devres_free(tbl);

	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios);

irq handler函数:

static irqreturn_t gpio_handler(int irq, void *data)
{
	struct snd_soc_jack_gpio *gpio = data;
	struct device *dev = gpio->jack->card->dev;
	trace_snd_soc_jack_irq(gpio->name);

	if (device_may_wakeup(dev)){
		pm_wakeup_event(dev, gpio->debounce_time + 50);
	}
	//下面指定中断下半部:
	queue_delayed_work(system_power_efficient_wq, &gpio->work,
			      msecs_to_jiffies(gpio->debounce_time));
			      
	return IRQ_HANDLED;
}

中断下半部:

/* gpio work */	
static void gpio_work(struct work_struct *work)
{
	struct snd_soc_jack_gpio *gpio;

	gpio = container_of(work, struct snd_soc_jack_gpio, work.work);
	snd_soc_jack_gpio_detect(gpio);

}

snd_soc_jack_gpio_detect(): 在这里终于看到了调用snd_soc_jack_report向上层上报事件

/* gpio detect */
static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio)
{
	struct snd_soc_jack *jack = gpio->jack;
	int enable;
	int report;

	enable = gpiod_get_value_cansleep(gpio->desc);
	if (gpio->invert)
		enable = !enable;

	if (enable)
		report = gpio->report;
	else
		report = 0;

	if (gpio->jack_status_check)
		report = gpio->jack_status_check(gpio->data);

	snd_soc_jack_report(jack, report, gpio->report);
}

snd_soc_jack_report函数:在这里添加对gpio的操作:

void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
{
	struct snd_soc_dapm_context *dapm;
	struct snd_soc_jack_pin *pin;
	unsigned int sync = 0;
	int enable;
	if (!jack)
		return;
	trace_snd_soc_jack_report(jack, mask, status);

	dapm = &jack->card->dapm;

	mutex_lock(&jack->mutex);

	jack->status &= ~mask;
	jack->status |= status & mask;
	trace_snd_soc_jack_notify(jack, status);

	list_for_each_entry(pin, &jack->pins, list) {
		enable = pin->mask & jack->status;

		if (pin->invert)
			enable = !enable;

		if (enable){
			snd_soc_dapm_enable_pin(dapm, pin->pin);
		}
		else{
			snd_soc_dapm_disable_pin(dapm, pin->pin);
		}
		/* we need to sync for this case only */
		sync = 1;
	}

	/* Report before the DAPM sync to help users updating micbias status */
	blocking_notifier_call_chain(&jack->notifier, jack->status, jack);

	if (sync){
		snd_soc_dapm_sync(dapm);
	}
	//调用snd_jack_report上报耳机状态
	snd_jack_report(jack->jack, jack->status);

	//这里添加自己的gpio操作,将mute脚极性切换:
	int ret, temp;
	ret = gpio_request(8, "mute-gpio");
	if(!ret){
		printk("*****soc-jack.c %s-->gpio_request, at line %d\n", __FUNCTION__, __LINE__);
	}
	
	//将gpio1-8转化成gpio_desc结构体
	struct gpio_desc *desc = gpio_to_desc(8);	

	/* 将GPIO导出到export中,方便之后调试 */
	gpiod_export(desc, false);

/*判断耳机状态然后操作mute:
*	status为1表示耳机插入,要拉低mute来enable耳机的运放
*/
	if(jack->status == 1){
		msleep(300);	//防抖操作	
		if(jack->status == 1){
			gpiod_direction_output(desc, 1);
		}
	}

	if(jack->status == 0){
		struct gpio_desc *desc = gpio_to_desc(8);
		msleep(300);	//防抖操作	
		if(jack->status == 0){
			gpiod_direction_output(desc, 0);
		}
	}
	//add end
	mutex_unlock(&jack->mutex);
}
EXPORT_SYMBOL_GPL(snd_soc_jack_report);

继续跟踪会发现最终调用的是input_event接口,只是上报的键值有区别
以上,中断功能添加完毕

  1. 下面修改DTS来声明HP_DET脚:
sound-wm8524 {
		compatible = "simple-audio-card";
		simple-audio-card,name = "wm8524-audio";
		simple-audio-card,format = "i2s";
		simple-audio-card,frame-master = <&cpudai>;
		simple-audio-card,bitclock-master = <&cpudai>;
		simple-audio-card,widgets =
			"Line", "Left Line Out Jack",
			"Line", "Right Line Out Jack",
			"Headphone","Headphone Jack";	//添加
		simple-audio-card,routing =
			"Left Line Out Jack", "LINEVOUTL",
			"Right Line Out Jack", "LINEVOUTR",
			"Headphone Jack", "HP_L",	//添加
			"Headphone Jack", "HP_R";	//添加

		//	如下添加,NXP平台还需要对gpio进行设置:
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_hp_det>;
		simple-audio-card,hp-det-gpio = <&gpio5 12 GPIO_ACTIVE_HIGH>;
		//add end

		cpudai: simple-audio-card,cpu {
			sound-dai = <&sai2>;
			dai-tdm-slot-num = <2>;
			dai-tdm-slot-width = <32>;
		};

		link_codec: simple-audio-card,codec {
			sound-dai = <&wm8524>;
			clocks = <&clk IMX8MQ_CLK_SAI2_ROOT>;
		};
	};
  1. codec中:sound/soc/codecs/wm8524.c 添加耳机音频路径
//这里表示耳机与SPEAKER一样通过DAC输出
static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_HP("Headphone Jack", NULL),	//add by wei
SND_SOC_DAPM_OUTPUT("HP_L"),	//add by wei
SND_SOC_DAPM_OUTPUT("HP_R"),	//add by wei
SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
};

static const struct snd_soc_dapm_route wm8524_dapm_routes[] = {
	{ "LINEVOUTL", NULL, "DAC" },
	{ "LINEVOUTR", NULL, "DAC" },
	{ "HP_L", NULL, "DAC" },
	{ "HP_R", NULL, "DAC" },	
};

调试问题总结:

  1. 上层对耳机事件无反应:

确认上层接收上报信息的方式,否则不能正确接收到耳机事件:
参考:https://blog.csdn.net/weixin_43013761/article/details/89674511

有两种上报插拔事件的方式,一种是使用输入子系统,另外一种是使用swith dev(实质是使用uevent,通过网络上报事件),那么我们的anroid系统最终是使用哪种方式呢?我们可以去配置android系统,可以去配置SDK/frameworks/base/core/res/res/values/config.xml
文件,修改文件中的config_useDevInputEventForAudioJack变量,该值为true时使用input子系统, 为false时使用uevent机制。

<!-- When true use the linux /dev/input/event subsystem to detect the switch changes
         on the headphone/microphone jack. When false use the older uevent framework. changed to true by wei -->
    <bool name="config_useDevInputEventForAudioJack">true</bool>

由于底层使用的是input event上报,所以将这里改为true,上层即可接收

  1. HAL层无法找到支持耳机的声卡:
    在device/fsl/common/audio-json$ vi wm8524_config.json中强制8524支持耳机输出:
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值