硬件电路分析:
- 框架:
- 分析:
由于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写寄存器来实现。
对于此项目,考虑在插入事件上报时,通过中断来实现切换。
驱动代码分析:
- 定位文件:
查看DTS文件,通过compatible定位驱动文件为wm8524.c
wm8524: audio-codec {
#sound-dai-cells = <0>;
compatible = "wlf,wm8524";
wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
};
- 分析:
平台总线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;
}
耳机检测功能添加
- 耳机检测部分:要在正确的位置添加中断操作功能:
对比其他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接口,只是上报的键值有区别
以上,中断功能添加完毕
- 下面修改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>;
};
};
- 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" },
};
调试问题总结:
- 上层对耳机事件无反应:
确认上层接收上报信息的方式,否则不能正确接收到耳机事件:
参考: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,上层即可接收
- HAL层无法找到支持耳机的声卡:
在device/fsl/common/audio-json$ vi wm8524_config.json中强制8524支持耳机输出: