1. 程式人生 > >linux ALSA & ASOC(1)—— framwork

linux ALSA & ASOC(1)—— framwork

一、ALSA framwork

1.涉及檔案、函式

sound/core/sound.c        snd_register_device_for_dev            建立次裝置
sound/core/init.c           snd_card_create                         建立contorl裝置
sound/core/pcm.c            snd_pcm_new                             建立 pcm 裝置

snd_card_create 和 snd_pcm_new 通過呼叫 snd_register_device_for_dev 函式建立次裝置

主要思路:通過次裝置號獲取新的fops對不同次裝置進行操作

2.框架圖

這裡寫圖片描述
☆ SNDRV_CTL_IOCTL_ELEM_WRITE ☆ // 呼叫put回撥,設定音量值
SNDRV_CTL_IOCTL_ELEM_READ // 呼叫get回撥,獲取音量值
SNDRV_CTL_IOCTL_ELEM_INFO // 呼叫info回撥

二、ASOC framwork

1.重要檔案、函式、結構

涉及檔案

 sound\sound_core.c
 ......
 sound\soc\mxs\mxs-sgtl5000.c
 sound\soc
\codecs\ sgtl5000.c ...... sound\soc\msm\msm8x10.c sound\soc\codecs\msm8x10-wcd.c ......

涉及函式

snd_soc_register_card
snd_soc_register_codec
snd_soc_register_dai
snd_soc_register_platform

重要結構

 (card)       snd_soc_card                                                           
 (dai_link)   snd_soc_dai_link  (snd_soc_card->dai_link)        
 (codec )     snd_soc_codec_driver                                          
 (dai)        snd_soc_dai_driver                                             
 (dma)        snd_soc_platform_driver       



----------
cat /sys/class/sound/card0/id 的字串和 snd_soc_card->name 名字相同

2.ASOC組成

—— ASOC 就是對 ALSA 驅動框架的進一步封裝,驅動框架分為了三個部分

2.1 machine

 指定了 cpu 端的DAI、 codec端的 DAI、 板子的platform名字 、 codec名字 (snd_soc_card->dai_link 結構中指定)

snd_soc_register_card(struct snd_soc_card *card)     // 註冊一個card   

通常有兩種方法註冊一個card

—— 在machine驅動中註冊一個名為 “soc-audio” 的 platform device,並將 struct snd_soc_card 結構變數作為這個device的私有資料,在 soc-core.c 呼叫 名為 “soc-audio” 的 platform drv的probe函式,probe中呼叫 snd_soc_register_card 註冊這個card

—— 直接在machine驅動中呼叫 snd_soc_register_card 函式註冊card

☆ snd_soc_register_card 函式解析

1.snd_soc_register_card 
   2. snd_soc_instantiate_card(card);  // ☆ 核心函式 ☆

      3.1/* soc_bind_dai_link ,就是將machine的 《dai_link》 結構中的
            codec、codec端的dai、cpu端的dai依次從已經註冊的三個連結串列中找到並且賦值給《runtime》結構

            以下三個函式的作用就是將 dai、dma、codec  註冊到三個連結串列中    
            snd_soc_register_dai
            snd_soc_register_platform
            snd_soc_register_codec

            */
                  for (i = 0; i < card->num_links; i++)
                    soc_bind_dai_link(card, i);
                        3.1.1 /* find CPU DAI */
                              rtd->cpu_dai = cpu_dai; 

                        3.1.2 /* find_codec */
                              rtd->codec = codec; 

                        3.1.3 /* find CODEC DAI */      
                              rtd->codec_dai = codec_dai;  

                        3.1.4 /* find_platform */
                              rtd->platform = platform;  

            3.2 /* initialize the register cache for each available codec */
                ret = snd_soc_init_codec_cache(codec, compress_type);

            3.3 snd_card_create     // ☆ ALSA 建立contorl裝置節點


            3.4 /* ☆ 此處依次呼叫以下三個結構模組的probe函式,注意不是驅動的probe ☆  

                    snd_soc_platform_driver 
                    snd_soc_codec_driver
                    snd_soc_dai_driver  
                */
                soc_probe_dai_link   

                        /* probe the cpu_dai */
                        /* probe the CODEC */
                        /* probe the platform */
                        /* probe the CODEC DAI */
                        /* create the pcm */
                                    ret = soc_new_pcm(rtd, num);
                                                        struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
                                                                        soc_pcm_ops->open   = soc_pcm_open;
                                                                        soc_pcm_ops->close  = soc_pcm_close;
                                                                        soc_pcm_ops->hw_params  = soc_pcm_hw_params;
                                                                        soc_pcm_ops->hw_free    = soc_pcm_hw_free;
                                                                        soc_pcm_ops->prepare    = soc_pcm_prepare;
                                                                        soc_pcm_ops->trigger    = soc_pcm_trigger;
                                                                        soc_pcm_ops->pointer    = soc_pcm_pointer;

                                                        snd_pcm_new      // ☆ ALSA 建立pcm裝置節點    

            3.5 snd_card_register                                        //  ☆ ALSA 建立sys節點 /sys/class/sound/cardx

2.2 platform

註冊了cpu端的 DAI 和 DMA 驅動
 

snd_soc_register_platform(struct device *dev,struct snd_soc_platform_driver *platform_drv)  // 註冊 DMA

snd_soc_register_dai(struct device *dev,struct snd_soc_dai_driver *dai_drv) // 註冊 dai

重要結構

struct snd_soc_platform_driver

重要回調函式

當應用程式開啟一個pcm裝置時,該函式會被呼叫,通常,該函式會使用snd_soc_set_runtime_hwparams()設定substream中的snd_pcm_runtime結構裡面的hw_params相關欄位,然後為snd_pcm_runtime的private_data欄位申請一個私有結構,用於儲存該平臺的dma引數。

snd_soc_platform_driver->ops->open 

驅動的hw_params階段,該函式會被呼叫。通常,該函式會通過snd_soc_dai_get_dma_data函式獲得對應的dai的dma引數,獲得的引數一般都會儲存在snd_pcm_runtime結構的private_data欄位。然後通過snd_pcm_set_runtime_buffer函式設定snd_pcm_runtime結構中的dma buffer的地址和大小等引數。要注意的是,該回調可能會被多次呼叫,具體實現時要小心處理多次申請資源的問題。

snd_soc_platform_driver->ops->hw_params 

正式開始資料傳送之前會呼叫該函式,該函式通常會完成dma操作的必要準備工作。

snd_soc_platform_driver->ops->prepare

資料傳送的開始,暫停,恢復和停止時,該函式會被呼叫。

snd_soc_platform_driver->ops->trigger

該函式返回傳送資料的當前位置。

snd_soc_platform_driver->ops->pointer

2.3 codec

註冊了codec和codec端的dai,指定了control 、dapm_widgets、dapm_route

snd_soc_register_codec(struct device *dev,
                       const struct snd_soc_codec_driver *codec_drv,
                       struct snd_soc_dai_driver *dai_drv,
                       int num_dai)   

// 註冊codec和 codec 端的dai,下面兩個結構作為這個函式的引數
struct snd_soc_codec_driver
struct snd_soc_dai_driver

// 結構成員
snd_soc_codec_driver->controls       // ☆ 音量調節等Control裝置 ☆           snd_kcontrol_new結構陣列
snd_soc_codec_driver->dapm_widgets   // ☆ dapm_widgets control集合 ☆       snd_soc_dapm_widget 結構陣列
snd_soc_codec_driver->dapm_route     // ☆ 路由 ☆                           snd_soc_dapm_route 結構陣列

// 重要回調函式
snd_soc_dai_driver->ops->startup
snd_soc_dai_driver->ops->hw_params

snd_soc_codec_driver->ops->probe         //dai驅動的probe函式,由snd_soc_instantiate_card回撥 

三、open、ioctl操作裝置節點的函式呼叫過程

1.播放過程

這裡寫圖片描述

snd_pcm_open

☆ 依次呼叫cpu_dai、 dma,、codec_dai,、machine 的open或startup函式 ☆
           ret = cpu_dai->driver->ops->startup(substream, cpu_dai);       // cpu_dai        startup
           ret = platform->driver->ops->open(substream);                  // dma             open
           ret = codec_dai->driver->ops->startup(substream);              // codec_dai     startup           
           ret = rtd->dai_link->ops->startup(substream);                  // machine       startup

snd_pcm_playback_ioctl1

☆ SNDRV_PCM_IOCTL_HW_PARAMS

snd_pcm_hw_params_user
       soc_pcm_hw_params
              ☆  依次呼叫 machine 、codec_dai、cpu_dai、platform(dma) 的hw_params函式 ☆




SNDRV_PCM_IOCTL_PREPARE  :
                 snd_pcm_prepare(substream, file);
                               snd_power_wait // 電源管理相關 
                               .... 呼叫到platform裡的prepare




☆ SNDRV_PCM_IOCTL_WRITEI_FRAMES :    // 迴圈資料傳輸
                                snd_pcm_lib_write
                                                snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer)
                                                                snd_pcm_lib_write_transfer
                                                                                copy_from_user
                                                                                snd_pcm_start(substream);  // 啟動傳輸

2.音量控制過程

見最開始時序圖

這裡寫圖片描述

codec驅動中搜索 “.put” 關鍵字直接找到音量控制相關操作

重要結構

/*
    定義一個kcontrol主要就是定義一個snd_kcontrol_new結構
    一個kcontrol代表著一個mixer(混音器),或者是一個mux(多路開關),又或者是一個音量控制器等等。
*/

struct snd_kcontrol_new

四、重要結構解析

snd_soc_codec

* SoC Audio Codec device */  
struct snd_soc_codec {  
    const char *name;  /* Codec的名字*/  
    struct device *dev; /* 指向Codec裝置的指標 */  
    const struct snd_soc_codec_driver *driver; /* 指向該codec的驅動的指標 */  
    struct snd_soc_card *card;    /* 指向Machine驅動的card例項 */  
    int num_dai; /* 該Codec數字介面的個數,目前越來越多的Codec帶有多個I2S或者是PCM介面 */  
    int (*volatile_register)(...);  /* 用於判定某一暫存器是否是volatile */  
    int (*readable_register)(...);  /* 用於判定某一暫存器是否可讀 */  
    int (*writable_register)(...);  /* 用於判定某一暫存器是否可寫 */  

    /* runtime */  
    ......  
    /* codec IO */  
    void *control_data; /* 該指標指向的結構用於對codec的控制,通常和read,write欄位聯合使用 */  
    enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一種 */  
    unsigned int (*read)(struct snd_soc_codec *, unsigned int);  /* 讀取Codec暫存器的函式 */  
    int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  /* 寫入Codec暫存器的函式 */  
    /* dapm */  
    struct snd_soc_dapm_context dapm;  /* 用於DAPM控制元件 */  
};  

snd_soc_codec_driver

/* codec driver */  
struct snd_soc_codec_driver {  
    /* driver ops */  
    int (*probe)(struct snd_soc_codec *);  /* codec驅動的probe函式,由snd_soc_instantiate_card回撥 */  
    int (*remove)(struct snd_soc_codec *);    
    int (*suspend)(struct snd_soc_codec *);  /* 電源管理 */  
    int (*resume)(struct snd_soc_codec *);   /* 電源管理 */  

    /* Default control and setup, added after probe() is run */  
    const struct snd_kcontrol_new *controls;          /* ☆ 音訊控制元件指標 */  
    const struct snd_soc_dapm_widget *dapm_widgets;   /* ☆ dapm部件指標 */  
    const struct snd_soc_dapm_route *dapm_routes;     /* ☆ dapm路由指標 */  

    /* codec wide operations */  
    int (*set_sysclk)(...);  /* 時鐘配置函式 */  
    int (*set_pll)(...);     /* 鎖相環配置函式 */  

    /* codec IO */  
    unsigned int (*read)(...);  /* 讀取codec暫存器函式 */  
    int (*write)(...);          /* 寫入codec暫存器函式 */  
    int (*volatile_register)(...);   /* 用於判定某一暫存器是否是volatile */  
    int (*readable_register)(...);   /* 用於判定某一暫存器是否可讀 */  
    int (*writable_register)(...);   /* 用於判定某一暫存器是否可寫 */  

    /* codec bias level */  
    int (*set_bias_level)(...);  /* 偏置電壓配置函式 */  

};  

snd_soc_dai

  /*  
 * digital Audio Interface runtime data.  
 * Holds runtime data for a DAI.  
 */  
struct snd_soc_dai {  
    const char *name;    /* dai的名字 */  
    struct device *dev;  /* 裝置指標 */  
    /* driver ops */  
    struct snd_soc_dai_driver *driver;  /* 指向dai驅動結構的指標 */
    /* DAI runtime info */  
    unsigned int capture_active:1;      /* stream is in use */  
    unsigned int playback_active:1;     /* stream is in use */  
    /* DAI DMA data */  
    void *playback_dma_data;           /* 用於管理playback dma */  
    void *capture_dma_data;            /* 用於管理capture dma */  

    /* parent platform/codec */  
    union {  
        struct snd_soc_platform *platform;  /* 如果是cpu dai,指向所繫結的平臺 */  
        struct snd_soc_codec *codec;        /* 如果是codec dai指向所繫結的codec */  
    };  
    struct snd_soc_card *card;              /* 指向Machine驅動中的crad例項 */  
};  

snd_soc_dai_driver


struct snd_soc_dai_driver {  
    /* DAI description */  
    const char *name;  /* dai驅動名字 */  

    /* DAI driver callbacks */  
    int (*probe)(struct snd_soc_dai *dai);  /* dai驅動的probe函式,由snd_soc_instantiate_card回撥 */  
    int (*remove)(struct snd_soc_dai *dai);    
    int (*suspend)(struct snd_soc_dai *dai);  /* 電源管理 */  
    int (*resume)(struct snd_soc_dai *dai);    

    /* ops */  
    const struct snd_soc_dai_ops *ops;  /* 指向本dai的snd_soc_dai_ops結構 */  

    /* DAI capabilities */  
    struct snd_soc_pcm_stream capture;   /* ☆ 描述capture的能力  ☆ */  
    struct snd_soc_pcm_stream playback;  /* ☆ 描述playback的能力 ☆ */  
};  

snd_soc_dai_driver->ops

struct snd_soc_dai_ops {  
    /*  
     * 工作時鐘配置函式  通常由machine驅動呼叫
     *  
     */  
    int (*set_sysclk)(...);     // 設定dai的主時鐘;
    int (*set_pll)(...);  
    int (*set_clkdiv)(...);  
    /*  
     * DAI format configuration  
     * Called by soc_card drivers, normally in their hw_params.  
     */  
    int (*set_fmt)(...);            // dai 的格式配置函式  通常由machine驅動呼叫
    int (*set_tdm_slot)(...);       // 如果dai支援時分複用,用於設定時分複用的slot;
    int (*set_channel_map)(...);    // 聲道的時分複用對映設定
    int (*set_tristate)(...);       // 設定dai引腳的狀態,當與其他dai並聯使用同一引腳時需要使用該回調
    /*  
     * DAI digital mute - optional.  
     * 抗pop,pop聲  由soc-core呼叫:
     */  
    int (*digital_mute)(...);  

    /*  
     * 標準的snd_soc_ops回撥             ☆
     * 通常由soc-core在進行PCM操作時呼叫   ☆ 
     */  
    int (*startup)(...);  
    void (*shutdown)(...);  
    int (*hw_params)(...);  
    int (*hw_free)(...);  
    int (*prepare)(...);  
    int (*trigger)(...);  
    /*  
     * For hardware based FIFO caused delay reporting.  
     * Optional.  
     */  
    snd_pcm_sframes_t (*delay)(...);  
};  

四、framwork 框圖