基於: Mini2440 開發板, Linux 3.4.2 內核 ASOC 簡介: ~~~~ ASoC ALSA System on Chip,是建立在標準ALSA驅動層上,為了更好地支持嵌入式處理器和移動設備中音頻 Codec 的一套軟體體系。 就像軟體領域里的抽象和重用一樣,嵌入式設備的音頻系統 ...
基於: Mini2440 開發板, Linux 3.4.2 內核
ASOC 簡介:
ASoC - ALSA System on Chip,是建立在標準ALSA驅動層上,為了更好地支持嵌入式處理器和移動設備中音頻 Codec 的一套軟體體系。
就像軟體領域里的抽象和重用一樣,嵌入式設備的音頻系統可以被劃分為 Machine、Platform、 Codec。
所以,我們可以把 ASOC 的驅動分為 3 個部分,以內核中的文件為例:
Machine:
S3c24xx_uda134x.c (sound\soc\samsung)
Platform:
S3c24xx-i2s.c (sound\soc\samsung)
Dma.c (sound\soc\samsung)
Codec:
Uda134x.c (sound\soc\codecs)
我們以 S3c24xx_uda134x.c 文件中的 s3c24xx_uda134x_probe 函數為起點:
/* 創建並配置一個 snd_soc_card 結構體 */
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.dai_link = &s3c24xx_uda134x_dai_link,
.num_links = 1,
};
/* 申請一個名為 "soc-audio" 的 platform_device */
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
/* 設置 platform_device 的 drvdata */
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&snd_soc_s3c24xx_uda134x);
/* 將 s3c24xx_uda134x 添加進平臺設備,它是 UDA1341 晶元的引腳狀態信息 */
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
/* 添加平臺設備 */
ret = platform_device_add(s3c24xx_uda134x_snd_device);
這裡的 snd_soc_dai_link 結構尤為重要,在它的內部指定了 Platform、Codec、 codec_dai、 cpu_dai 的名字,稍後 Machine 驅動將會利用這些名字去匹配已經在系統中註冊了的 platform、codec、dai,這些註冊的介面都是在另外相應的 Platform 驅動和
Codec 驅動的代碼文件中定義的。由此可見,Machine 的功能可認為是匹配相應的驅動實現一個完成的音頻控制體系。
註冊了名為 "soc-audio" 的 platform_device 之後,系統會在內核中找到同名的 driver,隨後調用 driver 的 probe 函數,該函數主要是完成音效卡的實例化、調用各個子介面的 probe 函數、創建標準 alsa 驅動的 pcm 邏輯設備以及完成音效卡的註冊。
Uda134x.c 文件
該文件為 ASOC 的 Codec 驅動,主要是完成 Codec 晶元的各種操控,我們以 uda134x_codec_probe 函數為入口:
static struct snd_soc_dai_driver uda134x_dai = {
.name = "uda134x-hifi",
/* playback capabilities */
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* capture capabilities */
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = UDA134X_RATES,
.formats = UDA134X_FORMATS,
},
/* pcm operations */
.ops = &uda134x_dai_ops,
};
static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
.probe = uda134x_soc_probe,
.remove = uda134x_soc_remove,
.suspend = uda134x_soc_suspend,
.resume = uda134x_soc_resume,
.reg_cache_size = sizeof(uda134x_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = uda134x_reg,
.reg_cache_step = 1,
.read = uda134x_read_reg_cache,
.write = uda134x_write,
.set_bias_level = uda134x_set_bias_level,
};
/* 註冊 Codec */
snd_soc_register_codec(&pdev->dev, &soc_codec_dev_uda134x, &uda134x_dai, 1);
snd_soc_register_codec 函數的大體步驟:
/* 分配 snd_soc_codec */
1. codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
2. 配置 Codec
/* 註冊 dais */
3. snd_soc_register_dais:
/* 分配 snd_soc_dai */
1. dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
2. 配置 Dai
3. 將 Dai 加入到 dai_list
4. 調用 snd_soc_instantiate_cards 函數觸發一次匹配綁定操作
4. 將 Codec 加入到 codec_list
5. 調用 snd_soc_instantiate_cards 函數觸發一次匹配綁定操作
S3c24xx-i2s.c 與 Dma.c 文件
ASOC 把 Platform 驅動分為兩個部分:snd_soc_platform_driver 和
snd_soc_dai_driver。其中,platform_driver 負責管理音頻數據,把音頻數據通過 dma 或其他操作傳送至 cpu dai
中,dai_driver 則主要完成 cpu 一側的 dai 的參數配置,同時也會通過一定的途徑把必要的 dma 等參數與 snd_soc_platform_driver 進行交互。
先來看 Dma.c 中的 samsung_asoc_platform_probe 函數:
static struct snd_pcm_ops dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
/* 註冊 soc platform */
snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
snd_soc_register_platform 函數中申請、配置了 snd_soc_platform 結構,並將其加入到了 platform_list,然後調用 snd_soc_instantiate_cards 函數進行綁定。
samsung_asoc_platform 結構中的 pcm_new 主要用來分配一個 buffer,dma 使用它來操作音頻數據; pcm_free 用來釋放這個 buffer; ops 中為涉及到 dma 操作以及 dma buffer 管理等工作的函數。
S3c24xx-i2s.c 中的 s3c24xx_iis_dev_probe 函數:
static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
.trigger = s3c24xx_i2s_trigger,
.hw_params = s3c24xx_i2s_hw_params,
.set_fmt = s3c24xx_i2s_set_fmt,
.set_clkdiv = s3c24xx_i2s_set_clkdiv,
.set_sysclk = s3c24xx_i2s_set_sysclk,
};
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.probe = s3c24xx_i2s_probe,
.suspend = s3c24xx_i2s_suspend,
.resume = s3c24xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c24xx_i2s_dai_ops,
};
/* 註冊 soc dai */
snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
snd_soc_register_dai 函數中申請、配置了 snd_soc_dai 結構,並將其加入到了 dai_list,然後調用 snd_soc_instantiate_cards 函數進行綁定。
這兩個結構中的函數不難理解,主要是為了配置 I2S 的引腳及時鐘、dai 數據格式等。
這些函數可能會由 soc-core 或 Machine 驅動中的函數調用。