android audio driver
kernel\sound\soc\s3c24xx\Valar_wm89xx.c
module_init(smdk64xx_audio_init);
static int __init smdk64xx_audio_init(void)
{
smdk64xx_snd_device = platform_device_alloc("soc-audio", 0); //模块初始化时,注册了一个名为soc-audio的Platform设备
platform_set_drvdata(smdk64xx_snd_device, &smdk64xx_rt5631_snd_devdata);//把smdk64xx_rt5631_snd_devdata设到platform_device结构的dev.drvdata字段中
smdk64xx_rt5631_snd_devdata.dev = &smdk64xx_snd_device->dev;//这里引出了第一个数据结构snd_soc_card的实例smdk64xx_rt5631
ret = platform_device_add(smdk64xx_snd_device);//把这个platform_device加入到内核中去
init_hw_params(smdk64xx_snd_device); //设置codec相关的clock,reset IIS
}
其中static struct platform_device *smdk64xx_snd_device;
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
static struct snd_soc_device smdk64xx_rt5631_snd_devdata = {
.card = &smdk64xx_rt5631,
.codec_dev = &soc_codec_dev_rt5631,
};
//这里引出了第一个数据结构snd_soc_card的实例smdk64xx_rt5631
它的定义如下:
static struct snd_soc_card smdk64xx_rt5631 = {
.name = "smdk",
.platform = &s3c_dma_wrapper,
.dai_link = smdk64xx_rt5631_dai, //这里引出了数据结构snd_soc_dai_link的实例smdk64xx_rt5631_dai,它的定义如下:
.num_links = ARRAY_SIZE(smdk64xx_rt5631_dai),
.suspend_pre = smdk64xx_suspend_pre,
.resume_post = smdk64xx_resume_post,
.suspend_post = smdk64xx_wm8961_suspend_post,
.resume_pre = smdk64xx_wm8961_resume_pre,
};
static struct snd_soc_dai_link smdk64xx_rt5631_dai[] = {
{ /* Primary Playback i/f */
.name = "RT5631 PAIF RX",
.stream_name = "Playback",
.cpu_dai = &s3c64xx_i2s_v4_dai[S3C24XX_DAI_I2S],
.codec_dai = &rt5631_dai[0],
.init = smdkv2xx_rt5631_init,
.ops = &smdk64xx_ops,
},
{ /* Primary Capture i/f */
.name = "RT5631 PAIF TX",
.stream_name = "Capture",
.cpu_dai = &s3c64xx_i2s_v4_dai[S3C24XX_DAI_I2S],
.codec_dai = &rt5631_dai[0],
.init = smdkv2xx_rt5631_tx_init,
.ops = &smdk64xx_ops,
},
};
通过snd_soc_card结构,又引出了Machine驱动的另外两个数据结构:
-
snd_soc_dai_link(实例:smdk64xx_rt5631_dai)
-
snd_soc_ops(实例:smdk64xx_ops)
其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例就是smdk64xx_ops,它只实现了hw_params函数:smdk_hw_params。
/* SoC Device - the audio subsystem */
struct snd_soc_device{
struct device *dev;
struct snd_soc_card *card; //.card = &smdk64xx_rt5631
struct snd_soc_codec_device *codec_dev; //.codec_dev = &soc_codec_dev_rt5631,
void *codec_data;
};
/* SoC card */
struct snd_soc_card {
char *name; //.name = "smdk",
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); //.suspend_pre = smdk64xx_suspend_pre,
int (*suspend_post)(struct platform_device *pdev, pm_message_t state); //.suspend_post = smdk64xx_wm8961_suspend_post,
int (*resume_pre)(struct platform_device *pdev); //.resume_pre = smdk64xx_wm8961_resume_pre,
int (*resume_post)(struct platform_device *pdev); //.resume_post = smdk64xx_resume_post,
/* callbacks */
int (*set_bias_level)(struct snd_soc_card *,
enum snd_soc_bias_level level);
long pmdown_time;
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link; //.dai_link = smdk64xx_rt5631_dai,
int num_links;
struct snd_soc_device *socdev;
struct snd_soc_codec *codec;
struct snd_soc_platform *platform; //.platform = &s3c_dma_wrapper,
struct delayed_work delayed_work;
struct work_struct deferred_resume_work;
};
/* codec device */
struct snd_soc_codec_device {
int (*probe)(struct platform_device *pdev); //.probe = rt5631_probe,
int (*remove)(struct platform_device *pdev); //.remove = rt5631_remove,
int (*suspend)(struct platform_device *pdev, pm_message_t state); //.suspend = rt5631_suspend,
int (*resume)(struct platform_device *pdev); //.resume = rt5631_resume,
};
ret = platform_device_add(smdk64xx_snd_device);//具体实现在(kernel\drviers\base\platform.c)
-> device_add(&pdev->dev); //(kernel\drviers\base\core.c)
-> bus_probe_device(dev); //(kernel\drviers\base\bus.c)
-> device_attach(dev); //(kernel\drviers\base\db.c)
-> bus_for_each_drv(dev->bus, NULL, dev, __device_attach); ( )
->__device_attach ->driver_probe_device(drv, dev);
-> really_probe(dev, drv);
-> drv->probe(dev); <=> rt5631_probe ???? 因为struct snd_soc_codec_device { int (*probe)(struct platform_device *pdev); //.probe = rt5631_probe
-> static int rt5631_probe(struct platform_device *pdev) (kernel\sound\soc\codecs\rt5631.c)
{
1. -> snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); //register PCMs (kernel\sound\soc\soc-core.c)
-> soc_new_pcm(socdev, &card->dai_link[i], i);
-> 1. snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,capture, &pcm);
-> 2. platform->pcm_new(codec->card, codec_dai, pcm);
因为struct snd_soc_platform idma_soc_platform = {
.name = "s5p-lp-audio",
.pcm_ops = &s3c_idma_ops,
.pcm_new = s3c_idma_pcm_new,
.pcm_free = s3c_idma_pcm_free,
};
所以 platform->pcm_new <=> s3c_idma_pcm_new
-> s3c_idma_preallocate_buffer(pcm,SNDRV_PCM_STREAM_PLAYBACK); -> ioremap(buf->addr, buf->bytes)
2. -> snd_soc_add_controls(socdev->card->codec, rt5631_snd_controls,ARRAY_SIZE(rt5631_snd_controls));
3. -> rt5631_add_widgets(socdev->card->codec);
}
static const struct snd_kcontrol_new rt5631_snd_controls[] = {
SOC_ENUM("MIC1 Mode Control", rt5631_enum[3]),
SOC_ENUM("MIC1 Boost", rt5631_enum[6]),
SOC_ENUM("MIC2 Mode Control", rt5631_enum[4]),
SOC_ENUM("MIC2 Boost", rt5631_enum[7]),
SOC_ENUM("MONOIN Mode Control", rt5631_enum[5]),
SOC_DOUBLE("PCM Playback Volume", RT5631_STEREO_DAC_VOL_2, 8, 0, 255, 1),
SOC_DOUBLE("PCM Playback Switch", RT5631_STEREO_DAC_VOL_1,15, 7, 1, 1),
SOC_DOUBLE("MONOIN_RX Capture Volume", RT5631_MONO_INPUT_VOL, 8, 0, 31, 1),
SOC_DOUBLE("AXI Capture Volume", RT5631_AUX_IN_VOL, 8, 0, 31, 1),
SOC_SINGLE("AXO1 Playback Switch", RT5631_MONO_AXO_1_2_VOL, 15, 1, 1),
SOC_SINGLE("AXO2 Playback Switch", RT5631_MONO_AXO_1_2_VOL, 7, 1, 1),
SOC_DOUBLE("OUTVOL Playback Volume", RT5631_MONO_AXO_1_2_VOL, 8, 0, 31, 1),
SOC_DOUBLE("Speaker Playback Switch", RT5631_SPK_OUT_VOL,15, 7, 1, 1),
SOC_DOUBLE("Speaker Playback Volume", RT5631_SPK_OUT_VOL, 8, 0, 63, 1),
SOC_SINGLE("MONO Playback Switch", RT5631_MONO_AXO_1_2_VOL, 13, 1, 1),
SOC_DOUBLE("HP Playback Switch", RT5631_HP_OUT_VOL,15, 7, 1, 1),
SOC_DOUBLE("HP Playback Volume", RT5631_HP_OUT_VOL, 8, 0, 63, 1),
SOC_SINGLE_EXT("DMIC Capture Switch", VIRTUAL_REG_FOR_MISC_FUNC, 2, 1, 0,
rt5631_dmic_get, rt5631_dmic_put),
SOC_ENUM_EXT("EQ Mode", rt5631_enum[10], snd_soc_get_enum_double, rt5631_eq_sel_put),
};
static const struct snd_soc_dapm_widget rt5631_dapm_widgets_pbk[] = {
SND_SOC_DAPM_HP("HP-L/R", NULL),
SND_SOC_DAPM_SPK("SPK-OUT",NULL),
SND_SOC_DAPM_MIC("MicIn", NULL),
SND_SOC_DAPM_MIC("HpMic", NULL),
};
static const struct snd_soc_dapm_route rt5631_audio_map[] = {
//RX(play) path
{"HP-L/R", NULL, "HPOL"},
{"HP-L/R", NULL, "HPOR"},
{"SPK-OUT", NULL, "SPOL"},
{"SPK-OUT", NULL, "SPOR"},
//TX(record) path
{"MIC1", NULL, "MicIn"},
{"MIC2", NULL, "HpMic"},
};
static int smdkv2xx_rt5631_init(struct snd_soc_codec *codec)
{
enable_mclk(true);
enable_audio_i2c(1);
snd_soc_dapm_new_controls(codec, rt5631_dapm_widgets_pbk, ARRAY_SIZE(rt5631_dapm_widgets_pbk)); //create new dapm controls for every widget
snd_soc_dapm_add_routes(codec, rt5631_audio_map, ARRAY_SIZE(rt5631_audio_map)); //Add routes between DAPM widgets
snd_soc_dapm_disable_pin(codec, "SPK-OUT");//将snd_soc_dapm_widget.connected置为0;
snd_soc_dapm_disable_pin(codec, "HP-L/R"); //snd_soc_dapm_widget.connected = 0;
snd_soc_dapm_enable_pin(codec, "MicIn"); //snd_soc_dapm_widget.connected = 1;
snd_soc_dapm_disable_pin(codec, "HpMic"); //snd_soc_dapm_widget.connected = 0;
snd_soc_dapm_sync(codec); //scan and power dapm paths
//add valar audio controls
snd_soc_add_controls(codec, valar_audio_controls_rt5631, ARRAY_SIZE(valar_audio_controls_rt5631));
}
snd_soc_dapm_sync(codec);
-> dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
-> snd_soc_dapm_set_bias_level(socdev,SND_SOC_BIAS_ON);
-> card->set_bias_level(card, level); <=> static int rt5631_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) //因为codec->set_bias_level = rt5631_set_bias_level;
//设置codec power reg 0x3A,0x3B,0x3C,0x3D;
static const struct snd_kcontrol_new valar_audio_controls_rt5631[] = {
SOC_SINGLE_BOOL_EXT("Headphone Out Switch",
(unsigned long)&valar_hp_switch,
valar_get_output, valar_set_output),
SOC_SINGLE_BOOL_EXT("Speaker Out Switch",
(unsigned long)&valar_spk_switch,
valar_get_output, valar_set_output),
SOC_SINGLE_BOOL_EXT("HDMI Audio Switch",
(unsigned long)&valar_hdmi_switch,
valar_get_hdmi, valar_set_hdmi),
SOC_ENUM_EXT("Jack Function", jack_func_enum, valar_get_jack, valar_set_jack),
};
#define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_bool_ext, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = xdata }