参考:
https://elixir.bootlin.com/linux/v4.9.218/source/sound/soc/soc-core.c#L3314
https://blog.csdn.net/DroidPhone/article/details/7283833
在移动设备中,Codec的作用可以归结为4种,分别是:
对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
codec通过调用snd_soc_register_codec进行注册。
在snd_soc_register_codec函数内主要创建和分配snd_soc_codec结构体的内存,将snd_soc_codec_driver 赋值给codec->driver,snd_soc_dai_driver注册到codec->component里,将创建的codec->component加到全局列表component_list中,将创建的codec加到全局列表codec_list中,方便后续注册Machine driver时调用到。
/** * snd_soc_register_codec - Register a codec with the ASoC core * * @dev: The parent device for this codec * @codec_drv: Codec driver * @dai_drv: The associated DAI driver * @num_dai: Number of DAIs */ int 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) { struct snd_soc_dapm_context *dapm; struct snd_soc_codec *codec; struct snd_soc_dai *dai; int ret, i; dev_dbg(dev, "codec register %s\n", dev_name(dev)); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; codec->component.codec = codec; ret = snd_soc_component_initialize(&codec->component, &codec_drv->component_driver, dev); if (ret) goto err_free; if (codec_drv->probe) codec->component.probe = snd_soc_codec_drv_probe; if (codec_drv->remove) codec->component.remove = snd_soc_codec_drv_remove; if (codec_drv->write) codec->component.write = snd_soc_codec_drv_write; if (codec_drv->read) codec->component.read = snd_soc_codec_drv_read; codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time; dapm = snd_soc_codec_get_dapm(codec); dapm->idle_bias_off = codec_drv->idle_bias_off; dapm->suspend_bias_off = codec_drv->suspend_bias_off; if (codec_drv->seq_notifier) dapm->seq_notifier = codec_drv->seq_notifier; if (codec_drv->set_bias_level) dapm->set_bias_level = snd_soc_codec_set_bias_level; codec->dev = dev; codec->driver = codec_drv; codec->component.val_bytes = codec_drv->reg_word_size; #ifdef CONFIG_DEBUG_FS codec->component.init_debugfs = soc_init_codec_debugfs; codec->component.debugfs_prefix = "codec"; #endif if (codec_drv->get_regmap) codec->component.regmap = codec_drv->get_regmap(dev); for (i = 0; i < num_dai; i++) { fixup_codec_formats(&dai_drv[i].playback); fixup_codec_formats(&dai_drv[i].capture); } ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false); if (ret < 0) { dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret); goto err_cleanup; } list_for_each_entry(dai, &codec->component.dai_list, list) dai->codec = codec; mutex_lock(&client_mutex); snd_soc_component_add_unlocked(&codec->component); list_add(&codec->list, &codec_list); mutex_unlock(&client_mutex); dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n", codec->component.name); return 0; err_cleanup: snd_soc_component_cleanup(&codec->component); err_free: kfree(codec); return ret; }
1.首先分配snd_soc_codec的内存。
/* SoC Audio Codec device */ struct snd_soc_codec { struct device *dev; const struct snd_soc_codec_driver *driver; struct list_head list; struct list_head card_list; /* runtime */ unsigned int cache_bypass:1; /* Suppress access to the cache */ unsigned int suspended:1; /* Codec is in suspend PM state */ unsigned int cache_init:1; /* codec cache has been initialized */ /* codec IO */ void *control_data; /* codec control (i2c/3wire) data */ hw_write_t hw_write; void *reg_cache; /* component */ struct snd_soc_component component; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_reg; #endif };
2.调用snd_soc_component_initiallize来初始化codec->component, 主要使用snd_soc_codec_driver的成员component_driver来初始化component。
snd_soc_codec_driver结构体如下:
/* codec driver */ struct snd_soc_codec_driver { /* driver ops */ int (*probe)(struct snd_soc_codec *); int (*remove)(struct snd_soc_codec *); int (*suspend)(struct snd_soc_codec *); int (*resume)(struct snd_soc_codec *); struct snd_soc_component_driver component_driver; /* codec wide operations */ int (*set_sysclk)(struct snd_soc_codec *codec, int clk_id, int source, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); /* codec IO */ struct regmap *(*get_regmap)(struct device *); unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); unsigned int reg_cache_size; short reg_cache_step; short reg_word_size; const void *reg_cache_default; /* codec bias level */ int (*set_bias_level)(struct snd_soc_codec *, enum snd_soc_bias_level level); bool idle_bias_off; bool suspend_bias_off; void (*seq_notifier)(struct snd_soc_dapm_context *, enum snd_soc_dapm_type, int); bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */ };
snd_soc_component_driver结构体如下,主要包含controls, dapm_widget, dapm_routes,在Codec DAI中,dapm主要描述了codec内部widget是如何link的。
/* component interface */ struct snd_soc_component_driver { const char *name; /* Default control and setup, added after probe() is run */ const struct snd_kcontrol_new *controls; unsigned int num_controls; const struct snd_soc_dapm_widget *dapm_widgets; unsigned int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes; int (*probe)(struct snd_soc_component *); void (*remove)(struct snd_soc_component *); /* DT */ int (*of_xlate_dai_name)(struct snd_soc_component *component, struct of_phandle_args *args, const char **dai_name); void (*seq_notifier)(struct snd_soc_component *, enum snd_soc_dapm_type, int subseq); int (*stream_event)(struct snd_soc_component *, int event); /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; };
具体的初始化过程如下:将component driver赋值给component的driver成员,使用component driver dapm_widget和dapm_route赋值给component相应的成员,初始化component的dai_list.
static int snd_soc_component_initialize(struct snd_soc_component *component, const struct snd_soc_component_driver *driver, struct device *dev) { struct snd_soc_dapm_context *dapm; component->name = fmt_single_name(dev, &component->id); if (!component->name) { dev_err(dev, "ASoC: Failed to allocate name\n"); return -ENOMEM; } component->dev = dev; component->driver = driver; component->probe = component->driver->probe; component->remove = component->driver->remove; dapm = &component->dapm; dapm->dev = dev; dapm->component = component; dapm->bias_level = SND_SOC_BIAS_OFF; dapm->idle_bias_off = true; if (driver->seq_notifier) dapm->seq_notifier = snd_soc_component_seq_notifier; if (driver->stream_event) dapm->stream_event = snd_soc_component_stream_event; component->controls = driver->controls; component->num_controls = driver->num_controls; component->dapm_widgets = driver->dapm_widgets; component->num_dapm_widgets = driver->num_dapm_widgets; component->dapm_routes = driver->dapm_routes; component->num_dapm_routes = driver->num_dapm_routes; INIT_LIST_HEAD(&component->dai_list); mutex_init(&component->io_mutex); return 0; }
3.设置codec->component的probe,remove,write,read函数,即为snd_soc_codec_driver相应的成员函数。
4.调用snd_soc_register_dais来注册codec DAI
static int snd_soc_register_dais(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, size_t count, bool legacy_dai_naming) { struct device *dev = component->dev; struct snd_soc_dai *dai; unsigned int i; int ret; dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count); component->dai_drv = dai_drv; for (i = 0; i < count; i++) { dai = soc_add_dai(component, dai_drv + i, count == 1 && legacy_dai_naming); if (dai == NULL) { ret = -ENOMEM; goto err; } } return 0; err: snd_soc_unregister_dais(component); return ret; }
在snd_soc_register_dais函数内遍历snd_soc_dai_driver 列表,通过soc_add_dai将每个dai都添加到component的dai_list。
在snd_add_dai函数中,创建snd_soc_dai结构体dai,并使用dai_drv对snd_soc_dai进行初始化,最后将创建的dai添加到component->dai_list中。在此函数中还会将dai_drv的name copy给dai的name,后续Machine driver就是通过这个name来找到codec DAI的。
/* Create a DAI and add it to the component's DAI list */ static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, bool legacy_dai_naming) { struct device *dev = component->dev; struct snd_soc_dai *dai; dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev)); dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); if (dai == NULL) return NULL; /* * Back in the old days when we still had component-less DAIs, * instead of having a static name, component-less DAIs would * inherit the name of the parent device so it is possible to * register multiple instances of the DAI. We still need to keep * the same naming style even though those DAIs are not * component-less anymore. */ if (legacy_dai_naming && (dai_drv->id == 0 || dai_drv->name == NULL)) { dai->name = fmt_single_name(dev, &dai->id); } else { dai->name = fmt_multiple_name(dev, dai_drv); if (dai_drv->id) dai->id = dai_drv->id; else dai->id = component->num_dai; } if (dai->name == NULL) { kfree(dai); return NULL; } dai->component = component; dai->dev = dev; dai->driver = dai_drv; if (!dai->driver->ops) dai->driver->ops = &null_dai_ops; list_add(&dai->list, &component->dai_list); component->num_dai++; dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); return dai; }
5.设置codec DAI的成员codec为当前创建的codec。
6.调用snd_soc_component_add_unlocked将codec->component加到全局列表component,并将创建的codec加到全局列表codec_list.
在后续注册card时(snd_soc_register_card),snd_soc_instantiate_card中会调用soc_bind_dai_link函数,在此函数中通过dai的name来找到machine driver的某条dai_link上的codec dai,并通过codec dai找到相应的codec, 具体如下:
struct snd_soc_dai_link_component *codecs = dai_link->codecs; /* Find CODEC from registered CODECs */ codec_dais = rtd->codec_dais; for (i = 0; i < rtd->num_codecs; i++) { codec_dais[i] = snd_soc_find_dai(&codecs[i]); if (!codec_dais[i]) { dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", codecs[i].dai_name); goto _err_defer; } } /* Single codec links expect codec and codec_dai in runtime data */ rtd->codec_dai = codec_dais[0]; rtd->codec = rtd->codec_dai->codec;
其中dai_link->codecs是在snd_soc_init_multicodec()中创建和初始化的。
static int snd_soc_init_multicodec(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) { /* Legacy codec/codec_dai link is a single entry in multicodec */ if (dai_link->codec_name || dai_link->codec_of_node || dai_link->codec_dai_name) { dai_link->num_codecs = 1; dai_link->codecs = devm_kzalloc(card->dev, sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); if (!dai_link->codecs) return -ENOMEM; dai_link->codecs[0].name = dai_link->codec_name; dai_link->codecs[0].of_node = dai_link->codec_of_node; dai_link->codecs[0].dai_name = dai_link->codec_dai_name; } if (!dai_link->codecs) { dev_err(card->dev, "ASoC: DAI link has no CODECs\n"); return -EINVAL; } return 0; }
在soc_probe_link_components中会对codec dai的component进行probe:
static int soc_probe_link_components(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd, int order) { struct snd_soc_platform *platform = rtd->platform; struct snd_soc_component *component; int i, ret; /* probe the CPU-side component, if it is a CODEC */ component = rtd->cpu_dai->component; if (component->driver->probe_order == order) { ret = soc_probe_component(card, component); if (ret < 0) return ret; } /* probe the CODEC-side components */ for (i = 0; i < rtd->num_codecs; i++) { component = rtd->codec_dais[i]->component; if (component->driver->probe_order == order) { ret = soc_probe_component(card, component); if (ret < 0) return ret; } } /* probe the platform */ if (platform->component.driver->probe_order == order) { ret = soc_probe_component(card, &platform->component); if (ret < 0) return ret; } return 0; }
在soc_probe_component函数内,对codec DAI进行probe,主要包括
将component 和dapm context的card赋值。
调用snd_soc_dapm_new_controls创建component的dapm_widget,并将widget加到card->widgets列表中.
调用snd_soc_dapm_new_dai_widgets对component dai_list每一个dai widget, widget的name为dai->driver->playback/capture.name.并将widget加到card->widgets列表中.
调用component的probe函数
调用snd_soc_dapm_add_routes对component的dapm_routes添加dapm path.
将component的dapm context加到card的dapm context列表。
static int soc_probe_component(struct snd_soc_card *card, struct snd_soc_component *component) { struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); struct snd_soc_dai *dai; int ret; if (!strcmp(component->name, "snd-soc-dummy")) return 0; if (component->card) { if (component->card != card) { dev_err(component->dev, "Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n", card->name, component->card->name); return -ENODEV; } return 0; } if (!try_module_get(component->dev->driver->owner)) return -ENODEV; component->card = card; dapm->card = card; soc_set_name_prefix(card, component); soc_init_component_debugfs(component); if (component->dapm_widgets) { ret = snd_soc_dapm_new_controls(dapm, component->dapm_widgets, component->num_dapm_widgets); if (ret != 0) { dev_err(component->dev, "Failed to create new controls %d\n", ret); goto err_probe; } } list_for_each_entry(dai, &component->dai_list, list) { ret = snd_soc_dapm_new_dai_widgets(dapm, dai); if (ret != 0) { dev_err(component->dev, "Failed to create DAI widgets %d\n", ret); goto err_probe; } } if (component->probe) { ret = component->probe(component); if (ret < 0) { dev_err(component->dev, "ASoC: failed to probe component %d\n", ret); goto err_probe; } WARN(dapm->idle_bias_off && dapm->bias_level != SND_SOC_BIAS_OFF, "codec %s can not start from non-off bias with idle_bias_off==1\n", component->name); } /* machine specific init */ if (component->init) { ret = component->init(component); if (ret < 0) { dev_err(component->dev, "Failed to do machine specific init %d\n", ret); goto err_probe; } } if (component->controls) snd_soc_add_component_controls(component, component->controls, component->num_controls); if (component->dapm_routes) snd_soc_dapm_add_routes(dapm, component->dapm_routes, component->num_dapm_routes); list_add(&dapm->list, &card->dapm_list); /* This is a HACK and will be removed soon */ if (component->codec) list_add(&component->codec->card_list, &card->codec_dev_list); return 0; err_probe: soc_cleanup_component_debugfs(component); component->card = NULL; module_put(component->dev->driver->owner); return ret; }