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)(...);
};