精华内容
下载资源
问答
  • 高通AudioHAL分析

    千次阅读 2020-08-31 16:45:49
    这篇文章主要介绍一下AudioFlinger到hal层的一些工作流程。我们知道AudioFlinger从FIFO种读取到数据之后会调用hal的out_write函数去写入数据,我们就以这个函数为入口展开分析。 out_write函数 这个函数首先是有...

    这篇文章主要介绍一下AudioFlinger到hal层的一些工作流程。我们知道AudioFlinger从FIFO种读取到数据之后会调用hal的out_write函数去写入数据,我们就以这个函数为入口展开分析。

    1. out_write函数
      这个函数首先是有一些out->devices的判断,然后会判断是否是standby,如果是会调用start_output_stream去打开输出设备。然后如果pcm状态正常的情况下会调用pcm_write函数向adsp中写入数据。pcm_write是kernel的函数,我们这里只分析hal层,所以接下来我们去看一下start_output_stream函数打开设备的流程。
    2. start_output_stream函数
      这个函数首先调用platform_get_pcm_device_id函数去获取device id,这个id的定义是取决于kernel的,也就是说hal层把这个id传给kernel,kernel根据对应的id去打开相应的通路。platform_get_pcm_device_id函数在platform.c中,主要是从pcm_device_table中根据相应的usecase去获取对应的通路id。定义如下:
    static int pcm_device_table[AUDIO_USECASE_MAX][2] = {
        [USECASE_AUDIO_PLAYBACK_DEEP_BUFFER] = {DEEP_BUFFER_PCM_DEVICE,
                                                DEEP_BUFFER_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_WITH_HAPTICS] = {AUDIO_HAPTICS_PCM_DEVICE,
                                                 AUDIO_HAPTICS_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = {LOWLATENCY_PCM_DEVICE,
                                               LOWLATENCY_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_ULL]         = {MULTIMEDIA3_PCM_DEVICE,
                                                MULTIMEDIA3_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_MULTI_CH] = {MULTIMEDIA2_PCM_DEVICE,
                                             MULTIMEDIA2_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_HIFI] = {MULTIMEDIA2_PCM_DEVICE,
                                         MULTIMEDIA2_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_TTS] = {MULTIMEDIA2_PCM_DEVICE,
                                            MULTIMEDIA2_PCM_DEVICE},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD] =
                         {PLAYBACK_OFFLOAD_DEVICE, PLAYBACK_OFFLOAD_DEVICE},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD2] =
                         {PLAYBACK_OFFLOAD_DEVICE2, PLAYBACK_OFFLOAD_DEVICE2},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD3] =
                         {PLAYBACK_OFFLOAD_DEVICE3, PLAYBACK_OFFLOAD_DEVICE3},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD4] =
                         {PLAYBACK_OFFLOAD_DEVICE4, PLAYBACK_OFFLOAD_DEVICE4},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD5] =
                         {PLAYBACK_OFFLOAD_DEVICE5, PLAYBACK_OFFLOAD_DEVICE5},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD6] =
                         {PLAYBACK_OFFLOAD_DEVICE6, PLAYBACK_OFFLOAD_DEVICE6},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD7] =
                         {PLAYBACK_OFFLOAD_DEVICE7, PLAYBACK_OFFLOAD_DEVICE7},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD8] =
                         {PLAYBACK_OFFLOAD_DEVICE8, PLAYBACK_OFFLOAD_DEVICE8},
        [USECASE_AUDIO_PLAYBACK_OFFLOAD9] =
                         {PLAYBACK_OFFLOAD_DEVICE9, PLAYBACK_OFFLOAD_DEVICE9},
    
    。。。
    };
    

    然后DEEP_BUFFER_PCM_DEVICE等分别对应定义好的一个int值。
    然后会调用到select_devices函数中。
    3. select_devices函数

    int select_devices(struct audio_device *adev, audio_usecase_t uc_id)
    {
        ALOGD("%s for use case (%s)", __func__, use_case_table[uc_id]);
    
        usecase = get_usecase_from_list(adev, uc_id);
        if (usecase == NULL) {
            ALOGE("%s: Could not find the usecase(%d)", __func__, uc_id);
            return -EINVAL;
        }
            if (usecase->type == PCM_PLAYBACK) {
                if (usecase->stream.out == NULL) {
                    ALOGE("%s: stream.out is NULL", __func__);
                    return -EINVAL;
                }
                usecase->devices = usecase->stream.out->devices;
                in_snd_device = SND_DEVICE_NONE;
                if (out_snd_device == SND_DEVICE_NONE) {
                    struct stream_out *voip_out = adev->primary_output;
                    struct stream_in *voip_in = get_voice_communication_input(adev);
                    if (usecase->devices & AUDIO_DEVICE_OUT_BUS)
                        out_snd_device = audio_extn_auto_hal_get_output_snd_device(adev, uc_id);
                    else
                        out_snd_device = platform_get_output_snd_device(adev->platform,
                                                                        usecase->stream.out);
                    voip_usecase = get_usecase_from_list(adev, USECASE_AUDIO_PLAYBACK_VOIP);
    
                    if (voip_usecase)
                        voip_out = voip_usecase->stream.out;
    
                    if (usecase->stream.out == voip_out && voip_in != NULL)
                        select_devices(adev, voip_in->usecase);
                }
            } 
            /* Enable new sound devices */
            if (out_snd_device != SND_DEVICE_NONE) {
                check_usecases_codec_backend(adev, usecase, out_snd_device);
                if (platform_check_codec_asrc_support(adev->platform))
                   check_and_set_asrc_mode(adev, usecase, out_snd_device);
                enable_snd_device(adev, out_snd_device);
            }
            enable_audio_route(adev, usecase);
    
            audio_extn_qdsp_set_device(usecase);
        }
    

    这个函数会调用get_usecase_from_list函数把device和usecase等信息封装到audio_usecase结构体中,如果usecase->type是播放的话会去判断输出设备是否是AUDIO_DEVICE_OUT_BUS(这个是车机的device,因为我们是车机,所以就看一下这个if调用的函数),然后调用audio_extn_auto_hal_get_output_snd_device函数,再到auto_hal_get_output_snd_device函数,这个函数会根据device和usecase去选择hal层对应的snd_device。这个snd_device和上面传过来的device不同,传过来的device是framework上定义的一个名称,这个snd_device才是真正的hal层的device,比如framework传过来的是通过offload模式(媒体播放)设备是AUDIO_DEVICE_OUT_BUS去播放设备,在这里会根据这个usecase和device把snd_device转换成SND_DEVICE_OUT_BUS_MEDIA,如果framework传过来的是NAV(导航)模式AUDIO_DEVICE_OUT_BUS设备,那么snd_device对应的就是SND_DEVICE_OUT_BUS_NAV。同样的,如果设备不是AUDIO_DEVICE_OUT_BUS会调用platform_get_output_snd_device,但是这个函数也是做上面的一些操作,只不过这个函数相对会复杂一些,除了AUDIO_DEVICE_OUT_BUS之外所有的device转换都是从这里做的,比如headset,手机上的听筒等。最后调用enable_snd_device去打开输出设备通路,以及调用enable_audio_route去使能audio route。
    4. enable_snd_device函数
    这个函数首先调用platform_get_snd_device_name_extn函数使用上一步得到的snd_device去获取device_name,这个device_name就是mixer_path中所定义的名字,还是以SND_DEVICE_OUT_BUS_MEDIA为例进行分析,这里从device_table数组中得到的device_name是"bus-speaker"。然后继续往下走,会走到audio_route_apply_and_update_path函数,再到audio_route_update_path函数,这里首先根据上面获取到的device_name去解析mixer_path,然后拿到mixer_path中定义的"bus-speaker"所对应的通路,然后调用tinyalsa的mixer.c中的mixer_ctl_set_value函数去打开设备通路。
    5. enable_audio_route函数
    这个函数会调用platform_add_backend_name函数在通路的设备上添加设备名,用于不同的usecase定义,也就是把"headphones-44.1","speaker-and-headphones"等添加到usecase的设备名称中,主要是为了应对同样的usecase不同的设备配置不同的问题。比如同样的offload模式,有如下两种不同的定义

        <path name="compress-offload-playback">
            <ctl name="TERT_TDM_RX_0 Channels" value="Six" />
            <ctl name="TERT_TDM_RX_0 Audio Mixer MultiMedia4" value="1" />
        </path>
        
        <path name="compress-offload-playback bt-sco">
            <ctl name="SLIMBUS_7_RX Audio Mixer MultiMedia4" value="1" />
        </path>
    
    

    通过上面的例子我们可以看到,同样的offload模式,蓝牙的和默认的定义是完全不同的。最后这个函数和enable_snd_device函数一样也是调用audio_route_apply_and_update_path函数通过tinyalsa去打开对应的usecase。到这里hal层的打开usecase通路以及打开device输出设备的流程就大体分析完成了

    展开全文
  • 高通Audio Hal学习笔记(1)结构体的关系1. Audio Hal的标准接口相关的结构体1.1 Module相关的结构体1.1.1 HAL_MODULE_INFO_SYM1.1.2 struct audio_module1.1.3 struct hw_module_t1.1.4 struct hw_module_methods_t...

    以下以高通的开源代码codeaurora.org分析,版本: lito LA.UM.8.13.r1-08500-SAIPAN.0 下载的方式: repo init -u git://codeaurora.org/platform/manifest.git -b
    release -m LA.UM.8.13.r1-08500-SAIPAN.0.xml
    –repo-url=git://codeaurora.org/tools/repo.git --repo-branch=caf-stable

    1. Audio Hal的标准接口相关的结构体

    1.1 Module相关的结构体

    在audio_hw.c中我们可以看到如下定义

    static struct hw_module_methods_t hal_module_methods = {
        .open = adev_open,
    };
    
    struct audio_module HAL_MODULE_INFO_SYM = {
        .common = {
            .tag = HARDWARE_MODULE_TAG,
            .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
            .hal_api_version = HARDWARE_HAL_API_VERSION,
            .id = AUDIO_HARDWARE_MODULE_ID,
            .name = "QCOM Audio HAL",
            .author = "The Linux Foundation",
            .methods = &hal_module_methods,
        },
    };
    

    1.1.1 HAL_MODULE_INFO_SYM

    当AudioFlinger加载AudioHal库之后会找到这个符号,获得audio_module这个结构。每一个Hal必须要有这个符号。
    hal_module_methods: 这个结构比较明显是提供一个open的回调函数, 猜测应该是用来打开Hal, 初始化Hal, 获取更多的接口

    1.1.2 struct audio_module

    内部只有audio_module这个结构,从结构体的描述我们知道,结构体必须以hw_module_t开始,之后可以是模块特有的信息。这样可以用来保证 HAL_MODULE_INFO_SYM 和 common的地址相同,方便通过指针做互相转化。

    struct audio_module {
    	struct hw_module_t common;
    };
    

    1.1.3 struct hw_module_t

    这个是标准Hal的接口,用于提供这个Hal的描述以及最基本的接口,关于内部成员的含义可以参考结构体声明部分的注释。

    	typedef struct hw_module_t {
    		uint32_t tag;
    		uint16_t module_api_version;
    		uint16_t hal_api_version;
    		const char *id;
    		const char *name;
    		const char *author;
    		struct hw_module_methods_t* methods;
    	}
    

    1.1.4 struct hw_module_methods_t

    这里面只有一个open函数, 比较明显是传入获取的module,返回一个struct hw_device_t结构体指针。

        int (*open)(const struct hw_module_t* module, const char* id,
                          struct hw_device_t** device);
    

    1.2 Device相关的结构体

    在AudioHal中会出现很多device的概念,比较容易混淆,其中这里的device我们可以理解为一个声卡设备。
    audio_hw_device包含两部分,struct hw_device_t common这个是标准Hal的一个接口,放在了结构体的最前面。也是为了指针转化。接下来的是Audio Hal特有的接口,都是回调函数:
    比较重要的是控制输入输出流的接口。其中通过open_output_stream的声明我们可以猜到,用来返回一个struct audio_stream_out结构体指针,这个结构体用来控制播放流。

    struct audio_hw_device {
    
        struct hw_device_t common;
    
        uint32_t (*get_supported_devices)();
        int (*get_microphones)();
        int (*init_check)();
    
        int (*create_audio_patch)();
        int (*release_audio_patch)();
        int (*get_audio_port)();
        int (*set_audio_port_config)();
    
        int (*set_voice_volume)();
        int (*set_master_volume)();
        int (*get_master_volume)();
        int (*set_mic_mute)();
        int (*get_mic_mute)();
        int (*set_master_mute)();
        int (*get_master_mute)();
    
        int (*set_mode)();
        int (*set_parameters)()
        char * (*get_parameters)();
        size_t (*get_input_buffer_size)();
    
        int (*open_output_stream)();
        void (*close_output_stream)();
        int (*open_input_stream)();
        void (*close_input_stream)();
    
        int (*dump)();
    

    1.3 Stream相关的接口

    audio_stream_out,audio_stream_in
    我们这里的流指的是一个具体的pcm数据流,与AudioFlinge的播放/录音线程相对应。
    有两个结构体: audio_stream_out, audio_stream_in
    两个结构体都以同一个结构体开头,struct audio_stream common,原因与上面的描述基本相同。
    以audio_stream_out为例,内部都是回调函数的指针,其中比较重要指针包括start, stop, write等接口具体的接口可以参考结构体的声明。

    2 高通针对接口的扩展

    2.1 audio_device结构体

    2.1.1 audio_device

    是对audio_hw_device的扩展,类似与继承的概念。用来描述整个Audio Hal也就是整个声卡。结构体只有一处定义adev,在接口adev_open处分配空间。因为 audio_device audio_hw_device hw_device_t 结构体指针相同。在之后的操作中有很多接口之间的转化。
    audio_device结构体太大,下面只列出来几个比较重要的成员:

    struct audio_device {
        struct audio_hw_device device;
    
        int snd_card;
    
        struct listnode usecase_list;
    
        struct mixer *mixer;
        struct audio_route *audio_route;
    
        struct voice voice;
        void *platform;
    };
    

    2.1.2 audio_hw_device

    audio hal的标准接口,放在了audio_device最前面。保证结构体指针相同。
    这样当AudioFligner传入一个struct audio_hw_device dev指针(通过open获取),我们就可以把dev转化为struct audio_device

    2.1.3 snd_card

    即声卡的ID,也就是ALSA设备中声卡的ID。audio_device可以理解为对声卡的抽象。

    2.1.4 usecase_list

    对Audio Hal最重要的应该是PCM数据流。在audio_device中的struct listnode usecase_list链表中保存了pcm流的信息。

    struct audio_device {
        struct audio_hw_device device;
    
        int snd_card;
    
        struct listnode usecase_list;
    
        struct mixer *mixer;
        struct audio_route *audio_route;
    
        struct voice voice;
        void *platform;
    };
    

    2.2 stream_out,stream_in

    这两个结构体是对audio_stream_out, audio_stream_in的扩展。用来描述一个PCM数据流。
    其中有个成员 int pcm_device_id,代表了一个ALSA中的PCM设备的ID。我们就可以理解:
    AudioPolicy中的output,AudioFlinger中的Thread,AudioHal的Stream,Alsa的PCM设备都是一一对应的。都是对同一个PCM数据流的抽象。
    之后,我们会重点分析一下这个两个结构体。

    2.3 struct audio_usecase

    这个结构体是对stream_out,stream_in进一步的抽象。特殊的场景,没有PCM设备的场景也会创建一个usecase,例如通话。为了可以对硬件统一操作,防止各种场景下硬件访问的冲突。
    其中audio_usecase是在start_output_stream函数中创建的,在stop中清除。所以它一般用来表示正在运行或者stanby状态的流。

    	struct audio_usecase {
    	    struct listnode list;
    	    audio_usecase_t id;
    	    usecase_type_t  type;
    	    audio_devices_t devices;
    	    snd_device_t out_snd_device;
    	    snd_device_t in_snd_device;
    	    struct stream_app_type_cfg out_app_type_cfg;
    	    struct stream_app_type_cfg in_app_type_cfg;
    	    union stream_ptr stream;
    	};
    

    2.3.1 union stream_ptr stream

    联合体中保存着stream_out或stream_in的指针。可以通过use_case->stream.out操作stream_out结构体。

    	union stream_ptr {
    		struct stream_in *in;
    		struct stream_out *out;
    		struct stream_inout *inout;
    	};
    

    2.3.2 audio_usecase_t id, usecase_type_t type

    usecase的类型,根据AudioPolicy中output的类型获得

    3 Audio Hal的结构图

    在这里插入图片描述

    展开全文
  • 高通audio入门

    2018-01-09 10:11:01
    介绍了高通audio入门 1.Audio软硬件系统框架 2.Audio bringup 3.ACDB校准 4.how to debug
  • 高通audio offload学习

    千次阅读 2018-07-25 16:37:26
    高通audio offload学习| http://thinks.me/2016/09/13/audio_qcom_offload/ 简述 offload在音频系统里面,就是将对于音频文件的解码操作过载到DSP中去做,比如说mp3的解码操作不是在mediaserver中来做,而是直接...

    高通audio offload学习|  

    http://thinks.me/2016/09/13/audio_qcom_offload/

    简述

    offload在音频系统里面,就是将对于音频文件的解码操作过载到DSP中去做,比如说mp3的解码操作不是在mediaserver中来做,而是直接将数据传递到dsp中,由DSP去解码和播放,就是所谓的硬解。硬解的优势是省功耗,除了DSP自身在处理音频格式的时候功耗相对较低,整个数据在安卓的音频系统中传递也会相对低些,前面那个好理解,后面这个我们结合代码来分析。

    正文

    这次的场景,就是在高通平台通过mediaplayer进行播放mp3.其中audiotrack中采用到了callback的方式。

    audiotrack部分

    我们从代码的角度一步步分析。
    摘取一些audiotrack中set中的代码:

    if (cbf != NULL) {
        mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
        mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
        // thread begins in paused state, and will not reference us until start()
    }
    

    这部分创建了AudioTrackThread,并且让它跑起来了。来看下线程的逻辑处理函数:

    bool AudioTrack::AudioTrackThread::threadLoop()
    {
        {
            AutoMutex _l(mMyLock);
            // mPaused 外部触发的pause,将会在这边进行block
            if (mPaused) {
                mMyCond.wait(mMyLock);
                // caller will check for exitPending()
                return true;
            }
            // 如果忽略下一次的内部pause, 下面的内部pause将会被跳过
            if (mIgnoreNextPausedInt) {
                mIgnoreNextPausedInt = false;
                mPausedInt = false;
            }
            // 内部pause,如果有内部pause的需求,将在这边进行等待。
            if (mPausedInt) {
                if (mPausedNs > 0) {
                    (void) mMyCond.waitRelative(mMyLock, mPausedNs);
                } else {
                    mMyCond.wait(mMyLock);
                }
                mPausedInt = false;
                return true;
            }
        }
        // 如果线程结束,则返回false,停止循环
        if (exitPending()) {
            return false;
        }
        // 这边将进行获取数据,处理数据的操作,返回的ns决定将内部block多久,跟开头的那边对应
        nsecs_t ns = mReceiver.processAudioBuffer();
        switch (ns) {
        case 0:
            return true;
        case NS_INACTIVE:
            pauseInternal();
            return true;
        case NS_NEVER:
            return false;
        case NS_WHENEVER:
            // Event driven: call wake() when callback notifications conditions change.
            ns = INT64_MAX;
            // fall through
        default:
            LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %" PRId64, ns);
            pauseInternal(ns);
            return true;
        }
    }
    

    上面函数大部分都在描述如何控制暂停,对于数据的处理在函数processAudioBuffer中,我们可以回头来看下这个函数的实现:

    nsecs_t AudioTrack::processAudioBuffer()
    {
    
        mLock.lock();
        // 申请线程成实时线程,这边进行循环检测,是否申请成功,每次休眠的时间成指数增长,最多循环5次
        if (mAwaitBoost) {
            mAwaitBoost = false;
            mLock.unlock();
            static const int32_t kMaxTries = 5;
            int32_t tryCounter = kMaxTries;
            uint32_t pollUs = 10000;
            do {
                int policy = sched_getscheduler(0);
                if (policy == SCHED_FIFO || policy == SCHED_RR) {
                    break;
                }
                usleep(pollUs);
                pollUs <<= 1;
            } while (tryCounter-- > 0);
            if (tryCounter < 0) {
                ALOGE("did not receive expected priority boost on time");
            }
            // Run again immediately
            return 0;
        }
    
        // Can only reference mCblk while locked
        int32_t flags = android_atomic_and(
            ~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END), &mCblk->mFlags);
    
        // Check for track invalidation
        if (flags & CBLK_INVALID) {
            // 对于offload tracks的restoreTrack_l将只会更新sequence和清空AudioSystem缓存。
            // 我们不会在这边直接退出,但是后面会调用callback来让上层recreate该track
            // 对于其他类型的track,将在这边进行restore的操作,并且不会让他在这边退出,因为flag中还要处理
            if (!isOffloadedOrDirect_l() || (mSequence == mObservedSequence)) {
                status_t status __unused = restoreTrack_l("processAudioBuffer");
            }
        }
        // 如果当前正在stopping状态的话,则设置waitStreamEnd为true
        bool waitStreamEnd = mState == STATE_STOPPING;
        // 判断当前该track的状态是否正在播放
        bool active = mState == STATE_ACTIVE;
    
        // Manage underrun callback, must be done under lock to avoid race with releaseBuffer()
        bool newUnderrun = false;
        if (flags & CBLK_UNDERRUN) {
            if (!mInUnderrun) {
                mInUnderrun = true;
                newUnderrun = true;
            }
        }
    
        // Get current position of server 更新server的position
        size_t position = updateAndGetPosition_l();
    
        // Cache other fields that will be needed soon
        uint32_t sampleRate = mSampleRate;
        float speed = mPlaybackRate.mSpeed;
        const uint32_t notificationFrames = mNotificationFramesAct;
        // 如果需要更新remaing则将notificationFrames更新到mRemainingFrames,一般在开始或者flush之后。
        if (mRefreshRemaining) {
            mRefreshRemaining = false;
            mRemainingFrames = notificationFrames;
            mRetryOnPartialBuffer = false;
        }
        size_t misalignment = mProxy->getMisalignment();
        uint32_t sequence = mSequence;
        sp<AudioTrackClientProxy> proxy = mProxy;
    
        mLock.unlock();
    
        // get anchor time to account for callbacks.
        const nsecs_t timeBeforeCallbacks = systemTime();
        // 处理留马上结束的逻辑
        if (waitStreamEnd) {
            struct timespec timeout;
            // 120s
            timeout.tv_sec = WAIT_STREAM_END_TIMEOUT_SEC;
            timeout.tv_nsec = 0;
            // 这边正常情况下会进入休眠,等待最长时间为2分钟
            status_t status = proxy->waitStreamEndDone(&timeout);
            switch (status) {
            case NO_ERROR:
            case DEAD_OBJECT:
            case TIMED_OUT:
                // 告诉上层stream end的事件
                mCbf(EVENT_STREAM_END, mUserData, NULL);
                {
                    AutoMutex lock(mLock);
                    // 更新确认下是否还处在waitStreamEnd状态。如果是的话,切换state到stoped状态
                    waitStreamEnd = mState == STATE_STOPPING;
                    if (waitStreamEnd) {
                        mState = STATE_STOPPED;
                        mReleased = 0;
                    }
                }
                // 如果这个track没有出现什么异常,并且已经stream end了,就进入休眠了
                if (waitStreamEnd && status != DEAD_OBJECT) {
                   return NS_INACTIVE;
                }
                // 如果不是,则直接跳出返回0,这样该函数再被调用一次。
                break;
            }
            return 0;
        }
    
        if (flags & CBLK_BUFFER_END) {
            mCbf(EVENT_BUFFER_END, mUserData, NULL);
        }
    
        // 如果当前处在非激活状态,则进入休眠等待到该track被重启
        if (!active) {
            return NS_INACTIVE;
        }
    
    
        // If > 0, poll periodically to recover from a stuck server.  A good value is 2.
        static const uint32_t kPoll = 0;
        if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) {
            minFrames = kPoll * notificationFrames;
        }
    
        // This "fudge factor" avoids soaking CPU, and compensates for late progress by server
        static const nsecs_t kWaitPeriodNs = WAIT_PERIOD_MS * 1000000LL;
        const nsecs_t timeAfterCallbacks = systemTime();
    
        // Convert frame units to time units
        nsecs_t ns = NS_WHENEVER;
    
        // If not supplying data by EVENT_MORE_DATA, then we're done
        if (mTransfer != TRANSFER_CALLBACK) {
            return ns;
        }
        // 阻塞时间的换算
        struct timespec timeout;
        const struct timespec *requested = &ClientProxy::kForever;
        if (ns != NS_WHENEVER) {
            timeout.tv_sec = ns / 1000000000LL;
            timeout.tv_nsec = ns % 1000000000LL;
            requested = &timeout;
        }
        // 开始进入获取buffer,填充buffer,release buffer的逻辑
        while (mRemainingFrames > 0) {
    
            Buffer audioBuffer;
            audioBuffer.frameCount = mRemainingFrames;
            size_t nonContig;
            // 第一次进入该循环,obtain是个阻塞操作,后面就变成非阻塞操作。这个主要跟buffer的设计思路有关。
            // avail buffer有可能包含两部分,一部分在前面,一部分在后面。而一开始我们只能获取后面部分的。
            // 在循环一次将触发获取前面部分的数据。
            status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig);
            // 修改获取策略为nonBlocking
            requested = &ClientProxy::kNonBlocking;
            // 计算上次获取到的总的空间大小,此次只能填充audioBuffer.frameCount
            size_t avail = audioBuffer.frameCount + nonContig;
    
            size_t reqSize = audioBuffer.size;
            // 问app要数据
            mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
            size_t writtenSize = audioBuffer.size;
            // 如果写入的数据为0,则返回给休眠的时间,省得多做无用功
            if (writtenSize == 0) {
                nsecs_t myns;
                if (audio_is_linear_pcm(mFormat)) {
                } else {
                    myns = kWaitPeriodNs;
                }
                if (ns > 0) { // account for obtain and callback time
                    const nsecs_t timeNow = systemTime();
                    ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks));
                }
                if (ns < 0 /* NS_WHENEVER */ || myns < ns) {
                    ns = myns;
                }
                return ns;
            }
    
            size_t releasedFrames = writtenSize / mFrameSize;
            audioBuffer.frameCount = releasedFrames;
            mRemainingFrames -= releasedFrames;
            if (misalignment >= releasedFrames) {
                misalignment -= releasedFrames;
            } else {
                misalignment = 0;
            }
            // 释放缓冲区,其实就是更新了下cblk的里面的参数
            releaseBuffer(&audioBuffer);
            // 写的数据小宇reqSize则循环再获取,填充等操作。
            if (writtenSize < reqSize) {
                continue;
            }
            // 如果buffer的前部分大于等于剩余的需要获取的数据量,则继续问app要来填充buffer
            if (mRemainingFrames <= nonContig) {
                continue;
            }
        }
        // 更新mRemainingFrames为整个buffer大小。notificationFrames这个值在offload的场景下为buffer大小
        mRemainingFrames = notificationFrames;
        mRetryOnPartialBuffer = true;
        return 0;
    }
    

    上面基本上就完成了怎么问上层要数据的逻辑了。下面代码说明mNotificationFramesAct值来自何处

    下面摘取createTrack_l中的部分代码:

    // mNotificationFramesAct 等于buffer的大小
    if (!audio_is_linear_pcm(mFormat)) {
    
        if (mSharedBuffer != 0) {
            // Same comment as below about ignoring frameCount parameter for set()
            frameCount = mSharedBuffer->size();
        } else if (frameCount == 0) {
            frameCount = mAfFrameCount;
        }
        if (mNotificationFramesAct != frameCount) {
            mNotificationFramesAct = frameCount;
        }
    } ...
    

    mNotificationFramesAct这个值我们之前有谈过了,这个值是用来确定该什么时候唤醒生产者往buffer里面填充数据,但是这个值如果大于mFrameCount的一半的话,那通知的大小将会采用mFrameCount的一半。

    到这边,我们就先完成offload的audiotrack部分通过线程获取数据的逻辑了。obtainBufferreleaseBuffer可以去参考下http://thinks.me/2016/03/18/audiotrack_write/这篇文章,里面有谈到这两个函数的实现。
    上面讲了那么多,其实无非就是检查下buffer有没有空间,如果有就去填充数据,如此反复。。只是实现起来没那么容易才有这么大片大片的代码。当然上面并没有包括控制流,全是数据流相关。跟audioflinger部分的交互,可以参考下图片。

    audioflinger部分

    这部分就是生产者与消费者模式中的消费者了,我们将从线程创建开始分析,再看AsyncCallbackThread的逻辑,不过我们只看跟普通线程的区别的部分。但是其实消费者的主要的逻辑确实在threadloop函数中,这个我们将只用图来描述,因为逻辑较为简单。

    offloadThread创建

    offloadThread线程的创建,其实依赖于audio_policy.conf中是否有配置,如果有配置,则才会有创建该线程的逻辑。
    offload跟mixer线程的区别,其实主要是基本上没有mixer的处理,直接绕过audioflinger的采样率、format、采样精度、通道数等的转变,所以其实offload的逻辑是更简单的。

    不过我们这篇重点在讲offload的callback的实现逻辑,直接引用PlaybackThread::readOutputParameters_l中的部分代码,这边有创建callback的线程

    if ((mOutput->flags & AUDIO_OUTPUT_FLAG_NON_BLOCKING) &&
            (mOutput->stream->set_callback != NULL)) {
        if (mOutput->stream->set_callback(mOutput->stream,
                                      AudioFlinger::PlaybackThread::asyncCallback, this) == 0) {
            mUseAsyncWrite = true;
            mCallbackThread = new AudioFlinger::AsyncCallbackThread(this);
        }
    }
    

    从上面的函数中,可以看到判断依据是audio_policy.conf中compressoffload的节点中的flag是否带有non_blocking,如果有还需要确认底层是否支持callback的场景,如果支持的话,则告诉hal层,我们这边的回调函数是哪个,并且创造callback的线程来。这个线程在这个场景中至关重要,它会等到底层有空间的时候来唤醒offload线程去通知app来填充数据。

    AsyncCallbackThread逻辑

    我们直接来看它的实现代码吧。

    下面这个代码来自PlaybackThread::threadLoop_write

    if (mUseAsyncWrite) {
        // 将该值+2 | 1 保证末位为1
        mWriteAckSequence += 2;
        mWriteAckSequence |= 1;
        // 将该值同步到callback线程中。在该函数中,这个sequence将会被左移1位。
        mCallbackThread->setWriteBlocked(mWriteAckSequence);
    }
    
    bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining);
    // 写完出来判断下写入的数据是否等于打算写入的值,如果不想等话,说明底层没有空间了。相等的话,则不需要进行block状态,可以再进行写操作。
    if (mUseAsyncWrite &&
            ((bytesWritten < 0) || (bytesWritten == (ssize_t)mBytesRemaining))) {
        // 清除末尾为1
        mWriteAckSequence &= ~1;
    
        mCallbackThread->setWriteBlocked(mWriteAckSequence);
    }
    

    这个是setWriteBlocked的操作,这个函数在threadloop_write往hal层写数据的时候会被调用一次,退出write操作之后,如果打算写到底层的数据量跟真实写入的值相等的话,还会再调用一次这个函数。

    void AudioFlinger::AsyncCallbackThread::setWriteBlocked(uint32_t sequence)
    {
        Mutex::Autolock _l(mLock);
        mWriteAckSequence = sequence << 1;
    }
    

    这个是线程的逻辑:

    bool AudioFlinger::AsyncCallbackThread::threadLoop()
    {
        while (!exitPending()) {
            uint32_t writeAckSequence;
            uint32_t drainSequence;
    
            {
                Mutex::Autolock _l(mLock);
                // 如果mWriteAckSequence末位为1 或者 mDrainSequence为1 并且已经被唤醒
                // 当底层有空间的时候,这个线程就会被触发唤醒,继续走下面的逻辑
                while (!((mWriteAckSequence & 1) ||
                         (mDrainSequence & 1) ||
                         exitPending())) {
                    mWaitWorkCV.wait(mLock);
                }
                // mWriteAckSequence、mDrainSequence 将末位进行清0,因为在threadloop_write的时候会将其+2 | 1 并且左移1位, 被唤醒之后,该值又被 |1 。
                writeAckSequence = mWriteAckSequence;
                // 对callback线程中的mWriteAckSequence、mDrainSequence进行末尾清零操作
                mWriteAckSequence &= ~1;
                drainSequence = mDrainSequence;
                mDrainSequence &= ~1;
            }
            {
                sp<AudioFlinger::PlaybackThread> playbackThread = mPlaybackThread.promote();
                if (playbackThread != 0) {
                    // 如果之前block住了,那就进行resetWriteBlocked,这个将会触发offloadThread线程从wait进入到wake up的状态。
                    if (writeAckSequence & 1) {
                        // 右移才是offloadThread中mWriteAckSequence的值
                        playbackThread->resetWriteBlocked(writeAckSequence >> 1);
                    }
                    if (drainSequence & 1) {
                        playbackThread->resetDraining(drainSequence >> 1);
                    }
                }
            }
        }
        return false;
    }
    

    看下resetWriteBlocked的实现

    void AudioFlinger::PlaybackThread::resetWriteBlocked(uint32_t sequence)
    {
        Mutex::Autolock _l(mLock);
        // sequence这个值必须跟offloadThread中的mWriteAckSequence相等,才会触发唤醒操作。
        if ((mWriteAckSequence & 1) && (sequence == mWriteAckSequence)) {
            // 末尾清零,唤醒offloadThread线程
            mWriteAckSequence &= ~1;
            mWaitWorkCV.signal();
        }
    }
    

    上面的sequence巧妙的利用二进制来实现开关的操作,表示进入和退出的效果,并且保证sequence的值一直处在递增可以排重。

    下图描述了offloadThread跟asynccallbackThread之间的调用逻辑:

    offload write

    下面在贴几张高通的文档中比较粗泛的图视:

    framework、hal、kernel的框图:
    offload write
    offload的初始化:
    offload write

    offload的playback:
    offload write
    offload的seek:
    offload write

    #Android #Audio #Offload

    展开全文
  • 高通Audio中ASOC的machine驱动 233333发表于linux驱动个人学习已订阅 1.1K ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次...

    高通Audio中ASOC的machine驱动

    233333发表于linux驱动个人学习已订阅

    1.1K

    ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

    ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等;

    1. 注册Platform driver:

    ASoC把声卡注册为Platform Device:

      1 static int msm8x16_asoc_machine_probe(struct platform_device *pdev)
      2 {
      3     struct snd_soc_card *card;
      4     struct msm8916_asoc_mach_data *pdata = NULL;
      5     struct pinctrl *pinctrl;
      6     const char *card_dev_id = "qcom,msm-snd-card-id";
      7     const char *codec_type = "qcom,msm-codec-type";
      8     const char *hs_micbias_type = "qcom,msm-hs-micbias-type";
      9     const char *ext_pa = "qcom,msm-ext-pa";
     10     const char *mclk = "qcom,msm-mclk-freq";
     11     const char *spk_ext_pa = "qcom,msm-spk-ext-pa";
     12     const char *ptr = NULL;
     13     const char *type = NULL;
     14     const char *ext_pa_str = NULL;
     15     int num_strings;
     16     int ret, id, i;
     17 
     18     pr_err("'msm8x16_asoc_machine_probe ......");
     19     pdata = devm_kzalloc(&pdev->dev,
     20             sizeof(struct msm8916_asoc_mach_data), GFP_KERNEL);
     21     if (!pdata) {
     22         dev_err(&pdev->dev, "Can't allocate msm8x16_asoc_mach_data\n");
     23         ret = -ENOMEM;
     24         goto err1;
     25     }
     26 
     27     pdata->vaddr_gpio_mux_spkr_ctl =
     28         ioremap(LPASS_CSR_GP_IO_MUX_SPKR_CTL , 4);
     29     if (!pdata->vaddr_gpio_mux_spkr_ctl) {
     30         pr_err("%s ioremap failure for addr %x",
     31                 __func__, LPASS_CSR_GP_IO_MUX_SPKR_CTL);
     32         ret = -ENOMEM;
     33         goto err;
     34     }
     35     pdata->vaddr_gpio_mux_mic_ctl =
     36         ioremap(LPASS_CSR_GP_IO_MUX_MIC_CTL , 4);
     37     if (!pdata->vaddr_gpio_mux_mic_ctl) {
     38         pr_err("%s ioremap failure for addr %x",
     39                 __func__, LPASS_CSR_GP_IO_MUX_MIC_CTL);
     40         ret = -ENOMEM;
     41         goto err;
     42     }
     43 
     44     pdata->vaddr_gpio_mux_pcm_ctl =
     45         ioremap(LPASS_CSR_GP_LPAIF_PRI_PCM_PRI_MODE_MUXSEL, 4);
     46     if (!pdata->vaddr_gpio_mux_pcm_ctl) {
     47         pr_err("%s ioremap failure for addr %x",
     48                 __func__,
     49             LPASS_CSR_GP_LPAIF_PRI_PCM_PRI_MODE_MUXSEL);
     50         ret = -ENOMEM;
     51         goto err;
     52     }
     53     ret = of_property_read_u32(pdev->dev.of_node, card_dev_id, &id);
     54     if (ret) {
     55         dev_err(&pdev->dev,
     56             "%s: missing %s in dt node\n", __func__, card_dev_id);
     57         goto err;
     58     }
     59 
     60     pdev->id = id;
     61     if (!pdev->dev.of_node) {
     62         dev_err(&pdev->dev, "No platform supplied from device tree\n");
     63         ret = -EINVAL;
     64         goto err;
     65     }
     66 
     67     ret = of_property_read_u32(pdev->dev.of_node, mclk, &id);
     68     if (ret) {
     69         dev_err(&pdev->dev,
     70             "%s: missing %s in dt node\n", __func__, mclk);
     71         id = DEFAULT_MCLK_RATE;
     72     }
     73     pdata->mclk_freq = id;
     74 
     75     pdata->spk_ext_pa_gpio = of_get_named_gpio(pdev->dev.of_node,
     76                 spk_ext_pa, 0);
     77     if (pdata->spk_ext_pa_gpio < 0) {
     78         dev_dbg(&pdev->dev,
     79             "%s: missing %s in dt node\n", __func__, spk_ext_pa);
     80     } else {
     81         if (gpio_is_valid(pdata->spk_ext_pa_gpio)) {
     82             ret = gpio_request(pdata->spk_ext_pa_gpio, "spk_ext_pa_gpio");
     83             if(ret) {
     84                 pr_err("spk ext pa gpio request failed");
     85                 goto err;
     86             }
     87 
     88             ret = gpio_direction_output(pdata->spk_ext_pa_gpio, 1);
     89             if(ret) {
     90                 pr_err("set_direction for spk ext pa gpio failed\n");
     91                 goto err;
     92             }
     93         } else {
     94                 pr_err("%s: Invaild external_speaker gpio: %d", __func__, pdata->spk_ext_pa_gpio);
     95                 ret = -EINVAL;
     96                 goto err;
     97             }
     98             
     99     }
    100 
    101     ret = of_property_read_string(pdev->dev.of_node, codec_type, &ptr);
    102     if (ret) {
    103         dev_err(&pdev->dev,
    104             "%s: missing %s in dt node\n", __func__, codec_type);
    105         goto err;
    106     }
    107     if (pdev->id >= MAX_SND_CARDS) {
    108         dev_err(&pdev->dev, "Sound Card parsed is wrong, id=%d\n",
    109                 pdev->id);
    110         ret = -EINVAL;
    111         goto err;
    112     }
    113     if (!strcmp(ptr, "external")) {
    114         dev_info(&pdev->dev, "external codec is configured\n");
    115         pdata->codec_type = 1;
    116             /*Populate external codec TLMM configs*/
    117         ret = populate_ext_snd_card_dt_data(pdev);
    118         if (ret < 0) {
    119             dev_err(&pdev->dev, "error finding the DT\n"
    120                 "params ret=%d\n", ret);
    121             goto err;
    122         }
    123         card = populate_ext_snd_card_dailinks(pdev);
    124         if (!card) {
    125             dev_err(&pdev->dev, "%s: Card uninitialized\n",
    126                 __func__);
    127             ret = -EPROBE_DEFER;
    128             goto err;
    129         }
    130     } else {
    131         card = populate_ext_snd_card_dailinks(pdev);
    132         if (!card) {
    133             dev_err(&pdev->dev, "%s: Card uninitialized\n",
    134                 __func__);
    135             ret = -EPROBE_DEFER;
    136             goto err;
    137         }
    138         dev_info(&pdev->dev, "default codec configured\n");
    139         pdata->codec_type = 0;
    140         num_strings = of_property_count_strings(pdev->dev.of_node,
    141                 ext_pa);
    142         if (num_strings < 0) {
    143             dev_err(&pdev->dev,
    144             "%s: missing %s in dt node or length is incorrect\n",
    145                     __func__, ext_pa);
    146             goto err;
    147         }
    148         for (i = 0; i < num_strings; i++) {
    149             ret = of_property_read_string_index(pdev->dev.of_node,
    150                     ext_pa, i, &ext_pa_str);
    151             if (ret) {
    152                 dev_err(&pdev->dev,
    153                     "%s:of read string %s i %d error %d\n",
    154                         __func__, ext_pa, i, ret);
    155                 goto err;
    156             }
    157             if (!strcmp(ext_pa_str, "primary"))
    158                 pdata->ext_pa = (pdata->ext_pa | PRI_MI2S_ID);
    159             else if (!strcmp(ext_pa_str, "secondary"))
    160                 pdata->ext_pa = (pdata->ext_pa | SEC_MI2S_ID);
    161             else if (!strcmp(ext_pa_str, "tertiary"))
    162                 pdata->ext_pa = (pdata->ext_pa | TER_MI2S_ID);
    163             else if (!strcmp(ext_pa_str, "quaternary"))
    164                 pdata->ext_pa = (pdata->ext_pa | QUAT_MI2S_ID);
    165         }
    166         pr_debug("%s: ext_pa = %d\n", __func__, pdata->ext_pa);
    167         pinctrl = devm_pinctrl_get(&pdev->dev);
    168         if (IS_ERR(pinctrl)) {
    169             pr_err("%s: Unable to get pinctrl handle\n",
    170                     __func__);
    171             return -EINVAL;
    172         }
    173         pinctrl_info.pinctrl = pinctrl;
    174         ret = get_cdc_gpio_lines(pinctrl, pdata->ext_pa);
    175         if (ret < 0) {
    176             pr_err("%s: failed to ger the codec gpio's %d\n",
    177                     __func__, ret);
    178             goto err;
    179         }
    180     }
    181 
    182     ret = of_property_read_string(pdev->dev.of_node,
    183         hs_micbias_type, &type);
    184     if (ret) {
    185         dev_err(&pdev->dev, "%s: missing %s in dt node\n",
    186             __func__, hs_micbias_type);
    187         goto err;
    188     }
    189     if (!strcmp(type, "external")) {
    190         dev_dbg(&pdev->dev, "Headset is using external micbias\n");
    191         mbhc_cfg.hs_ext_micbias = true;
    192     } else {
    193         dev_dbg(&pdev->dev, "Headset is using internal micbias\n");
    194         mbhc_cfg.hs_ext_micbias = false;
    195     }
    196 
    197     /* initialize the mclk */
    198     pdata->digital_cdc_clk.i2s_cfg_minor_version =
    199                     AFE_API_VERSION_I2S_CONFIG;
    200     pdata->digital_cdc_clk.clk_val = pdata->mclk_freq;
    201     pdata->digital_cdc_clk.clk_root = 5;
    202     pdata->digital_cdc_clk.reserved = 0;
    203     /* initialize the digital codec core clk */
    204     pdata->digital_cdc_core_clk.clk_set_minor_version =
    205             AFE_API_VERSION_I2S_CONFIG;
    206     pdata->digital_cdc_core_clk.clk_id =
    207             Q6AFE_LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE;
    208     pdata->digital_cdc_core_clk.clk_freq_in_hz =
    209             pdata->mclk_freq;
    210     pdata->digital_cdc_core_clk.clk_attri =
    211             Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO;
    212     pdata->digital_cdc_core_clk.clk_root =
    213             Q6AFE_LPASS_CLK_ROOT_DEFAULT;
    214     pdata->digital_cdc_core_clk.enable = 1;
    215     /* Initialize loopback mode to false */
    216     pdata->lb_mode = false;
    217 
    218     msm8x16_setup_hs_jack(pdev, pdata);
    219     msm8x16_dt_parse_cap_info(pdev, pdata);
    220 
    221     card->dev = &pdev->dev;
    222     platform_set_drvdata(pdev, card);
    223     snd_soc_card_set_drvdata(card, pdata);
    224     ret = snd_soc_of_parse_card_name(card, "qcom,model");
    225     if (ret)
    226         goto err;
    227     /* initialize timer */
    228     INIT_DELAYED_WORK(&pdata->disable_mclk_work, disable_mclk);
    229     mutex_init(&pdata->cdc_mclk_mutex);
    230     atomic_set(&pdata->mclk_rsc_ref, 0);
    231     atomic_set(&pdata->mclk_enabled, false);
    232     atomic_set(&quat_mi2s_clk_ref, 0);
    233     atomic_set(&auxpcm_mi2s_clk_ref, 0);
    234 
    235     ret = snd_soc_of_parse_audio_routing(card,
    236             "qcom,audio-routing");
    237     if (ret)
    238         goto err;
    239 
    240     ret = msm8x16_populate_dai_link_component_of_node(card);
    241     if (ret) {
    242         ret = -EPROBE_DEFER;
    243         goto err;
    244     }
    245 
    246     ret = snd_soc_register_card(card);
    247     if (ret) {
    248         dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
    249             ret);
    250         goto err;
    251     }
    252 
    253     ret = core_get_adsp_ver();
    254     if (ret < 0) {
    255         ret = -EPROBE_DEFER;
    256         dev_info(&pdev->dev, "%s: Get adsp version failed (%d)\n",
    257                 __func__, ret);
    258         goto err;
    259     }
    260 
    261     return 0;
    262 err:
    263     if (pdata->vaddr_gpio_mux_spkr_ctl)
    264         iounmap(pdata->vaddr_gpio_mux_spkr_ctl);
    265     if (pdata->vaddr_gpio_mux_mic_ctl)
    266         iounmap(pdata->vaddr_gpio_mux_mic_ctl);
    267     if (pdata->vaddr_gpio_mux_pcm_ctl)
    268         iounmap(pdata->vaddr_gpio_mux_pcm_ctl);
    269     if(gpio_is_valid(pdata->spk_ext_pa_gpio))
    270         gpio_free(pdata->spk_ext_pa_gpio);
    271     devm_kfree(&pdev->dev, pdata);
    272 err1:
    273     return ret;
    274 }

    DTS:

     1 sound {
     2         compatible = "qcom,msm8x16-audio-codec";
     3         qcom,model = "msm8x16-skui-snd-card";
     4         qcom,msm-snd-card-id = <0>;
     5         qcom,msm-ext-pa = "secondary";//"primary";
     6         qcom,msm-codec-type = "internal";
     7         qcom,msm-mbhc-hphl-swh = <0>;
     8         qcom,msm-mbhc-gnd-swh = <1>;
     9         qcom,msm-hs-micbias-type = "internal";
    10         qcom,audio-routing =
    11             "RX_BIAS", "MCLK",
    12             "SPK_RX_BIAS", "MCLK",
    13             "INT_LDO_H", "MCLK",
    14             "MIC BIAS Internal1", "Handset Mic",
    15             "MIC BIAS Internal2", "Headset Mic",
    16             "MIC BIAS Internal1", "Secondary Mic",
    17             "AMIC1", "MIC BIAS Internal1",
    18             "AMIC2", "MIC BIAS Internal2",
    19             "AMIC3", "MIC BIAS Internal1";
    20         pinctrl-names = "cdc_lines_act",
    21                 "cdc_lines_sus",
    22                 "cdc_lines_sec_ext_act",
    23                 "cdc_lines_sec_ext_sus";
    24         pinctrl-0 = <&cdc_pdm_lines_act>;
    25         pinctrl-1 = <&cdc_pdm_lines_sus>;
    26         pinctrl-2 = <&cdc_pdm_lines_act &cdc_ext_pa_act &cdc_ext_pa_ws_act>;
    27         pinctrl-3 = <&cdc_pdm_lines_sus &cdc_ext_pa_sus &cdc_ext_pa_ws_sus>;
    28     };

    通过与DTS匹配,开始分析:

    (1)、获取card的id:

    1 ret = of_property_read_u32(pdev->dev.of_node, card_dev_id, &id);

    (2)、设置card的名字:

    1 pdev->id = id;
    2 dev_set_name(&pdev->dev, "%s.%d", "msm-snd-card", id);

    (3)、设置codec的类型为external还是internal的:

    1 ret = of_property_read_string(pdev->dev.of_node, codec_type, &ptr);
    2     if (ret) {
    3         dev_err(&pdev->dev,
    4             "%s: missing %s in dt node\n", __func__, codec_type);
    5         goto err;
    6     }

    (4)、根据external还是internal的card,进入相应的处理函数中:

    假设进入internal card:

    1 card = &bear_cards[pdev->id];
    2 bear_cards[pdev->id].name = dev_name(&pdev->dev);

    在这里,bear_cards是一个snd_soc_card的结构体,由设备树又可知,id=0:

     1 static struct snd_soc_card bear_cards[MAX_SND_CARDS] = {
     2     /* snd_soc_card_msm8x16 */
     3     {
     4         .name        = "msm8x16-snd-card",
     5         .dai_link    = msm8x16_dai,
     6         .num_links    = ARRAY_SIZE(msm8x16_dai),
     7     },
     8     {
     9         .name        = "msm8x16-tapan-snd-card",
    10         .dai_link    = msm8x16_9306_dai_links,
    11         .num_links    = ARRAY_SIZE(msm8x16_9306_dai_links),
    12     },
    13     {
    14         .name        = "msm8x16-tapan9302-snd-card",
    15         .dai_link    = msm8x16_9302_dai_links,
    16         .num_links    = ARRAY_SIZE(msm8x16_9302_dai_links),
    17     },
    18 };

    所以用到的只有msm8x16_dai;

      1 /* Digital audio interface glue - connects codec <---> CPU */
      2 static struct snd_soc_dai_link msm8x16_dai[] = {
      3     /* FrontEnd DAI Links */
      4     {/* hw:x,0 */
      5         .name = "MSM8X16 Media1",
      6         .stream_name = "MultiMedia1",
      7         .cpu_dai_name    = "MultiMedia1",
      8         .platform_name  = "msm-pcm-dsp.0",
      9         .dynamic = 1,
     10         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     11             SND_SOC_DPCM_TRIGGER_POST},
     12         .codec_dai_name = "snd-soc-dummy-dai",
     13         .codec_name = "snd-soc-dummy",
     14         .ignore_suspend = 1,
     15         /* this dainlink has playback support */
     16         .ignore_pmdown_time = 1,
     17         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1
     18     },
     19     {/* hw:x,1 */
     20         .name = "MSM8X16 Media2",
     21         .stream_name = "MultiMedia2",
     22         .cpu_dai_name   = "MultiMedia2",
     23         .platform_name  = "msm-pcm-dsp.0",
     24         .dynamic = 1,
     25         .codec_dai_name = "snd-soc-dummy-dai",
     26         .codec_name = "snd-soc-dummy",
     27         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     28             SND_SOC_DPCM_TRIGGER_POST},
     29         .ignore_suspend = 1,
     30         /* this dainlink has playback support */
     31         .ignore_pmdown_time = 1,
     32         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2,
     33     },
     34     {/* hw:x,2 */
     35         .name = "Circuit-Switch Voice",
     36         .stream_name = "CS-Voice",
     37         .cpu_dai_name   = "CS-VOICE",
     38         .platform_name  = "msm-pcm-voice",
     39         .dynamic = 1,
     40         .codec_dai_name = "snd-soc-dummy-dai",
     41         .codec_name = "snd-soc-dummy",
     42         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     43             SND_SOC_DPCM_TRIGGER_POST},
     44         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
     45         .ignore_suspend = 1,
     46         /* this dainlink has playback support */
     47         .ignore_pmdown_time = 1,
     48         .be_id = MSM_FRONTEND_DAI_CS_VOICE,
     49     },
     50     {/* hw:x,3 */
     51         .name = "MSM VoIP",
     52         .stream_name = "VoIP",
     53         .cpu_dai_name    = "VoIP",
     54         .platform_name  = "msm-voip-dsp",
     55         .dynamic = 1,
     56         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     57             SND_SOC_DPCM_TRIGGER_POST},
     58         .codec_dai_name = "snd-soc-dummy-dai",
     59         .codec_name = "snd-soc-dummy",
     60         .ignore_suspend = 1,
     61         /* this dainlink has playback support */
     62         .ignore_pmdown_time = 1,
     63         .be_id = MSM_FRONTEND_DAI_VOIP,
     64     },
     65     {/* hw:x,4 */
     66         .name = "MSM8X16 LPA",
     67         .stream_name = "LPA",
     68         .cpu_dai_name    = "MultiMedia3",
     69         .platform_name  = "msm-pcm-lpa",
     70         .dynamic = 1,
     71         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     72             SND_SOC_DPCM_TRIGGER_POST},
     73         .codec_dai_name = "snd-soc-dummy-dai",
     74         .codec_name = "snd-soc-dummy",
     75         .ignore_suspend = 1,
     76         /* this dainlink has playback support */
     77         .ignore_pmdown_time = 1,
     78         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3,
     79     },
     80     /* Hostless PCM purpose */
     81     {/* hw:x,5 */
     82         .name = "Primary MI2S_RX Hostless",
     83         .stream_name = "Primary MI2S_RX Hostless",
     84         .cpu_dai_name = "PRI_MI2S_RX_HOSTLESS",
     85         .platform_name    = "msm-pcm-hostless",
     86         .dynamic = 1,
     87         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
     88                 SND_SOC_DPCM_TRIGGER_POST},
     89         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
     90         .ignore_suspend = 1,
     91         .ignore_pmdown_time = 1,
     92          /* This dainlink has MI2S support */
     93         .codec_dai_name = "snd-soc-dummy-dai",
     94         .codec_name = "snd-soc-dummy",
     95     },
     96     {/* hw:x,6 */
     97         .name = "INT_FM Hostless",
     98         .stream_name = "INT_FM Hostless",
     99         .cpu_dai_name    = "INT_FM_HOSTLESS",
    100         .platform_name  = "msm-pcm-hostless",
    101         .dynamic = 1,
    102         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    103             SND_SOC_DPCM_TRIGGER_POST},
    104         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    105         .ignore_suspend = 1,
    106         /* this dainlink has playback support */
    107         .ignore_pmdown_time = 1,
    108         .codec_dai_name = "snd-soc-dummy-dai",
    109         .codec_name = "snd-soc-dummy",
    110     },
    111     {/* hw:x,7 */
    112         .name = "MSM AFE-PCM RX",
    113         .stream_name = "AFE-PROXY RX",
    114         .cpu_dai_name = "msm-dai-q6-dev.241",
    115         .codec_name = "msm-stub-codec.1",
    116         .codec_dai_name = "msm-stub-rx",
    117         .platform_name  = "msm-pcm-afe",
    118         .ignore_suspend = 1,
    119         /* this dainlink has playback support */
    120         .ignore_pmdown_time = 1,
    121     },
    122     {/* hw:x,8 */
    123         .name = "MSM AFE-PCM TX",
    124         .stream_name = "AFE-PROXY TX",
    125         .cpu_dai_name = "msm-dai-q6-dev.240",
    126         .codec_name = "msm-stub-codec.1",
    127         .codec_dai_name = "msm-stub-tx",
    128         .platform_name  = "msm-pcm-afe",
    129         .ignore_suspend = 1,
    130     },
    131     {/* hw:x,9 */
    132         .name = "MSM8X16 Compr",
    133         .stream_name = "COMPR",
    134         .cpu_dai_name    = "MultiMedia4",
    135         .platform_name  = "msm-compress-dsp",
    136         .dynamic = 1,
    137         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    138              SND_SOC_DPCM_TRIGGER_POST},
    139         .codec_dai_name = "snd-soc-dummy-dai",
    140         .codec_name = "snd-soc-dummy",
    141         .ignore_suspend = 1,
    142         .ignore_pmdown_time = 1,
    143          /* this dainlink has playback support */
    144         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA4,
    145     },
    146     {/* hw:x,10 */
    147         .name = "AUXPCM Hostless",
    148         .stream_name = "AUXPCM Hostless",
    149         .cpu_dai_name   = "AUXPCM_HOSTLESS",
    150         .platform_name  = "msm-pcm-hostless",
    151         .dynamic = 1,
    152         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    153             SND_SOC_DPCM_TRIGGER_POST},
    154         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    155         .ignore_suspend = 1,
    156         /* this dainlink has playback support */
    157         .ignore_pmdown_time = 1,
    158         .codec_dai_name = "snd-soc-dummy-dai",
    159         .codec_name = "snd-soc-dummy",
    160     },
    161     {/* hw:x,11 */
    162         .name = "Tertiary MI2S_TX Hostless",
    163         .stream_name = "Tertiary MI2S_TX Hostless",
    164         .cpu_dai_name = "TERT_MI2S_TX_HOSTLESS",
    165         .platform_name  = "msm-pcm-hostless",
    166         .dynamic = 1,
    167         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    168                 SND_SOC_DPCM_TRIGGER_POST},
    169         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    170         .ignore_suspend = 1,
    171         .ignore_pmdown_time = 1, /* dai link has playback support */
    172         .codec_dai_name = "snd-soc-dummy-dai",
    173         .codec_name = "snd-soc-dummy",
    174     },
    175     {/* hw:x,12 */
    176         .name = "MSM8x16 LowLatency",
    177         .stream_name = "MultiMedia5",
    178         .cpu_dai_name   = "MultiMedia5",
    179         .platform_name  = "msm-pcm-dsp.1",
    180         .dynamic = 1,
    181         .codec_dai_name = "snd-soc-dummy-dai",
    182         .codec_name = "snd-soc-dummy",
    183         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    184                 SND_SOC_DPCM_TRIGGER_POST},
    185         .ignore_suspend = 1,
    186         /* this dainlink has playback support */
    187         .ignore_pmdown_time = 1,
    188         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA5,
    189     },
    190     {/* hw:x,13 */
    191         .name = "Voice2",
    192         .stream_name = "Voice2",
    193         .cpu_dai_name   = "Voice2",
    194         .platform_name  = "msm-pcm-voice",
    195         .dynamic = 1,
    196         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    197                 SND_SOC_DPCM_TRIGGER_POST},
    198         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    199         .ignore_suspend = 1,
    200         /* this dainlink has playback support */
    201         .ignore_pmdown_time = 1,
    202         .codec_dai_name = "snd-soc-dummy-dai",
    203         .codec_name = "snd-soc-dummy",
    204     },
    205     {/* hw:x,14 */
    206         .name = "MSM8x16 Media9",
    207         .stream_name = "MultiMedia9",
    208         .cpu_dai_name   = "MultiMedia9",
    209         .platform_name  = "msm-pcm-dsp.0",
    210         .dynamic = 1,
    211         .codec_dai_name = "snd-soc-dummy-dai",
    212         .codec_name = "snd-soc-dummy",
    213         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    214                 SND_SOC_DPCM_TRIGGER_POST},
    215         .ignore_suspend = 1,
    216         /* This dailink has playback support */
    217         .ignore_pmdown_time = 1,
    218         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA9,
    219     },
    220     { /* hw:x,15 */
    221         .name = "VoLTE",
    222         .stream_name = "VoLTE",
    223         .cpu_dai_name   = "VoLTE",
    224         .platform_name  = "msm-pcm-voice",
    225         .dynamic = 1,
    226         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    227                 SND_SOC_DPCM_TRIGGER_POST},
    228         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    229         .ignore_suspend = 1,
    230         /* this dainlink has playback support */
    231         .ignore_pmdown_time = 1,
    232         .codec_dai_name = "snd-soc-dummy-dai",
    233         .codec_name = "snd-soc-dummy",
    234         .be_id = MSM_FRONTEND_DAI_VOLTE,
    235     },
    236     { /* hw:x,16 */
    237         .name = "VoWLAN",
    238         .stream_name = "VoWLAN",
    239         .cpu_dai_name   = "VoWLAN",
    240         .platform_name  = "msm-pcm-voice",
    241         .dynamic = 1,
    242         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    243                 SND_SOC_DPCM_TRIGGER_POST},
    244         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    245         .ignore_suspend = 1,
    246         .ignore_pmdown_time = 1,
    247         .codec_dai_name = "snd-soc-dummy-dai",
    248         .codec_name = "snd-soc-dummy",
    249         .be_id = MSM_FRONTEND_DAI_VOWLAN,
    250     },
    251     {/* hw:x,17 */
    252         .name = "INT_HFP_BT Hostless",
    253         .stream_name = "INT_HFP_BT Hostless",
    254         .cpu_dai_name = "INT_HFP_BT_HOSTLESS",
    255         .platform_name  = "msm-pcm-hostless",
    256         .dynamic = 1,
    257         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    258                 SND_SOC_DPCM_TRIGGER_POST},
    259         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    260         .ignore_suspend = 1,
    261         /* this dai link has playback support */
    262         .ignore_pmdown_time = 1,
    263         .codec_dai_name = "snd-soc-dummy-dai",
    264         .codec_name = "snd-soc-dummy",
    265     },
    266     {/* hw:x,18 */
    267         .name = "MSM8916 HFP TX",
    268         .stream_name = "MultiMedia6",
    269         .cpu_dai_name = "MultiMedia6",
    270         .platform_name  = "msm-pcm-loopback",
    271         .dynamic = 1,
    272         .codec_dai_name = "snd-soc-dummy-dai",
    273         .codec_name = "snd-soc-dummy",
    274         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    275                 SND_SOC_DPCM_TRIGGER_POST},
    276         .ignore_suspend = 1,
    277         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    278         /* this dai link has playback support */
    279         .ignore_pmdown_time = 1,
    280         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA6,
    281     },
    282     /* LSM FE */
    283     {/* hw:x,19 */
    284         .name = "Listen 1 Audio Service",
    285         .stream_name = "Listen 1 Audio Service",
    286         .cpu_dai_name = "LSM1",
    287         .platform_name = "msm-lsm-client",
    288         .dynamic = 1,
    289         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    290                 SND_SOC_DPCM_TRIGGER_POST },
    291         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    292         .ignore_suspend = 1,
    293         .ignore_pmdown_time = 1,
    294         .codec_dai_name = "snd-soc-dummy-dai",
    295         .codec_name = "snd-soc-dummy",
    296         .be_id = MSM_FRONTEND_DAI_LSM1,
    297     },
    298     {/* hw:x,20 */
    299         .name = "Listen 2 Audio Service",
    300         .stream_name = "Listen 2 Audio Service",
    301         .cpu_dai_name = "LSM2",
    302         .platform_name = "msm-lsm-client",
    303         .dynamic = 1,
    304         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    305                 SND_SOC_DPCM_TRIGGER_POST },
    306         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    307         .ignore_suspend = 1,
    308         .ignore_pmdown_time = 1,
    309         .codec_dai_name = "snd-soc-dummy-dai",
    310         .codec_name = "snd-soc-dummy",
    311         .be_id = MSM_FRONTEND_DAI_LSM2,
    312     },
    313     {/* hw:x,21 */
    314         .name = "Listen 3 Audio Service",
    315         .stream_name = "Listen 3 Audio Service",
    316         .cpu_dai_name = "LSM3",
    317         .platform_name = "msm-lsm-client",
    318         .dynamic = 1,
    319         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    320                 SND_SOC_DPCM_TRIGGER_POST },
    321         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    322         .ignore_suspend = 1,
    323         .ignore_pmdown_time = 1,
    324         .codec_dai_name = "snd-soc-dummy-dai",
    325         .codec_name = "snd-soc-dummy",
    326         .be_id = MSM_FRONTEND_DAI_LSM3,
    327     },
    328     {/* hw:x,22 */
    329         .name = "Listen 4 Audio Service",
    330         .stream_name = "Listen 4 Audio Service",
    331         .cpu_dai_name = "LSM4",
    332         .platform_name = "msm-lsm-client",
    333         .dynamic = 1,
    334         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    335                 SND_SOC_DPCM_TRIGGER_POST },
    336         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    337         .ignore_suspend = 1,
    338         .ignore_pmdown_time = 1,
    339         .codec_dai_name = "snd-soc-dummy-dai",
    340         .codec_name = "snd-soc-dummy",
    341         .be_id = MSM_FRONTEND_DAI_LSM4,
    342     },
    343     {/* hw:x,23 */
    344         .name = "Listen 5 Audio Service",
    345         .stream_name = "Listen 5 Audio Service",
    346         .cpu_dai_name = "LSM5",
    347         .platform_name = "msm-lsm-client",
    348         .dynamic = 1,
    349         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    350                 SND_SOC_DPCM_TRIGGER_POST },
    351         .no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
    352         .ignore_suspend = 1,
    353         .ignore_pmdown_time = 1,
    354         .codec_dai_name = "snd-soc-dummy-dai",
    355         .codec_name = "snd-soc-dummy",
    356         .be_id = MSM_FRONTEND_DAI_LSM5,
    357     },
    358     {/* hw:x,24 */
    359         .name = "MSM8916 ULL",
    360         .stream_name = "MultiMedia7",
    361         .cpu_dai_name   = "MultiMedia7",
    362         .platform_name  = "msm-pcm-dsp.1",
    363         .dynamic = 1,
    364         .codec_dai_name = "snd-soc-dummy-dai",
    365         .codec_name = "snd-soc-dummy",
    366         .trigger = {SND_SOC_DPCM_TRIGGER_POST,
    367                 SND_SOC_DPCM_TRIGGER_POST},
    368         .ignore_suspend = 1,
    369         /* this dainlink has playback support */
    370         .ignore_pmdown_time = 1,
    371         .be_id = MSM_FRONTEND_DAI_MULTIMEDIA7,
    372     },
    373     /* Backend I2S DAI Links */
    374     {
    375         .name = LPASS_BE_PRI_MI2S_RX,
    376         .stream_name = "Primary MI2S Playback",
    377         .cpu_dai_name = "msm-dai-q6-mi2s.0",
    378         .platform_name = "msm-pcm-routing",
    379         .codec_name     = MSM8X16_CODEC_NAME,
    380         .codec_dai_name = "msm8x16_wcd_i2s_rx1",
    381         .no_pcm = 1,
    382         .be_id = MSM_BACKEND_DAI_PRI_MI2S_RX,
    383         .init = &msm_audrx_init,
    384         .be_hw_params_fixup = msm_pri_rx_be_hw_params_fixup,
    385         .ops = &msm8x16_mi2s_be_ops,
    386         .ignore_suspend = 1,
    387     },
    388     {
    389         .name = LPASS_BE_SEC_MI2S_RX,
    390         .stream_name = "Secondary MI2S Playback",
    391         .cpu_dai_name = "msm-dai-q6-mi2s.1",
    392         .platform_name = "msm-pcm-routing",
    393         .codec_name = "msm-stub-codec.1",
    394         .codec_dai_name = "msm-stub-rx",
    395         .no_pcm = 1,
    396         .be_id = MSM_BACKEND_DAI_SECONDARY_MI2S_RX,
    397         .be_hw_params_fixup = msm_be_hw_params_fixup,
    398         .ops = &msm8x16_sec_mi2s_be_ops,
    399         .ignore_suspend = 1,
    400     },
    401     {
    402         .name = LPASS_BE_TERT_MI2S_TX,
    403         .stream_name = "Tertiary MI2S Capture",
    404         .cpu_dai_name = "msm-dai-q6-mi2s.2",
    405         .platform_name = "msm-pcm-routing",
    406         .codec_name     = MSM8X16_CODEC_NAME,
    407         .codec_dai_name = "msm8x16_wcd_i2s_tx1",
    408         .no_pcm = 1,
    409         .be_id = MSM_BACKEND_DAI_TERTIARY_MI2S_TX,
    410         .be_hw_params_fixup = msm_tx_be_hw_params_fixup,
    411         .ops = &msm8x16_mi2s_be_ops,
    412         .ignore_suspend = 1,
    413     },
    414     {
    415         .name = LPASS_BE_INT_BT_SCO_RX,
    416         .stream_name = "Internal BT-SCO Playback",
    417         .cpu_dai_name = "msm-dai-q6-dev.12288",
    418         .platform_name = "msm-pcm-routing",
    419         .codec_name = "msm-stub-codec.1",
    420         .codec_dai_name    = "msm-stub-rx",
    421         .no_pcm = 1,
    422         .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX,
    423         .be_hw_params_fixup = msm_btsco_be_hw_params_fixup,
    424         /* this dainlink has playback support */
    425         .ignore_pmdown_time = 1,
    426         .ignore_suspend = 1,
    427     },
    428     {
    429         .name = LPASS_BE_INT_BT_SCO_TX,
    430         .stream_name = "Internal BT-SCO Capture",
    431         .cpu_dai_name = "msm-dai-q6-dev.12289",
    432         .platform_name = "msm-pcm-routing",
    433         .codec_name = "msm-stub-codec.1",
    434         .codec_dai_name    = "msm-stub-tx",
    435         .no_pcm = 1,
    436         .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX,
    437         .be_hw_params_fixup = msm_btsco_be_hw_params_fixup,
    438         .ignore_suspend = 1,
    439     },
    440     {
    441         .name = LPASS_BE_INT_FM_RX,
    442         .stream_name = "Internal FM Playback",
    443         .cpu_dai_name = "msm-dai-q6-dev.12292",
    444         .platform_name = "msm-pcm-routing",
    445         .codec_name = "msm-stub-codec.1",
    446         .codec_dai_name = "msm-stub-rx",
    447         .no_pcm = 1,
    448         .be_id = MSM_BACKEND_DAI_INT_FM_RX,
    449         .be_hw_params_fixup = msm_be_hw_params_fixup,
    450         /* this dainlink has playback support */
    451         .ignore_pmdown_time = 1,
    452         .ignore_suspend = 1,
    453     },
    454     {
    455         .name = LPASS_BE_INT_FM_TX,
    456         .stream_name = "Internal FM Capture",
    457         .cpu_dai_name = "msm-dai-q6-dev.12293",
    458         .platform_name = "msm-pcm-routing",
    459         .codec_name = "msm-stub-codec.1",
    460         .codec_dai_name = "msm-stub-tx",
    461         .no_pcm = 1,
    462         .be_id = MSM_BACKEND_DAI_INT_FM_TX,
    463         .be_hw_params_fixup = msm_be_hw_params_fixup,
    464         .ignore_suspend = 1,
    465     },
    466     {
    467         .name = LPASS_BE_AFE_PCM_RX,
    468         .stream_name = "AFE Playback",
    469         .cpu_dai_name = "msm-dai-q6-dev.224",
    470         .platform_name = "msm-pcm-routing",
    471         .codec_name = "msm-stub-codec.1",
    472         .codec_dai_name = "msm-stub-rx",
    473         .no_pcm = 1,
    474         .be_id = MSM_BACKEND_DAI_AFE_PCM_RX,
    475         .be_hw_params_fixup = msm_proxy_rx_be_hw_params_fixup,
    476         /* this dainlink has playback support */
    477         .ignore_pmdown_time = 1,
    478         .ignore_suspend = 1,
    479     },
    480     {
    481         .name = LPASS_BE_AFE_PCM_TX,
    482         .stream_name = "AFE Capture",
    483         .cpu_dai_name = "msm-dai-q6-dev.225",
    484         .platform_name = "msm-pcm-routing",
    485         .codec_name = "msm-stub-codec.1",
    486         .codec_dai_name = "msm-stub-tx",
    487         .no_pcm = 1,
    488         .be_id = MSM_BACKEND_DAI_AFE_PCM_TX,
    489         .be_hw_params_fixup = msm_proxy_tx_be_hw_params_fixup,
    490         .ignore_suspend = 1,
    491     },
    492     /* Incall Record Uplink BACK END DAI Link */
    493     {
    494         .name = LPASS_BE_INCALL_RECORD_TX,
    495         .stream_name = "Voice Uplink Capture",
    496         .cpu_dai_name = "msm-dai-q6-dev.32772",
    497         .platform_name = "msm-pcm-routing",
    498         .codec_name     = "msm-stub-codec.1",
    499         .codec_dai_name = "msm-stub-tx",
    500         .no_pcm = 1,
    501         .be_id = MSM_BACKEND_DAI_INCALL_RECORD_TX,
    502         .be_hw_params_fixup = msm_be_hw_params_fixup,
    503         .ignore_suspend = 1,
    504     },
    505     /* Incall Record Downlink BACK END DAI Link */
    506     {
    507         .name = LPASS_BE_INCALL_RECORD_RX,
    508         .stream_name = "Voice Downlink Capture",
    509         .cpu_dai_name = "msm-dai-q6-dev.32771",
    510         .platform_name = "msm-pcm-routing",
    511         .codec_name     = "msm-stub-codec.1",
    512         .codec_dai_name = "msm-stub-tx",
    513         .no_pcm = 1,
    514         .be_id = MSM_BACKEND_DAI_INCALL_RECORD_RX,
    515         .be_hw_params_fixup = msm_be_hw_params_fixup,
    516         .ignore_suspend = 1,
    517     },
    518     /* Incall Music BACK END DAI Link */
    519     {
    520         .name = LPASS_BE_VOICE_PLAYBACK_TX,
    521         .stream_name = "Voice Farend Playback",
    522         .cpu_dai_name = "msm-dai-q6-dev.32773",
    523         .platform_name = "msm-pcm-routing",
    524         .codec_name     = "msm-stub-codec.1",
    525         .codec_dai_name = "msm-stub-rx",
    526         .no_pcm = 1,
    527         .be_id = MSM_BACKEND_DAI_VOICE_PLAYBACK_TX,
    528         .be_hw_params_fixup = msm_be_hw_params_fixup,
    529         .ignore_suspend = 1,
    530     },
    531     /* Incall Music 2 BACK END DAI Link */
    532     {
    533         .name = LPASS_BE_VOICE2_PLAYBACK_TX,
    534         .stream_name = "Voice2 Farend Playback",
    535         .cpu_dai_name = "msm-dai-q6-dev.32770",
    536         .platform_name = "msm-pcm-routing",
    537         .codec_name     = "msm-stub-codec.1",
    538         .codec_dai_name = "msm-stub-rx",
    539         .no_pcm = 1,
    540         .be_id = MSM_BACKEND_DAI_VOICE2_PLAYBACK_TX,
    541         .be_hw_params_fixup = msm_be_hw_params_fixup,
    542         .ignore_suspend = 1,
    543     },
    544 };

    其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。

    当然还要实现连接Platform和Codec的dai_link对应的ops实现;

    msm8x16_sec_mi2s_be_ops的结构体是一个函数指针结构体,里面注册了相应的回调函数:

    1 static struct snd_soc_ops msm8x16_mi2s_be_ops = {
    2     .startup = msm_mi2s_snd_startup,
    3     .hw_params = msm_mi2s_snd_hw_params,
    4     .shutdown = msm_mi2s_snd_shutdown,
    5 };

    在高通平台中,这primary_mi2s这一路i2s,都是留给内部codec用的,所以,这路的dai_link上的codec_name和codec_dai_name,就是对应着内部codec的信息:

     1 /* Backend I2S DAI Links */
     2 {
     3         .name = LPASS_BE_PRI_MI2S_RX,
     4         .stream_name = "Primary MI2S Playback",
     5         .cpu_dai_name = "msm-dai-q6-mi2s.0",
     6         .platform_name = "msm-pcm-routing",
     7         .codec_name     = MSM8X16_CODEC_NAME,
     8         .codec_dai_name = "msm8x16_wcd_i2s_rx1",
     9         .no_pcm = 1,
    10         .be_id = MSM_BACKEND_DAI_PRI_MI2S_RX,
    11         .init = &msm_audrx_init,
    12         .be_hw_params_fixup = msm_pri_rx_be_hw_params_fixup,
    13         .ops = &msm8x16_mi2s_be_ops,
    14         .ignore_suspend = 1,
    15 },

    从msm8x16_wcd_i2s_rx1我们便可以找到高通平台默认的msm8x16-wcd.c,在该文件中,注册了snd_soc_codec_driver:

    (5)、注册相应的驱动:

    如何匹配?

     那这里就要谈论一个问题,在初始化的时候,如何凭借dai_link中的codec信息找到对应的codec,答案是codec_name。但注意,这里并不是通过这个名字直接寻找的,例如8916平台。 

    这下就来到重要的函数:

    1 ret = snd_soc_register_card(card);
    2 if (ret) {
    3         dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
    4             ret);
    5         goto err;
    6 }
     1 /**
     2  * snd_soc_register_card - Register a card with the ASoC core
     3  *
     4  * @card: Card to register
     5  *
     6  */
     7 int snd_soc_register_card(struct snd_soc_card *card)
     8 {
     9     int i, ret;
    10 
    11     if (!card->name || !card->dev)
    12         return -EINVAL;
    13 
    14     for (i = 0; i < card->num_links; i++) {
    15         struct snd_soc_dai_link *link = &card->dai_link[i];
    16 
    17         /*
    18          * Codec must be specified by 1 of name or OF node,
    19          * not both or neither.
    20          */
    21         if (!!link->codec_name == !!link->codec_of_node) {
    22             dev_err(card->dev, "ASoC: Neither/both codec"
    23                 " name/of_node are set for %s\n", link->name);
    24             return -EINVAL;
    25         }
    26         /* Codec DAI name must be specified */
    27         if (!link->codec_dai_name) {
    28             dev_err(card->dev, "ASoC: codec_dai_name not"
    29                 " set for %s\n", link->name);
    30             return -EINVAL;
    31         }
    32 
    33         /*
    34          * Platform may be specified by either name or OF node, but
    35          * can be left unspecified, and a dummy platform will be used.
    36          */
    37         if (link->platform_name && link->platform_of_node) {
    38             dev_err(card->dev, "ASoC: Both platform name/of_node"
    39                 " are set for %s\n", link->name);
    40             return -EINVAL;
    41         }
    42 
    43         /*
    44          * CPU device may be specified by either name or OF node, but
    45          * can be left unspecified, and will be matched based on DAI
    46          * name alone..
    47          */
    48         if (link->cpu_name && link->cpu_of_node) {
    49             dev_err(card->dev, "ASoC: Neither/both "
    50                 "cpu name/of_node are set for %s\n",link->name);
    51             return -EINVAL;
    52         }
    53         /*
    54          * At least one of CPU DAI name or CPU device name/node must be
    55          * specified
    56          */
    57         if (!link->cpu_dai_name &&
    58             !(link->cpu_name || link->cpu_of_node)) {
    59             dev_err(card->dev, "ASoC: Neither cpu_dai_name nor "
    60                 "cpu_name/of_node are set for %s\n", link->name);
    61             return -EINVAL;
    62         }
    63     }
    64 
    65     dev_set_drvdata(card->dev, card);
    66 
    67     snd_soc_initialize_card_lists(card);
    68 
    69     soc_init_card_debugfs(card);
    70 
    71     card->rtd = devm_kzalloc(card->dev,
    72                  sizeof(struct snd_soc_pcm_runtime) *
    73                  (card->num_links + card->num_aux_devs),
    74                  GFP_KERNEL);
    75     if (card->rtd == NULL)
    76         return -ENOMEM;
    77     card->num_rtd = 0;
    78     card->rtd_aux = &card->rtd[card->num_links];
    79 
    80     for (i = 0; i < card->num_links; i++)
    81         card->rtd[i].dai_link = &card->dai_link[i];
    82 
    83     INIT_LIST_HEAD(&card->list);
    84     INIT_LIST_HEAD(&card->dapm_dirty);
    85     card->instantiated = 0;
    86     mutex_init(&card->mutex);
    87     mutex_init(&card->dapm_mutex);
    88     mutex_init(&card->dapm_power_mutex);
    89     ret = snd_soc_instantiate_card(card);
    90     if (ret != 0)
    91         soc_cleanup_card_debugfs(card);
    92 
    93     return ret;
    94 }

     将link指针遍历msm8x16_dai结构体数组的每一个成员:

    1 for (i = 0; i < card->num_links; i++) {
    2     struct snd_soc_dai_link *link = &card->dai_link[i];

     这里定义了codec_name,名字为msm8x16_wcd_codec,所以不执行if的内容:

    1 if (!!link->codec_name == !!link->codec_of_node) {
    2             dev_err(card->dev, "ASoC: Neither/both codec"
    3                 " name/of_node are set for %s\n", link->name);
    4             return -EINVAL;
    5 }

      同理,下面的函数也是一样:

     1 /* Codec DAI name must be specified */
     2         if (!link->codec_dai_name) {
     3             dev_err(card->dev, "ASoC: codec_dai_name not"
     4                 " set for %s\n", link->name);
     5             return -EINVAL;
     6         }
     7 
     8         /*
     9          * Platform may be specified by either name or OF node, but
    10          * can be left unspecified, and a dummy platform will be used.
    11          */
    12         if (link->platform_name && link->platform_of_node) {
    13             dev_err(card->dev, "ASoC: Both platform name/of_node"
    14                 " are set for %s\n", link->name);
    15             return -EINVAL;
    16         }
    17 
    18         /*
    19          * CPU device may be specified by either name or OF node, but
    20          * can be left unspecified, and will be matched based on DAI
    21          * name alone..
    22          */
    23         if (link->cpu_name && link->cpu_of_node) {
    24             dev_err(card->dev, "ASoC: Neither/both "
    25                 "cpu name/of_node are set for %s\n",link->name);
    26             return -EINVAL;
    27         }
    28         /*
    29          * At least one of CPU DAI name or CPU device name/node must be
    30          * specified
    31          */
    32         if (!link->cpu_dai_name &&
    33             !(link->cpu_name || link->cpu_of_node)) {
    34             dev_err(card->dev, "ASoC: Neither cpu_dai_name nor "
    35                 "cpu_name/of_node are set for %s\n", link->name);
    36             return -EINVAL;
    37         }

     继续向下看:

     1        //设置声卡设备驱动信息
     2        dev_set_drvdata(card->dev,card);
     3        //初始化声卡设备列表
     4        snd_soc_initialize_card_lists(card);
     5  
     6        soc_init_card_debugfs(card);
     7        //为声卡中的snd_soc_pcm_runtime数据结构分配内存空间
     8        card->rtd= devm_kzalloc(card->dev,
     9                              sizeof(struct snd_soc_pcm_runtime) *
    10                              (card->num_links + card->num_aux_devs),
    11                              GFP_KERNEL);
    12        if(card->rtd == NULL)
    13               return-ENOMEM;
    14        card->num_rtd= 0;
    15        card->rtd_aux= &card->rtd[card->num_links];
    16  
    17        for(i = 0; i < card->num_links; i++)
    18               card->rtd[i].dai_link= &card->dai_link[i];
    19  
    20        INIT_LIST_HEAD(&card->list);
    21        INIT_LIST_HEAD(&card->dapm_dirty);
    22        card->instantiated= 0;          //表明声卡还没有被初始化
    23        mutex_init(&card->mutex);
    24        mutex_init(&card->dapm_mutex);
    25        //初始化声卡
    26        ret= snd_soc_instantiate_card(card);
    27        if(ret != 0)
    28               soc_cleanup_card_debugfs(card);

     在snd_soc_instantiate_card函数中:

      1 static int snd_soc_instantiate_card(struct snd_soc_card *card)
      2 {
      3     struct snd_soc_codec *codec;
      4     struct snd_soc_codec_conf *codec_conf;
      5     enum snd_soc_compress_type compress_type;
      6     struct snd_soc_dai_link *dai_link;
      7     int ret, i, order, dai_fmt;
      8 
      9     mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);
     10 
     11     /* bind DAIs */
     12     for (i = 0; i < card->num_links; i++) {
     13         ret = soc_bind_dai_link(card, i);
     14         if (ret != 0)
     15             goto base_error;
     16     }
     17 
     18     /* check aux_devs too */
     19     for (i = 0; i < card->num_aux_devs; i++) {
     20         ret = soc_check_aux_dev(card, i);
     21         if (ret != 0)
     22             goto base_error;
     23     }
     24 
     25     /* initialize the register cache for each available codec */
     26     list_for_each_entry(codec, &codec_list, list) {
     27         if (codec->cache_init)
     28             continue;
     29         /* by default we don't override the compress_type */
     30         compress_type = 0;
     31         /* check to see if we need to override the compress_type */
     32         for (i = 0; i < card->num_configs; ++i) {
     33             codec_conf = &card->codec_conf[i];
     34             if (!strcmp(codec->name, codec_conf->dev_name)) {
     35                 compress_type = codec_conf->compress_type;
     36                 if (compress_type && compress_type
     37                     != codec->compress_type)
     38                     break;
     39             }
     40         }
     41         ret = snd_soc_init_codec_cache(codec, compress_type);
     42         if (ret < 0)
     43             goto base_error;
     44     }
     45 
     46     /* card bind complete so register a sound card */
     47     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
     48             card->owner, 0, &card->snd_card);
     49     if (ret < 0) {
     50         dev_err(card->dev, "ASoC: can't create sound card for"
     51             " card %s: %d\n", card->name, ret);
     52         goto base_error;
     53     }
     54     card->snd_card->dev = card->dev;
     55 
     56     card->dapm.bias_level = SND_SOC_BIAS_OFF;
     57     card->dapm.dev = card->dev;
     58     card->dapm.card = card;
     59     list_add(&card->dapm.list, &card->dapm_list);
     60 
     61 #ifdef CONFIG_DEBUG_FS
     62     snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
     63 #endif
     64 
     65 #ifdef CONFIG_PM_SLEEP
     66     /* deferred resume work */
     67     INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
     68 #endif
     69 
     70     if (card->dapm_widgets)
     71         snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
     72                       card->num_dapm_widgets);
     73 
     74     /* initialise the sound card only once */
     75     if (card->probe) {
     76         ret = card->probe(card);
     77         if (ret < 0)
     78             goto card_probe_error;
     79     }
     80 
     81     /* probe all components used by DAI links on this card */
     82     for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
     83             order++) {
     84         for (i = 0; i < card->num_links; i++) {
     85             ret = soc_probe_link_components(card, i, order);
     86             if (ret < 0) {
     87                 dev_err(card->dev,
     88                     "ASoC: failed to instantiate card %d\n",
     89                     ret);
     90                 goto probe_dai_err;
     91             }
     92         }
     93     }
     94 
     95     /* probe all DAI links on this card */
     96     for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
     97             order++) {
     98         for (i = 0; i < card->num_links; i++) {
     99             ret = soc_probe_link_dais(card, i, order);
    100             if (ret < 0) {
    101                 dev_err(card->dev,
    102                     "ASoC: failed to instantiate card %d\n",
    103                     ret);
    104                 goto probe_dai_err;
    105             }
    106         }
    107     }
    108 
    109     for (i = 0; i < card->num_aux_devs; i++) {
    110         ret = soc_probe_aux_dev(card, i);
    111         if (ret < 0) {
    112             dev_err(card->dev,
    113                 "ASoC: failed to add auxiliary devices %d\n",
    114                 ret);
    115             goto probe_aux_dev_err;
    116         }
    117     }
    118 
    119     snd_soc_dapm_link_dai_widgets(card);
    120 
    121     if (card->controls)
    122         snd_soc_add_card_controls(card, card->controls, card->num_controls);
    123 
    124     if (card->dapm_routes)
    125         snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
    126                     card->num_dapm_routes);
    127 
    128     snd_soc_dapm_new_widgets(&card->dapm);
    129 
    130     for (i = 0; i < card->num_links; i++) {
    131         dai_link = &card->dai_link[i];
    132         dai_fmt = dai_link->dai_fmt;
    133 
    134         if (dai_fmt) {
    135             ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai,
    136                           dai_fmt);
    137             if (ret != 0 && ret != -ENOTSUPP)
    138                 dev_warn(card->rtd[i].codec_dai->dev,
    139                      "ASoC: Failed to set DAI format: %d\n",
    140                      ret);
    141         }
    142 
    143         /* If this is a regular CPU link there will be a platform */
    144         if (dai_fmt &&
    145             (dai_link->platform_name || dai_link->platform_of_node)) {
    146             ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
    147                           dai_fmt);
    148             if (ret != 0 && ret != -ENOTSUPP)
    149                 dev_warn(card->rtd[i].cpu_dai->dev,
    150                      "ASoC: Failed to set DAI format: %d\n",
    151                      ret);
    152         } else if (dai_fmt) {
    153             /* Flip the polarity for the "CPU" end */
    154             dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
    155             switch (dai_link->dai_fmt &
    156                 SND_SOC_DAIFMT_MASTER_MASK) {
    157             case SND_SOC_DAIFMT_CBM_CFM:
    158                 dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
    159                 break;
    160             case SND_SOC_DAIFMT_CBM_CFS:
    161                 dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
    162                 break;
    163             case SND_SOC_DAIFMT_CBS_CFM:
    164                 dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
    165                 break;
    166             case SND_SOC_DAIFMT_CBS_CFS:
    167                 dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
    168                 break;
    169             }
    170 
    171             ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
    172                           dai_fmt);
    173             if (ret != 0 && ret != -ENOTSUPP)
    174                 dev_warn(card->rtd[i].cpu_dai->dev,
    175                      "ASoC: Failed to set DAI format: %d\n",
    176                      ret);
    177         }
    178     }
    179 
    180     snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
    181          "%s", card->name);
    182     snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
    183          "%s", card->long_name ? card->long_name : card->name);
    184     snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
    185          "%s", card->driver_name ? card->driver_name : card->name);
    186     for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
    187         switch (card->snd_card->driver[i]) {
    188         case '_':
    189         case '-':
    190         case '\0':
    191             break;
    192         default:
    193             if (!isalnum(card->snd_card->driver[i]))
    194                 card->snd_card->driver[i] = '_';
    195             break;
    196         }
    197     }
    198 
    199     if (card->late_probe) {
    200         ret = card->late_probe(card);
    201         if (ret < 0) {
    202             dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n",
    203                 card->name, ret);
    204             goto probe_aux_dev_err;
    205         }
    206     }
    207 
    208     snd_soc_dapm_new_widgets(&card->dapm);
    209 
    210     if (card->fully_routed)
    211         list_for_each_entry(codec, &card->codec_dev_list, card_list)
    212             snd_soc_dapm_auto_nc_codec_pins(codec);
    213 
    214     ret = snd_card_register(card->snd_card);
    215     if (ret < 0) {
    216         dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
    217                 ret);
    218         goto probe_aux_dev_err;
    219     }
    220 
    221 #ifdef CONFIG_SND_SOC_AC97_BUS
    222     /* register any AC97 codecs */
    223     for (i = 0; i < card->num_rtd; i++) {
    224         ret = soc_register_ac97_dai_link(&card->rtd[i]);
    225         if (ret < 0) {
    226             dev_err(card->dev, "ASoC: failed to register AC97:"
    227                 " %d\n", ret);
    228             while (--i >= 0)
    229                 soc_unregister_ac97_dai_link(card->rtd[i].codec);
    230             goto probe_aux_dev_err;
    231         }
    232     }
    233 #endif
    234 
    235     card->instantiated = 1;
    236     snd_soc_dapm_sync(&card->dapm);
    237     mutex_unlock(&card->mutex);
    238 
    239     return 0;
    240 
    241 probe_aux_dev_err:
    242     for (i = 0; i < card->num_aux_devs; i++)
    243         soc_remove_aux_dev(card, i);
    244 
    245 probe_dai_err:
    246     soc_remove_dai_links(card);
    247 
    248 card_probe_error:
    249     if (card->remove)
    250         card->remove(card);
    251 
    252     snd_card_free(card->snd_card);
    253 
    254 base_error:
    255     mutex_unlock(&card->mutex);
    256 
    257     return ret;
    258 }
    1 /*bind DAIs */
    2  for(i = 0; i < card->num_links; i++) {
    3         //逐一绑定声卡的各类DAI链接,下面会详细介绍该函数
    4         ret= soc_bind_dai_link(card, i);
    5         if(ret != 0)
    6              gotobase_error;
    7  }
     1 /* initialize the register cache for each available codec */
     2 //遍历CODEC列表中的所有CODEC
     3 list_for_each_entry(codec, &codec_list, list) {
     4     //CODEC缓存是否已初始化
     5     if (codec->cache_init)
     6         continue;
     7     /* by default we don't override the compress_type */
     8     //设置压缩类型
     9     compress_type = 0;
    10     /* check to see if we need to override the compress_type */
    11     for (i = 0; i < card->num_configs; ++i) {
    12         codec_conf = &card->codec_conf[i];
    13         if (!strcmp(codec->name, codec_conf->dev_name)) {
    14             compress_type = codec_conf->compress_type;
    15             if (compress_type && compress_type
    16                 != codec->compress_type)
    17                 break;
    18         }
    19     }
    20     /*初始化CODEC缓存,该函数最终调用sound/soc/soc-cache.c文件内的snd_soc_flat_cache_init函数,为缓存(reg_cache)开辟内存空间,并把codec->cache_init置为1*/
    21     ret = snd_soc_init_codec_cache(codec, compress_type);
    22     if (ret < 0)
    23         goto base_error;
    24 }
    1 //创建声卡
    2 ret= snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
    3              card->owner,0, &card->snd_card);

    继续看soc_bind_dai_link函数,通过msm8x16-wcd.c中probe的of_device_id就可以知道:

    554         pm8916_tombak_dig: msm8x16_wcd_codec@f000{
    555             compatible = "qcom,wcd-spmi";
    556             reg = <0xf000 0x100>;
    557             interrupt-parent = <&spmi_bus>;
    558             interrupts = <0x1 0xf0 0x0>,
    559                      <0x1 0xf0 0x1>,
    560                      <0x1 0xf0 0x2>,
    561                      <0x1 0xf0 0x3>,
    562                      <0x1 0xf0 0x4>,
    563                      <0x1 0xf0 0x5>,
    564                      <0x1 0xf0 0x6>,
    565                      <0x1 0xf0 0x7>;
    566             interrupt-names = "spk_cnp_int",
    567                       "spk_clip_int",
    568                       "spk_ocp_int",
    569                       "ins_rem_det1",
    570                       "but_rel_det",
    571                       "but_press_det",
    572                       "ins_rem_det",
    573                       "mbhc_int";

     而这个node节点正式codec驱动的设备树节点。在soc_bind_dai_link()函数中,会做出如下处理:

     1 /*注册codec的时候,会将所有注册的codec链接到codec_list中*/
     2     list_for_each_entry(codec, &codec_list, list) {
     3         if (dai_link->codec_of_node) {
     4             /*根据设备数节点句柄进行匹配*/
     5             if (codec->dev->of_node != dai_link->codec_of_node)
     6                 continue;
     7         } else {
     8             /*如果句柄为空,根据,codec_name进行匹配,在这里不会走这里,其实codec_name是 wcd-spmi-core.1*/
     9             if (strcmp(codec->name, dai_link->codec_name))
    10                 continue;
    11         }
    12 
    13         rtd->codec = codec;
    14 
    15         /*找到codec之后,根据codec_dai的名字找到对应的codec_dai*/
    16         list_for_each_entry(codec_dai, &dai_list, list) {
    17             if (codec->dev == codec_dai->dev &&
    18                 !strcmp(codec_dai->name,
    19                     dai_link->codec_dai_name)) {
    20 
    21                 rtd->codec_dai = codec_dai;
    22             }
    23         }
    24     }

     所以,我们可以根据dai_link中的codec_dai的名字或者codec名字来找到对应的codec驱动。

    另外,当播放音频的时候需要打开一个外部pa,所以probe函数中有一部分是external pa的初始化:

    (6)、获取external pa:(也就是放大器):

     1   for (i = 0; i < num_strings; i++) {
     2             ret = of_property_read_string_index(pdev->dev.of_node,
     3                     ext_pa, i, &ext_pa_str);
     4             if (ret) {
     5                 dev_err(&pdev->dev, "%s:of read string %s i %d error %d\n",
     6                         __func__, ext_pa, i, ret);
     7                 goto err;
     8             }
     9             if (!strcmp(ext_pa_str, "primary"))
    10                 pdata->ext_pa = (pdata->ext_pa | PRI_MI2S_ID);
    11             else if (!strcmp(ext_pa_str, "secondary"))
    12                 pdata->ext_pa = (pdata->ext_pa | SEC_MI2S_ID);
    13             else if (!strcmp(ext_pa_str, "tertiary"))
    14                 pdata->ext_pa = (pdata->ext_pa | TER_MI2S_ID);
    15             else if (!strcmp(ext_pa_str, "quaternary"))
    16                 pdata->ext_pa = (pdata->ext_pa | QUAT_MI2S_ID);
    17         }
    18         pr_err("%s: ext_pa = %d\n", __func__, pdata->ext_pa);
    1 pinctrl_info.pinctrl = pinctrl;
    2 ret = get_cdc_gpio_lines(pinctrl, pdata->ext_pa);    //获取gpio状态
    3 if (ret < 0) {
    4         pr_err("%s: failed to ger the codec gpio's %d\n",
    5                     __func__, ret);
    6         goto err;
    7 }

     可以根据高通手册来看,所以设备树上的配置为secondary:

    1 qcom,msm-ext-pa = "secondary";//"primary";

    2. 相应的资料:

    其实以上便是linux3.10以上的audio内核machine架构,网上搜索相应资料便可找到;贴上借鉴的资料:

    http://blog.csdn.net/zhaocj/article/details/20533369

    网上大牛的架构:

    http://www.cnblogs.com/linhaostudy/p/8169383.html

    本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

    发表于 2018-03-07

    举报

    4

    分享

    •  

    •  
    •  
    •  

    扫描二维码

     

    linux驱动个人学习

    231 篇文章59 人订阅

    已订阅

    展开全文
  • 高通audio软件模块功能框图 .

    千次阅读 2014-02-19 12:25:58
    高通audio软件模块功能框图 分类: android 2013-04-12 17:22 581人阅读 评论(0) 收藏 举报   StageFright (SF)媒体框架  1,播放标准audio格式  2,SF媒体架构作为客户接口和Qualcomm ...
  • StageFright (SF)媒体框架: 1,播放标准audio格式 2,SF媒体架构作为客户接口和Qualcomm OpenMAX接口进行通讯,对adsp支持的audio格式进行解码。 3,解码后的audio流传递给audio manager 4,位置: \frameworks...
  • 1. Audio hal层文件所在目录如下蓝色部分,audio使用的配置文件是sdm450auto.conf,怎么知道是这个文件呢?看了编译规则相当复杂,最简单直接的办法是看提交记录,如果不幸是第一个到这里的人,那就只能打印log追踪...
  • Qualcomm Audio HAL 音频通路设置

    千次阅读 2018-07-25 16:04:49
    Qualcomm Audio HAL 音频通路设置 https://blog.csdn.net/azloong/article/details/79383323 1. 音频框图概述 | Front End PCMs | SoC DSP | Back End DAIs | Audio devices | ************* PCM0...
  • Platform: Rockchip ...以采样率和buffer size为线索来跟踪开机时Audio Hal第一次初始化: AudioPolicyManager ->  ConfigParsingUtils::loadAudioPolicyConfig -> AUDIO_POLICY_CONFIG_FILE是/sys
  • AudioFlinger进行的分析,主要是通过loadHwModule_l 找到模块接口名字if_name相匹配的模块库,加载,然后audio_hw_device_open() 调用模块的open方法,完成音频设备模块的初始化。*/ ...
  • 一种定位android HAL代码位置的方法 背景 设备厂商一般会在Android HAL层做自己定义的实现...以AudioHAL实现为例,查看adb logcat输出 adb logcat | grep audio 看到日志输出中很多都带有标签audio_hw_primary V...
  • 1. 音频框图概述 ...| Front End PCMs | SoC DSP | Back End DAIs | Audio devices | ************* PCM0 <------------> * * <----DAI0-----> Codec Headset * * PCM1 <------------> *

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 576
精华内容 230
关键字:

hal高通audio