先從幾個重要的structure 看起,並以wm8580為例,這些屬於platform driver,請見smdkc100_wm8580.c:
platform device註冊
snd_soc_device: audio subsystem
這個結構是platform device註冊最主要的,註冊就是利用這個結構來填platform drvdata。
/* 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;
};
在該platform driver是這樣宣告的:
static struct snd_soc_device smdkc100_snd_devdata = {
.card = &smdkc100,
.codec_dev = &soc_codec_dev_wm8580,
.codec_data = &smdkc100_wm8580_setup,
};
一個一個來看。
snd_soc_card
就是這張卡的描述,主要成員為
1. 名字 (name),即user看到這張卡的名字
2. 所使用的platform interface (snd_soc_platform,這個結構實作了pcm creation/destruction)
3. CPU與codec間的dai_link(snd_soc_dai_link),包括cpu_dai (I2S/PCM/AC97)及codec_dai (playback/capture)
struct snd_soc_card {
char *name;
struct device *dev;
struct list_head list;
int instantiated;
int lp_mode; /* if this card is operating in LowPower mode, where SUSPEND/RESUME doesn't matter */
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 */smdkc100
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;
};
在該platform driver是這樣宣告的
static struct snd_soc_card smdkc100 = {
.name = "smdkc100",
.lp_mode = 0,
.platform = &s3c_pcm_pdat.pcm_pltfm,
.dai_link = smdkc100_dai,
.num_links = ARRAY_SIZE(smdkc100_dai),
};
name可看到是smdkc100,來看看platform和dai_link這兩個結構
snd_soc_platform
/* SoC platform interface */
struct snd_soc_platform {
char *name;
struct list_head list;
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,
struct snd_pcm *);codec_data
void (*pcm_free)(struct snd_pcm *);
/* platform stream ops */
struct snd_pcm_ops *pcm_ops;
};
在該platform driver是設為s3c_pcm_pdat.pcm_pltfm,這個interface是宣告在s3c-pcm-lp.c。這應該是samsung audio device所共同使用的platform interface,所有的音效卡都註冊到這個platform list。其中pcm_ops定義了pcm的open/close。這是userspace每次在播放音效時都會呼叫到的。
struct s5p_pcm_pdata s3c_pcm_pdat = {
.lp_mode = 0,
.lp_buffs = {
.dma_addr[SNDRV_PCM_STREAM_PLAYBACK] = LP_TXBUFF_ADDR,
//.dma_addr[SNDRV_PCM_STREAM_CAPTURE] = LP_RXBUFF_ADDR,
},
.set_mode = s3c_pcm_setmode,
.pcm_pltfm = {
.name = "s3c-audio",
.pcm_ops = &s5p_pcm_ops,
.pcm_new = s5p_pcm_new,
.pcm_free = s5p_pcm_free_dma_buffers,
},
..........
再來是dai_link
snd_soc_dai_link
/* 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;
};
其中cpu_dai是cpu與codec之間的interface,如I2S,PCM及AC97。這就看hardware 如何設計。而codec_dai宣告在 codec driver (wm8580.c)裡,指定playback及capture的相關資訊.
以上完成了snd_soc_card的宣告。再來是snd_soc_codec_device
snd_soc_codec_device
這是將此codec註冊在codec_dai list,將evi來dce/driver match時,會來呼叫其probe。這個probe會呼叫snd_soc_new_pcms,註冊一個pcm
/* 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);
};
這屬於codec,因此定義在 codec driver wm8580.c
struct snd_soc_codec_device soc_codec_dev_wm8580 = {
.probe = wm8580_probe,
.remove = wm8580_remove,
};
再來是codec_data。
codec_data
這邊定義了有關i2c address的資訊,直接看其設定
static struct wm8580_setup_data smdkc100_wm8580_setup = {
.i2c_address = 0x1b,
};
這是有關i2c command的address,設定需看其spec的定義,通常是0x1a或0x1b。
以上有了snd_soc_card,snd_soc_codec_device和 codec_data就有足夠的資訊可以來註冊platform_device。
platform driver要init了...
static int __init smdkc100_audio_init(void)
{
int ret;
..........
smdkc100_snd_device = platform_device_alloc("soc-audio", 0);
if (!smdkc100_snd_device)
return -ENOMEM;
platform_set_drvdata(smdkc100_snd_device, &smdkc100_snd_devdata);
smdkc100_snd_devdata.dev = &smdkc100_snd_device->dev;
ret = platform_device_add(smdkc100_snd_device);
先從platform_device_alloc("soc-audio", 0)看起,其allocate了memory,然後將snd_soc_device這個結構整個塞進去這個platform device。最後執行platform_device_add(smdkc100_snd_device);
這邊要注意的是soc-audio這個關鍵字,device/ driver在 match時是用這個name。
Platform driver註冊
因此來看一下platform driver的註冊,在soc-core.c
先看看soc-core做了什麼事:
soc-core platform driver
sound/soc/soc-core.c
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
這是一個名為soc-audio的platform driver,如前篇所提,此時platform bus會以name來做match。將來若有以此名字註冊進platform bus,則會match成功。注意其宣告了probe function,將來match成功是會被觸發的。
Match
match的流程主要是確認這張卡是否該註冊的地方都有註冊,包括platform list, cpu_dai list,codec_dai list。然後就是各別執行每個list上所定義的probe (有定義才執行)。一樣先從soc-core的probe:
static int soc_probe(struct platform_device *pdev)
{
int ret = 0;
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_card *card = socdev->card;
/* Bodge while we push things out of socdev */
card->socdev = socdev;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
此時傳入的pdev就是wm8580這個platform_device,取得platform_set_drvdata(smdkc100_snd_device, &smdkc100_snd_devdata)填進去的東西。然後指定card,最後snd_soc_register_card(card):
static int snd_soc_register_card(struct snd_soc_card *card)
{
if (!card->name || !card->dev)
return -EINVAL;
INIT_LIST_HEAD(&card->list);
card->instantiated = 0;
mutex_lock(&client_mutex);
list_add(&card->list, &card_list); //加入card list
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
然後是
static void snd_soc_instantiate_cards(void)
{
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
}
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct platform_device *pdev = container_of(card->dev,
struct platform_device,
dev);
struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *dai;
int i, found, ret, ac97;
if (card->instantiated)
return;
found = 0;
list_for_each_entry(platform, &platform_list, list) //檢查platform_list是否有這張卡,此例並沒有定義
if (card->platform == platform) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "Platform %s not registered\n",
card->platform->name);
return;
}
ac97 = 0;
for (i = 0; i < card->num_links; i++) {
found = 0;
list_for_each_entry(dai, &dai_list, list) //檢查 dai_list是否有cpu_dai
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].cpu_dai->name);
return;
}
if (card->dai_link[i].cpu_dai->ac97_control)
ac97 = 1;
}
for (i = 0; i < card->num_links; i++) {
if (!card->dai_link[i].codec_dai->ops)
card->dai_link[i].codec_dai->ops = &null_dai_ops;
}
/* If we have AC97 in the system then don't wait for the
* codec. This will need revisiting if we have to handle
* systems with mixed AC97 and non-AC97 parts. Only check for
* DAIs currently; we can't do this per link since some AC97
* codecs have non-AC97 DAIs.
*/
if (!ac97)
for (i = 0; i < card->num_links; i++) {
found = 0;
list_for_each_entry(dai, &dai_list, list) //檢查 dai_list是否有codec_dai
if (card->dai_link[i].codec_dai == dai) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].codec_dai->name);
return;
}
}
/* Note that we do not current check for codec components */
dev_dbg(card->dev, "All components present, instantiating\n");
/* Found everything, bring it up */
card->pmdown_time = pmdown_time;
if (card->probe) { //wm8580並沒有實作probe
ret = card->probe(pdev);
if (ret < 0)
return;
}
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
if (cpu_dai->probe) { //cpu_dai有實作,即sound/soc/s3c/s3c-i2s.c的s3c_i2s_probe
ret = cpu_dai->probe(pdev, cpu_dai);
if (ret < 0)
goto cpu_dai_err;
}
}
if (codec_dev->probe) {
ret = codec_dev->probe(pdev); //此處就是呼叫codec driver的probe,執行ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
//並建立pcm instance
if (ret < 0)
goto cpu_dai_err;
}
codec = card->codec;
if (platform->probe) {
ret = platform->probe(pdev);
if (ret < 0)
goto platform_err;
}
/*.....以下有關dapm省略....*/
card_err:
if (platform->remove)
platform->remove(pdev);
platform_err:
if (codec_dev->remove)
codec_dev->remove(pdev);
cpu_dai_err:
for (i--; i >= 0; i--) {,
此例並沒有定義
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
if (cpu_dai->remove)
cpu_dai->remove(pdev, cpu_dai);
}
if (card->remove)
card->remove(pdev);
}
Reference
留言列表