音頻數據流向:
| DMA | | I2S/PCM/AC97 |
RAM -------------> I2SControllerFIFO ---------------------------> CODEC ----> SPK/Headset
PCM模塊初始化:
struct snd_soc_platform s3c_soc_platform = {
.name = "s3c-pcm-audio",
.pcm_ops = &s3c_pcm_ops, //OK
.pcm_new = s3c_pcm_new, //OK
.pcm_free = s3c_pcm_free_dma_buffers, //OK
.suspend = s3c_pcm_suspend,
.resume = s3c_pcm_resume,
};
調用snd_soc_register_platform()向ALSA core註冊一個snd_soc_platform結構體。成員pcm_new需要調用dma_alloc_writecombine()給DMA分配一塊write-combining的內存空間,並把這塊緩衝區的相關信息保存到substream->dma_buffer中,相當於構造函數。pcm_free則相反。
snd_pcm_ops結構體如下:
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream); //OK
int (*close)(struct snd_pcm_substream *substream); //OK
int (*ioctl)(struct snd_pcm_substream * substream, unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); //OK
int (*hw_free)(struct snd_pcm_substream *substream); //OK
int (*prepare)(struct snd_pcm_substream *substream); //OK
int (*trigger)(struct snd_pcm_substream *substream, int cmd); //OK
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); //PCM中間層通過調用這個函數來獲取緩衝區的位置。
int (*copy)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream, unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
open函數爲PCM模塊設定支持的傳輸模式、數據格式、通道數、period等參數,併爲playback/capture stream分配相應的DMA通道。
static int s3c_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_stream_a *s = runtime->private_data;
int ret;
if (!cpu_dai->active) {
audio_dma_request(&s[0], audio_dma_callback); //爲playback stream分配DMA,audio_dma_callback,這是dma的中斷函數。
audio_dma_request(&s[1], audio_dma_callback); //爲capture stream分配DMA
}
//設定runtime硬件參數,硬件參數要根據芯片的數據手冊來定義。
snd_soc_set_runtime_hwparams(substream, &s3c_pcm_hardware);
/* Ensure that buffer size is a multiple of period size */
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
return ret;
}
- static const struct snd_pcm_hardware s3c_pcm_hardware = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_BLOCK_TRANSFER |
- SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_U16_LE |
- SNDRV_PCM_FMTBIT_U8 |
- SNDRV_PCM_FMTBIT_S8,
- .channels_min = 2,
- .channels_max = 2,
- .buffer_bytes_max = 128*1024,
- .period_bytes_min = PAGE_SIZE,
- .period_bytes_max = PAGE_SIZE*2,
- .periods_min = 2,
- .periods_max = 128,
- .fifo_size = 32,
- };
static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int err = 0;
/*dma_buffer是DMA緩衝區,它通過4個字段定義:dma_area、dma_addr、dma_bytes和dma_private。其中dma_area是緩衝區邏輯地址,
dma_addr是緩衝區的物理地址,dma_bytes是緩衝區的大小,dma_private是ALSA的DMA管理用到的。dma_buffer是在pcm_new()中初始化的。*/
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
return err;
}
prepare
當pcm“準備好了”調用該函數。在這裏根據channels、buffer_bytes等來設定DMA傳輸參數,跟具體硬件平臺相關。注:每次調用snd_pcm_prepare()的時候均會調用prepare函數。trigger
ALSA: Advanced Linux Sound Architecture,它包括內核驅動集合、API庫和工具。用戶層程序直接調用libsound的API庫,不需要打開設備等操作,因此編程者不需要了解底層細節。在嵌入式中,音頻數據傳輸一般用I2S接口,控制一般用I2c或SPI接口。如下僅以嵌入式聲卡爲例,其驅動代碼一般放在sound/soc下面。
struct snd_soc_dai結構體:
/* Digital Audio Interface runtime data.
Holds runtime data for a DAI. */
struct snd_soc_dai {
/* DAI description */
char *name; //模塊聲卡名稱
unsigned int id;
int ac97_control;
struct device *dev;
void *ac97_pdata; /* platform_data for the ac97 codec */
/* DAI callbacks */
int (*probe)(struct platform_device *pdev, struct snd_soc_dai *dai);
void (*remove)(struct platform_device *pdev, struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* ops */
struct snd_soc_dai_ops *ops; //聲卡操作函數集合指針,實現的有hw_params(硬件參數設定)、digital_mute(靜音操作)、set_fmt(格式配置)等,這些函數的實現均與硬件相關,根據硬件的數據手冊來實現。
/* DAI capabilities */
struct snd_soc_pcm_stream capture; //錄音參數設定
struct snd_soc_pcm_stream playback; //播放參數設定,均包含channel數目、PCM_RATE和PCM_FMTBIT等信息。
unsigned int symmetric_rates:1; //
/* DAI runtime info */
struct snd_pcm_runtime *runtime;
struct snd_soc_codec *codec;
unsigned int active;
unsigned char pop_wait:1;
void *dma_data;
/* DAI private data */
void *private_data;
/* parent platform */
struct snd_soc_platform *platform;
struct list_head list;
};
結構體定義範例:struct snd_soc_dai uda134x_dai = {
.name = "UDA134X",
/* 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,
};
/* codec device */
struct snd_soc_codec_device {
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
int (*suspend)(struct platform_device *pdev, pm_message_t state);
int (*resume)(struct platform_device *pdev);
};
Probe指聲卡的探測與初始化,remove指聲卡的卸載,suspend指聲卡的休眠,resume指聲卡從休眠狀態下恢復。詳細介紹probe函數。
- static int uda134x_soc_probe(struct platform_device *pdev)
- {
- //獲得snd_soc_device結構體
- //在聲卡的初始化過程中,其實首先是調用sound/soc/<SOC>下的相關驅動的probe函數,在probe有platform_set_drvdata()的操作,
- //這裏有個將指針類型的轉換:(struct snd_soc_device *s) ==> (struct platform_device *)。
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec;
- …
- //爲codec分配內存
- socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (socdev->card->codec == NULL)
- return ret;
- codec = socdev->card->codec;
- …
- //初始化codec
- codec->name = "uda134x";
- codec->owner = THIS_MODULE;
- codec->dai = &uda134x_dai; //指向上面定義好的dai
- codec->num_dai = 1;
- codec->read = uda134x_read_reg_cache; //控制接口—讀
- codec->write = uda134x_write; //控制接口—寫
- …
- mutex_init(&codec->mutex);
- INIT_LIST_HEAD(&codec->dapm_widgets);
- INIT_LIST_HEAD(&codec->dapm_paths);
- …
- /* register pcms */
- /*創建一個PCM實例以便播放數據流。函數裏重要的是如下兩句:
- ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card); //create and initialize a soundcard structure
- ret = soc_new_pcm(socdev, &card->dai_link[i], i);//創建播放流/錄音流的子流,將所有播放流/錄音流的子流操作函數設爲soc_pcm_ops。
- ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
- …
- /*將操作集合掛到card->control鏈表上來,這個集合實現了音頻播放時各個參數的設置,主要有.info、.get和.set。
- 如playback volume control:SOC_DOUBLE_R_TLV("Playback Volume", SNDCARD_REG_L_GAIN, SNDCARD_REG_R_GAIN, 0, 192, 0, digital_tlv),
- 其中SNDCARD_REG_L_GAIN和SNDCARD_REG_R_GAIN分別是左右聲道音量增益寄存器偏移。最終要調用的函數都是在soc-core.c裏面的,
- 這裏只是提供一些跟硬件相關的參數,大爲增加了代碼的複用性。*/
- ret = snd_soc_add_controls(codec, uda134x_snd_controls, ARRAY_SIZE(uda134x_snd_controls));
- …
- /* register card */
- ret = snd_soc_init_card(socdev);
- }
/* SoC Device - the audio subsystem */
struct snd_soc_device {
struct device *dev;
struct snd_soc_card *card;
struct snd_soc_codec_device *codec_dev;
void *codec_data;
};
這個結構體用於向內核註冊一個device。初始化一般如下:
- static struct snd_soc_device SOC_SNDCARD_snd_devdata = {
- .card = &snd_soc_s3c24xx_uda134x,
- .codec_dev = &soc_codec_dev_uda134x,//就是CODEC定義的snd_soc_codec_device結構體
- .codec_data = &s3c24xx_uda134x, //私有數據,一般存放SNDCARD控制接口信息,如I2C從設備地址等
- };
對於module_init,其實用platform_driver_register註冊一個platform_driver結構體的方式也好,還是直接寫一個init也好,都問題不大。前者更貼近Linux的驅動模型。Probe的一般過程如下:
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
s3c24xx_uda134x_snd_devdata.codec_dev = &soc_codec_dev_uda134x;
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
platform_set_drvdata(s3c24xx_uda134x_snd_device, & s3c24xx_uda134x_snd_devdata);
s3c24xx_uda134x_snd_devdata.dev = & s3c24xx_uda134x_snd_device->dev;
platform_device_add(s3c24xx_uda134x_snd_device); //codec中導出的結構體在這裏註冊
}
/* SoC card */
struct snd_soc_card {
char *name;
struct device *dev;
struct list_head list;
int instantiated;
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAI's do any PM work. */
int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
int (*resume_pre)(struct platform_device *pdev);
int (*resume_post)(struct platform_device *pdev);
/* callbacks */
int (*set_bias_level)(struct snd_soc_card *,
enum snd_soc_bias_level level);
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;
struct snd_soc_device *socdev;
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct delayed_work delayed_work;
struct work_struct deferred_resume_work;
};
定義這個結構體是讓snd_soc_register_card()註冊一個card的。初始化範例:
- static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
- .name = "S3C24XX_UDA134X",
- .platform = &s3c24xx_soc_platform,
- .dai_link = &s3c24xx_uda134x_dai_link,
- .num_links = 1,
- };
/* SoC machine DAI configuration, glues a codec and cpu DAI together */
struct snd_soc_dai_link {
char *name; /* Codec name */
char *stream_name; /* Stream name */
/* DAI */
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai;
/* machine stream operations */
struct snd_soc_ops *ops;
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_codec *codec);
/* Symmetry requirements */
unsigned int symmetric_rates:1;
/* Symmetry data - only valid if symmetry is being enforced */
unsigned int rate;
/* DAI pcm */
struct snd_pcm *pcm;
};
因爲一個平臺可以運行多個音頻設備,snd_soc_dai_link的作用也在這,將CODEC定義的snd_soc_dai掛到一個鏈表上。
name指定codec名稱;.codec_dai指向CODEC定義的snd_soc_dai結構體;.cpu_dai指向I2S定義的snd_soc_dai結構體;.ops接下來分析。
- /* SoC audio ops */
- struct snd_soc_ops {
- int (*startup)(struct snd_pcm_substream *);
- void (*shutdown)(struct snd_pcm_substream *);
- int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
- int (*hw_free)(struct snd_pcm_substream *);
- int (*prepare)(struct snd_pcm_substream *);
- int (*trigger)(struct snd_pcm_substream *, int);
- };
CODEC:控制接口及芯片基本初始化
PCM:pcm dma操作
I2S:i2s配置操作
之後i2s和pcm其實都跟codec差不多了,只需要理解alsa-core、<soc>、<codec、pcm、i2s>三層的關係。其中codec、pcm、i2s可以看做同層的,分別對於音頻設備的control、dma、i2s接口;<codec、pcm、i2s>會分別export相關結構體給<soc>層,<soc>層將音頻設備三部分與CPU Spec聯結起來,其probe順序是<SOC>.probe-><codec, pcm, i2s>.probe;另外<codec、pcm、i2s>在各自的module_init中將自身註冊到alsa-core中。