首页 > 技术文章 > rk音频驱动分析之widget的上下电过程

wen123456 2020-11-26 16:39 原文

参考: https://blog.csdn.net/tronteng/article/details/7355977
 
当一个widget的状态改变后,该widget会被加入dapm_dirty链表,然后通过dapm_power_widgets函数来改变整个音频路径上的电源状态
 
一.dapm触发的情况
1、dapm widgets建立时,详见snd_soc_dapm_new_widgets;
 

2、上层通过alsa_amixer等工具改变codec音频路径时,此时与此相关的widgets状态要重置,详见dapm_mixer_update_power和dapm_mux_update_power;

  1. amixer-应用层[alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1]      
  2.   |->snd_ctl_ioctl-系统调用      
  3.        |->snd_ctl_elem_write_user-内核钩子函数      
  4.             |->snd_ctl_elem_wirte-      
  5.                  |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl      
  6.                  |->kctl->put()-调用kctl的成员函数put()      
  7.                       |->snd_soc_dapm_put_volsw    
  8.                            |->dapm_mixer_update_power    
  9.                                 |->更新path->connect状态  
  10.                                 |->dapm_power_widgets 触发dapm,重置相关的widgets  
 

3、发生stream事件时,会触发snd_soc_dapm_stream_even。什么叫stream事件?准备或关闭一个pcm stream通道(snd_pcm_prepare/snd_pcm_close)这些都属于stream事件。另外suspend或resume时,也会触发snd_soc_dapm_stream_event处理。

  1. snd_pcm_prepare  
  2.   |->soc_pcm_prepare  
  3.        |->处理platform、codec-dai、cpu-dai的prepare回调函数  
  4.        |->snd_soc_dapm_stream_event  
  5.             |->遍历codec每个dapm widget,如果该widget的stream name与传递进来的stream参数相匹配,如果匹配则置widget->active为真  
  6.             |->dapm_power_widgets 触发dapm,重置相关的widgets  
 
 
 
 
二.dapm_power_widgets分析

1、初始化两个链表up_list和down_list,如字面意思,up_list指向要power up的widgets,down_list指向要power down的widgets;

2、遍历所有widgets,检查是否需要对其进行power操作;要power up的则插入到up_list,要power down的则插入到down_list;

3、先power down down_list上widgets,再power up up_list上的widgets;

 
 /* Scan each dapm widget for complete audio path. //搜索dapm widget得到完整的路径
 * A complete path is a route that has valid endpoints i.e.:- //一个完整的路径,需要有有效的端点
 * //主要有下面几种有效的路径
 * o DAC to output pin. //DAC 输出
 * o Input Pin to ADC. //ADC输入
 * o Input pin to Output pin (bypass, sidetone) //输入直连输出
 * o DAC to ADC (loopback). //DAC 直接连接ADC,回路  */
static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
    struct snd_soc_dapm_widget *w;
    struct snd_soc_dapm_context *d;
    LIST_HEAD(up_list); //上电链表
    LIST_HEAD(down_list); //下电链表
    list_for_each_entry(d, &card->dapm_list, list)  //遍历所有的snd_soc_dapm_context,电源域
        //与SND_SOC_BIAS_OFF区别,SND_SOC_BIAS_STANDBY不能多于10ms.NOTE: The transition time between STANDBY and ON 
        // should be as fast as possible and no longer than 10ms.
        if (d->idle_bias_off) ///* Use BIAS_OFF instead of STANDBY */
            d->target_bias_level = SND_SOC_BIAS_OFF;
        else
            d->target_bias_level = SND_SOC_BIAS_STANDBY;
    dapm_reset(card); //单独分析1,遍历所有widgets,把他们的输入输出连接数都置-1,置未检查标志
    /* Check which widgets we need to power and store them in lists indicating if they should be powered up or down. We
    * only check widgets that have been flagged as dirty but note that new widgets may be added to the dirty list while we iterate. */
    //查看哪些widgets 需要改变电源,放在上下电链表。我们只检查有dirty标志位的widgets ,但是迭代的生活新建的widgets 也会在dirty链表
    list_for_each_entry(w, &card->dapm_dirty, dirty) //遍历
        dapm_power_one_widget(w, &up_list, &down_list); //单独分析2
    list_for_each_entry(w, &card->widgets, list)
        if (w->power)
            d = w->dapm;
            /* Supplies and micbiases only bring the context up to STANDBY as unless something else is active and passing audio they
            * generally don't require full power. Signal generators are virtual pins and have no  power impact themselves.*/
            case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply:
             case snd_soc_dapm_clock_supply: case snd_soc_dapm_micbias:
            if (d->target_bias_level < SND_SOC_BIAS_STANDBY) //如果小于SND_SOC_BIAS_STANDBY,都是置为SND_SOC_BIAS_STANDBY
                d->target_bias_level = SND_SOC_BIAS_STANDBY;
            default:
                d->target_bias_level = SND_SOC_BIAS_ON; //其他的类型就是SND_SOC_BIAS_ON;
    /* Force all contexts in the card to the same bias state if they're not ground referenced. */
    bias = SND_SOC_BIAS_OFF;
    list_for_each_entry(d, &card->dapm_list, list)
          if (d->target_bias_level > bias) //如果电源域大于off 
                bias = d->target_bias_level;
    list_for_each_entry(d, &card->dapm_list, list)
        if (!d->idle_bias_off) //如果/* Use BIAS_OFF instead of STANDBY */,不是这个状态
            d->target_bias_level = bias;
    /* Run all the bias changes in parallel */
    list_for_each_entry(d, &dapm->card->dapm_list, list) //每个电源域,异步执行预先的序列,单独分析6
        async_schedule_domain(dapm_pre_sequence_async, d, &async_domain); //主要是设置了codec的bias_level
    /* Power down widgets first; try to avoid amplifying pops. */
    dapm_seq_run(dapm, &down_list, event, false); //先执行down_list,单独分析7
    dapm_widget_update(dapm);
    /* Now power up. */
    dapm_seq_run(dapm, &up_list, event, true); //上电
    /* Run all the bias changes in parallel */
    list_for_each_entry(d, &dapm->card->dapm_list, list) //已经分析
        async_schedule_domain(dapm_post_sequence_async, d, &async_domain);
    /* do we need to notify any clients that DAPM event is complete */
    list_for_each_entry(d, &card->dapm_list, list)
        if (d->stream_event)
            d->stream_event(d, event);
    pop_wait(card->pop_time);
 
1.单独分析1
static void dapm_reset(struct snd_soc_card *card)
    struct snd_soc_dapm_widget *w;
    memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); //把card的card->dapm_stats置0
    list_for_each_entry(w, &card->widgets, list) //遍历所有widgets,把他们的输入输出连接数都置-1,置未检查标志
        w->power_checked = false;
        w->inputs = -1;
        w->outputs = -1;
 
 
2.单独分析2
static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,struct list_head *up_list,struct list_head *down_list)
    switch (w->id)
    case snd_soc_dapm_pre:  //如果是第一个执行的
        ////属machine specific pre widget,插入到down_list最前方
        dapm_seq_insert(w, down_list, false); //单独分析3,加入到所有顺序号比它大的widget的power_list里面,还有加到down_list的末尾
    case snd_soc_dapm_post: //如果是最后一个执行的,属machine specific post widget,插入到up_list最后方
        dapm_seq_insert(w, up_list, true); 
    default:
        power = dapm_widget_power_check(w); //主要是检测每个widget的状态,用回调函数
            if (w->power_checked) //如果已经检测过了,就返回
                return w->new_power;
            if (w->force) //如果设置了强制的电源状态,返回
                w->new_power = 1;
            else //如果没有就调用power_check函数,这个在初始化的会根据不同的widget,设置不同的回调函数
                w->new_power = w->power_check(w);
            w->power_checked = true;
    dapm_widget_set_power(w, power, up_list, down_list); //单独分析5,主要是找当前连接的输入输出,然后把有连接的加入到dirty链表
 
 
 
单独分析3dapm_seq_insert(w, down_list, false);
加入到所有顺序号比它大的widget的power_list里面,还有加到总的list,down_list或者up_list
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, struct list_head *list, bool power_up)
    list_for_each_entry(w, list, power_list) //遍历链表
        if (dapm_seq_compare(new_widget, w, power_up) < 0) //单独分析4,把它加入到所有顺序号比它大的widget的power_list里面
            list_add_tail(&new_widget->power_list, &w->power_list); //把new_widget->power_list加入到w->power_list的里面的尾部
    list_add_tail(&new_widget->power_list, list); //把它加到总的list的末尾
 
 
 
单独分析4;dapm_seq_compare(new_widget, w, power_up)
/* dapm power sequences - make this per codec in the future */
//widget就是按照这个0开始的顺序是上下电的
static int dapm_up_seq[] = { //上电队列
 [snd_soc_dapm_pre] = 0,
 [snd_soc_dapm_supply] = 1,
 [snd_soc_dapm_regulator_supply] = 1,
 [snd_soc_dapm_clock_supply] = 1,
 [snd_soc_dapm_micbias] = 2,
 [snd_soc_dapm_dai_link] = 2,
 [snd_soc_dapm_dai_in] = 3,
 [snd_soc_dapm_dai_out] = 3,
 [snd_soc_dapm_aif_in] = 3,
 [snd_soc_dapm_aif_out] = 3,
 [snd_soc_dapm_mic] = 4,
 [snd_soc_dapm_mux] = 5,
 [snd_soc_dapm_virt_mux] = 5,
 [snd_soc_dapm_value_mux] = 5,
 [snd_soc_dapm_dac] = 6,
 [snd_soc_dapm_mixer] = 7,
 [snd_soc_dapm_mixer_named_ctl] = 7,
 [snd_soc_dapm_pga] = 8,
 [snd_soc_dapm_adc] = 9,
 [snd_soc_dapm_out_drv] = 10,
 [snd_soc_dapm_hp] = 10,
 [snd_soc_dapm_spk] = 10,
 [snd_soc_dapm_line] = 10,
 [snd_soc_dapm_post] = 11,
};
static int dapm_down_seq[] = { //下电队列
 [snd_soc_dapm_pre] = 0,
 [snd_soc_dapm_adc] = 1,
 [snd_soc_dapm_hp] = 2,
 [snd_soc_dapm_spk] = 2,
 [snd_soc_dapm_line] = 2,
 [snd_soc_dapm_out_drv] = 2,
 [snd_soc_dapm_pga] = 4,
 [snd_soc_dapm_mixer_named_ctl] = 5,
 [snd_soc_dapm_mixer] = 5,
 [snd_soc_dapm_dac] = 6,
 [snd_soc_dapm_mic] = 7,
 [snd_soc_dapm_micbias] = 8,
 [snd_soc_dapm_mux] = 9,
 [snd_soc_dapm_virt_mux] = 9,
 [snd_soc_dapm_value_mux] = 9,
 [snd_soc_dapm_aif_in] = 10,
 [snd_soc_dapm_aif_out] = 10,
 [snd_soc_dapm_dai_in] = 10,
 [snd_soc_dapm_dai_out] = 10,
 [snd_soc_dapm_dai_link] = 11,
 [snd_soc_dapm_clock_supply] = 12,
 [snd_soc_dapm_regulator_supply] = 12,
 [snd_soc_dapm_supply] = 12,
 [snd_soc_dapm_post] = 13,
};
//主要是根据上下顺序号
static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, bool power_up)
    if (power_up) //选择上电队列
        sort = dapm_up_seq;
    else //选择下电队列
        sort = dapm_down_seq;
    if (sort[a->id] != sort[b->id]) //如果他们在队列里面的值不一样
        return sort[a->id] - sort[b->id]; //这里需要插入的widget顺序值小于链表里面的
    if (a->subseq != b->subseq) //这个下一级的排序,就是当seq相等的时候
        if (power_up) //如果是上电,值越小就排在前面
            return a->subseq - b->subseq;
        else  //如果是下电,值越小排在后面
            return b->subseq - a->subseq;
    if (a->reg != b->reg) //如果前面都相等,比较reg ,没有直接的dapm/* negative reg = no direct dapm */
        return a->reg - b->reg;
    if (a->dapm != b->dapm) //不同的/* DAPM context */
        return (unsigned long)a->dapm - (unsigned long)b->dapm;
 
 
单独分析5:主要是找当前连接的输入输出,然后把有连接的加入到dirty链表
static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, struct list_head *up_list, struct list_head *down_list)
 /* If we changed our power state perhaps our neigbours changed also.*/
    list_for_each_entry(path, &w->sources, list_sink) //遍历我们的source链表,找到这些path
        if (path->source) //找到source
             dapm_widget_set_peer_power(path->source, power, path->connect);
                /* If a connection is being made or broken then that update  will have marked the peer dirty, otherwise the widgets are
                * not connected and this update has no impact. */
                //如果相连接,就加入到dirty链表,没有连接就没有影响,在哪里连接的是这个问题
                if (!connect) //connect是在route里面有定义,加入的会显示连接状态
                    return;
                /* If the peer is already in the state we're moving to then we won't have an impact on it. */
                //如果这个邻居的的状态已经是我们想要的状态,就没有影响
                if (power != peer->power)
                    dapm_mark_dirty(peer, "peer state change");
    switch (w->id) //如果是下面这三种纯输入就跳过
    case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_clock_supply:
        /* Supplies can't affect their outputs, only their inputs */
        break;
    default:
        list_for_each_entry(path, &w->sinks, list_source) //z罗列它的输出
            if (path->sink) //  //如果相连接,就加入到dirty链表,没有连接就没有影响,在哪里连接的是这个问题
                dapm_widget_set_peer_power(path->sink, power,path->connect);
    if (power) //插入到
        dapm_seq_insert(w, up_list, true); //加入到所有顺序号比它大的widget的power_list里面,还有加到up_list的末尾
 else
        dapm_seq_insert(w, down_list, false);  //加入到所有顺序号比它大的widget的power_list里面,还有加到down_list的末尾
w->power = power; //赋值状态
 
 
单独分析6:主要是设置了codec的bias_level
static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
    /* If we're off and we're not supposed to be go into STANDBY */
    if (d->bias_level == SND_SOC_BIAS_OFF && d->target_bias_level != SND_SOC_BIAS_OFF)
        // //需要使用设备时,device driver调用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用计数
        pm_runtime_get_sync(d->dev);
        //set the bias level for the system,设置偏压
        ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); //这里主要是设置codec的偏压
            if (card && card->set_bias_level) //暂未发现
                ret = card->set_bias_level(card, dapm, level);
            if (dapm->codec)
                if (dapm->codec->driver->set_bias_level) //调用rk音频驱动分析之codec的es8323_set_bias_level 
                    ret = dapm->codec->driver->set_bias_level(dapm->codec, level);
            if (card && card->set_bias_level_post) //暂时没有
                ret = card->set_bias_level_post(card, dapm, level);
    /* Prepare for a STADDBY->ON or ON->STANDBY transition */
    if (d->bias_level != d->target_bias_level) //这个和上面一样,不过是SND_SOC_BIAS_PREPARE
        ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
 
 
单独分析7:
static void dapm_seq_run(struct snd_soc_dapm_context *dapm, struct list_head *list, int event, bool power_up)
    if (power_up)
        sort = dapm_up_seq;
    else
        sort = dapm_down_seq;
    list_for_each_entry_safe(w, n, list, power_list)
        /* Do we need to apply any queued changes? */
        if (!list_empty(&pending)) 
            // * Apply the coalesced changes from a DAPM sequence */ 合并执行
            dapm_seq_run_coalesced(cur_dapm, &pending); //这个单独分析8
       //这里是在codec->dapm.seq_notifier = codec_drv->seq_notifier;,其实调用codec的seq_notifier
        if (cur_dapm && cur_dapm->seq_notifier)  //这里我们没有
            for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
                if (sort[i] == cur_sort) 
                    cur_dapm->seq_notifier(cur_dapm, i, cur_subseq);
        INIT_LIST_HEAD(&pending);
    switch (w->id)
        ///为什么类型为pre/post的widget只执行event回调函数?看看它们的原型就明白了。  
          //#define SND_SOC_DAPM_PRE(wname, wevent),显然这些widget只含有stream name和event回调函数。
        case snd_soc_dapm_pre:
            if (!w->event) 
                list_for_each_entry_safe_continue(w, n, list, power_list);
            if (event == SND_SOC_DAPM_STREAM_START) //执行 SND_SOC_DAPM_PRE(wname, wevent)传入的event函数
                ret = w->event(w,  NULL, SND_SOC_DAPM_PRE_PMU);
            else if (event == SND_SOC_DAPM_STREAM_STOP) 
                ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
        case snd_soc_dapm_post: //这个与snd_soc_dapm_pre一样,就是顺序不同
        default:
            /* Queue it up for application */
            //遇到非以上类型的widget,则插入到pending链表,进一步调用dapm_seq_run_coalesced处理。  
             //这里设计很巧妙!下面详细解析这点。
            cur_sort = sort[w->id];
            cur_subseq = w->subseq;
            cur_reg = w->reg;    
            cur_dapm = w->dapm;
    if (!list_empty(&pending))
        dapm_seq_run_coalesced(cur_dapm, &pending);
 
 
单独分析8:/* Apply the coalesced changes from a DAPM sequence */
//这个函数的注释写得很清楚,它遍历之前已排序好(dapm_seq_insert)的链表,把1)连续的、
//2)同一个widget register的、3)同一个power sequence的widgets送到pending链表上,然后调用dapm_seq_run_coalesced对该链表上的widgets进行设置。这样做的目的是尽可能减少对widget registers的读写。
static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm, struct list_head *pending)
    reg = list_first_entry(pending, struct snd_soc_dapm_widget, power_list)->reg;
    list_for_each_entry(w, pending, power_list)
        cur_mask = 1 << w->shift;
        if (w->invert)
            power = !w->power;
        else
            power = w->power;
        mask |= cur_mask;
        if (power)
            value |= cur_mask;
        /* Check for events */
      dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMU);
      dapm_seq_check_event(dapm, w, SND_SOC_DAPM_PRE_PMD);
    if (reg >= 0)
        /* Any widget will do, they should all be updating the same register.*/
        w = list_first_entry(pending, struct snd_soc_dapm_widget, power_list);
        pop_wait(card->pop_time); //延时
        soc_widget_update_bits_locked(w, reg, mask, value);
    list_for_each_entry(w, pending, power_list) 
        dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMU);
        dapm_seq_check_event(dapm, w, SND_SOC_DAPM_POST_PMD);
 
 
 

推荐阅读