linux-alsa详解6 ASOC-codec
1. Codec简介
在移动设备中,Codec的作用可以归结为4种,分别是:
(1)对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
(2)对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
(3)对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
(4)对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制.ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上.以下的讨论基于wolfson的Codec芯片WM8994。
2. ASoC中对Codec的数据抽象
描述Codec的最主要的几个数据结构分别:
snd_soc_codec,
snd_soc_codec_driver,
snd_soc_dai,
snd_soc_dai_driver;
snd_soc_dai_ops 。
其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也有用到,Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接.下面我们先看看这几个结构的定义 ,定义位于/include/sound/soc.h
2.1 结构体snd_soc_codec
1 /* SoC Audio Codec device */ 2 struct snd_soc_codec { 3 struct device *dev;/* 指向Codec设备的指针 */ 4 const struct snd_soc_codec_driver *driver;/* 指向该codec的驱动的指针 */ 5 6 struct list_head list; 7 struct list_head card_list; 8 9 /* runtime */ 10 unsigned int cache_bypass:1; /* Suppress access to the cache */ 11 unsigned int suspended:1; /* Codec is in suspend PM state */ 12 unsigned int cache_init:1; /* codec cache has been initialized */ 13 14 /* codec IO */ 15 void *control_data; /* codec control (i2c/3wire) data *//* 该指针指向的结构用于对codec的控制,通常和read,write字段联合使用 */ 16 hw_write_t hw_write; 17 void *reg_cache; 18 19 /* component */ 20 struct snd_soc_component component; 21 22 #ifdef CONFIG_DEBUG_FS 23 struct dentry *debugfs_reg; 24 #endif 25 }
2.2 snd_soc_codec_driver
1 /* codec driver */ 2 struct snd_soc_codec_driver { 3 4 /* driver ops */ 5 int (*probe)(struct snd_soc_codec *);/* codec驱动的probe函数,由snd_soc_instantiate_card回调 */ 6 int (*remove)(struct snd_soc_codec *); 7 int (*suspend)(struct snd_soc_codec *);/* 电源管理 */ 8 int (*resume)(struct snd_soc_codec *);/* 电源管理 */ 9 struct snd_soc_component_driver component_driver; 10 11 /* codec wide operations */ 12 int (*set_sysclk)(struct snd_soc_codec *codec, 13 int clk_id, int source, unsigned int freq, int dir); 14 int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source, 15 unsigned int freq_in, unsigned int freq_out); 16 17 /* codec IO */ 18 struct regmap *(*get_regmap)(struct device *); 19 unsigned int (*read)(struct snd_soc_codec *, unsigned int); 20 int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); 21 unsigned int reg_cache_size; 22 short reg_cache_step; 23 short reg_word_size; 24 const void *reg_cache_default; 25 26 /* codec bias level */ 27 int (*set_bias_level)(struct snd_soc_codec *, 28 enum snd_soc_bias_level level); 29 bool idle_bias_off; 30 bool suspend_bias_off; 31 32 void (*seq_notifier)(struct snd_soc_dapm_context *, 33 enum snd_soc_dapm_type, int); 34 35 bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */ 36 }
2.3 snd_soc_dai
1 /* 2 * Digital Audio Interface runtime data. 3 * 4 * Holds runtime data for a DAI. 5 */ 6 struct snd_soc_dai { 7 const char *name; 8 int id; 9 struct device *dev; 10 11 /* driver ops */ 12 struct snd_soc_dai_driver *driver; 13 14 /* DAI runtime info */ 15 unsigned int capture_active:1; /* stream is in use */ 16 unsigned int playback_active:1; /* stream is in use */ 17 unsigned int symmetric_rates:1; 18 unsigned int symmetric_channels:1; 19 unsigned int symmetric_samplebits:1; 20 unsigned int active; 21 unsigned char probed:1; 22 23 struct snd_soc_dapm_widget *playback_widget; 24 struct snd_soc_dapm_widget *capture_widget; 25 26 /* DAI DMA data */ 27 void *playback_dma_data; 28 void *capture_dma_data; 29 30 /* Symmetry data - only valid if symmetry is being enforced */ 31 unsigned int rate; 32 unsigned int channels; 33 unsigned int sample_bits; 34 35 /* parent platform/codec */ 36 struct snd_soc_codec *codec; 37 struct snd_soc_component *component; 38 39 /* CODEC TDM slot masks and params (for fixup) */ 40 unsigned int tx_mask; 41 unsigned int rx_mask; 42 43 struct list_head list; 44 }
2.4 snd_soc_dai_driver
1 /* 2 * Digital Audio Interface Driver. 3 * 4 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97 5 * operations and capabilities. Codec and platform drivers will register this 6 * structure for every DAI they have. 7 * 8 * This structure covers the clocking, formating and ALSA operations for each 9 * interface. 10 */ 11 struct snd_soc_dai_driver { 12 /* DAI description */ 13 const char *name; 14 unsigned int id; 15 unsigned int base; 16 struct snd_soc_dobj dobj; 17 18 /* DAI driver callbacks */ 19 int (*probe)(struct snd_soc_dai *dai); 20 int (*remove)(struct snd_soc_dai *dai); 21 int (*suspend)(struct snd_soc_dai *dai); 22 int (*resume)(struct snd_soc_dai *dai); 23 /* compress dai */ 24 int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num); 25 /* DAI is also used for the control bus */ 26 bool bus_control; 27 28 /* ops */ 29 const struct snd_soc_dai_ops *ops; 30 31 /* DAI capabilities */ 32 struct snd_soc_pcm_stream capture; 33 struct snd_soc_pcm_stream playback; 34 unsigned int symmetric_rates:1; 35 unsigned int symmetric_channels:1; 36 unsigned int symmetric_samplebits:1; 37 38 /* probe ordering - for components with runtime dependencies */ 39 int probe_order; 40 int remove_order; 41 }
2.5结构体snd_soc_dai_ops
1 struct snd_soc_dai_ops { 2 /* 3 * DAI clocking configuration, all optional. 4 * Called by soc_card drivers, normally in their hw_params. 5 */ 6 int (*set_sysclk)(struct snd_soc_dai *dai, 7 int clk_id, unsigned int freq, int dir); 8 int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, 9 unsigned int freq_in, unsigned int freq_out); 10 int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div); 11 int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio); 12 13 /* 14 * DAI format configuration 15 * Called by soc_card drivers, normally in their hw_params. 16 */ 17 int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt); 18 int (*xlate_tdm_slot_mask)(unsigned int slots, 19 unsigned int *tx_mask, unsigned int *rx_mask); 20 int (*set_tdm_slot)(struct snd_soc_dai *dai, 21 unsigned int tx_mask, unsigned int rx_mask, 22 int slots, int slot_width); 23 int (*set_channel_map)(struct snd_soc_dai *dai, 24 unsigned int tx_num, unsigned int *tx_slot, 25 unsigned int rx_num, unsigned int *rx_slot); 26 int (*set_tristate)(struct snd_soc_dai *dai, int tristate); 27 28 /* 29 * DAI digital mute - optional. 30 * Called by soc-core to minimise any pops. 31 */ 32 int (*digital_mute)(struct snd_soc_dai *dai, int mute); 33 int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream); 34 35 /* 36 * ALSA PCM audio operations - all optional. 37 * Called by soc-core during audio PCM operations. 38 */ 39 int (*startup)(struct snd_pcm_substream *, 40 struct snd_soc_dai *); 41 void (*shutdown)(struct snd_pcm_substream *, 42 struct snd_soc_dai *); 43 int (*hw_params)(struct snd_pcm_substream *, 44 struct snd_pcm_hw_params *, struct snd_soc_dai *); 45 int (*hw_free)(struct snd_pcm_substream *, 46 struct snd_soc_dai *); 47 int (*prepare)(struct snd_pcm_substream *, 48 struct snd_soc_dai *); 49 /* 50 * NOTE: Commands passed to the trigger function are not necessarily 51 * compatible with the current state of the dai. For example this 52 * sequence of commands is possible: START STOP STOP. 53 * So do not unconditionally use refcounting functions in the trigger 54 * function, e.g. clk_enable/disable. 55 */ 56 int (*trigger)(struct snd_pcm_substream *, int, 57 struct snd_soc_dai *); 58 int (*bespoke_trigger)(struct snd_pcm_substream *, int, 59 struct snd_soc_dai *); 60 /* 61 * For hardware based FIFO caused delay reporting. 62 * Optional. 63 */ 64 snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, 65 struct snd_soc_dai *); 66 }
3 codec的注册
因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用,以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c。
模块的入口函数注册了一个platform driver
1 static struct platform_driver wm8994_codec_driver = { 2 .driver = { 3 .name = "wm8994-codec", 4 .pm = &wm8994_pm_ops, 5 }, 6 .probe = wm8994_probe, 7 .remove = wm8994_remove, 8 }; 9 10 module_platform_driver(wm8994_codec_driver);
3.1 看probe函数wm8994_probe
1 static int wm8994_probe(struct platform_device *pdev) 2 { 3 struct wm8994_priv *wm8994; 4 5 wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv), 6 GFP_KERNEL); 7 if (wm8994 == NULL) 8 return -ENOMEM; 9 platform_set_drvdata(pdev, wm8994); 10 11 mutex_init(&wm8994->fw_lock); 12 13 wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent); 14 15 pm_runtime_enable(&pdev->dev); 16 pm_runtime_idle(&pdev->dev); 17 18 return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,//注册codec 19 wm8994_dai, ARRAY_SIZE(wm8994_dai)); 20 }
3.2 snd_soc_register_codec
结构体snd_soc_codec_driver定义和初始化
1 static const struct snd_soc_codec_driver soc_codec_dev_wm8994 = { 2 .probe = wm8994_codec_probe, 3 .remove = wm8994_codec_remove, 4 .suspend = wm8994_codec_suspend, 5 .resume = wm8994_codec_resume, 6 .get_regmap = wm8994_get_regmap, 7 .set_bias_level = wm8994_set_bias_level, 8 }
结构体snd_soc_dai_driver 定义和初始化
1 static struct snd_soc_dai_driver wm8994_dai[] = { 2 { 3 .name = "wm8994-aif1", 4 .id = 1, 5 .playback = { 6 .stream_name = "AIF1 Playback", 7 .channels_min = 1, 8 .channels_max = 2, 9 .rates = WM8994_RATES, 10 .formats = WM8994_FORMATS, 11 .sig_bits = 24, 12 }, 13 .capture = { 14 .stream_name = "AIF1 Capture", 15 .channels_min = 1, 16 .channels_max = 2, 17 .rates = WM8994_RATES, 18 .formats = WM8994_FORMATS, 19 .sig_bits = 24, 20 }, 21 .ops = &wm8994_aif1_dai_ops, 22 }, 23 { 24 .name = "wm8994-aif2", 25 .id = 2, 26 .playback = { 27 .stream_name = "AIF2 Playback", 28 .channels_min = 1, 29 .channels_max = 2, 30 .rates = WM8994_RATES, 31 .formats = WM8994_FORMATS, 32 .sig_bits = 24, 33 }, 34 .capture = { 35 .stream_name = "AIF2 Capture", 36 .channels_min = 1, 37 .channels_max = 2, 38 .rates = WM8994_RATES, 39 .formats = WM8994_FORMATS, 40 .sig_bits = 24, 41 }, 42 .probe = wm8994_aif2_probe, 43 .ops = &wm8994_aif2_dai_ops, 44 }, 45 { 46 .name = "wm8994-aif3", 47 .id = 3, 48 .playback = { 49 .stream_name = "AIF3 Playback", 50 .channels_min = 1, 51 .channels_max = 2, 52 .rates = WM8994_RATES, 53 .formats = WM8994_FORMATS, 54 .sig_bits = 24, 55 }, 56 .capture = { 57 .stream_name = "AIF3 Capture", 58 .channels_min = 1, 59 .channels_max = 2, 60 .rates = WM8994_RATES, 61 .formats = WM8994_FORMATS, 62 .sig_bits = 24, 63 }, 64 .ops = &wm8994_aif3_dai_ops, 65 } 66 }
codec注册函数snd_soc_register_codec ,定义位于:snd-soc.c中
1 /** 2 * snd_soc_register_codec - Register a codec with the ASoC core 3 * 4 * @dev: The parent device for this codec 5 * @codec_drv: Codec driver 6 * @dai_drv: The associated DAI driver 7 * @num_dai: Number of DAIs 8 */ 9 int snd_soc_register_codec(struct device *dev, 10 const struct snd_soc_codec_driver *codec_drv, 11 struct snd_soc_dai_driver *dai_drv, 12 int num_dai) 13 { 14 struct snd_soc_dapm_context *dapm; 15 struct snd_soc_codec *codec;//定义codec结构体 16 struct snd_soc_dai *dai;//定义dai结构体 17 int ret, i; 18 19 dev_dbg(dev, "codec register %s\n", dev_name(dev)); 20 21 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);//给codec分配内存 22 if (codec == NULL) 23 return -ENOMEM; 24 25 codec->component.codec = codec;给结构体codec中的component赋值 26 27 ret = snd_soc_component_initialize(&codec->component,//给codec中component初始化,详见linux-alsa详解5 asoc-platform 中2.3
28 &codec_drv->component_driver, dev); 29 if (ret) 30 goto err_free; 31 //初始化codec结构体 32 if (codec_drv->probe) 33 codec->component.probe = snd_soc_codec_drv_probe; 34 if (codec_drv->remove) 35 codec->component.remove = snd_soc_codec_drv_remove; 36 if (codec_drv->write) 37 codec->component.write = snd_soc_codec_drv_write; 38 if (codec_drv->read) 39 codec->component.read = snd_soc_codec_drv_read; 40 codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time; 41 42 dapm = snd_soc_codec_get_dapm(codec); 43 dapm->idle_bias_off = codec_drv->idle_bias_off; 44 dapm->suspend_bias_off = codec_drv->suspend_bias_off; 45 if (codec_drv->seq_notifier) 46 dapm->seq_notifier = codec_drv->seq_notifier; 47 if (codec_drv->set_bias_level) 48 dapm->set_bias_level = snd_soc_codec_set_bias_level; 49 codec->dev = dev; 50 codec->driver = codec_drv; 51 codec->component.val_bytes = codec_drv->reg_word_size; 52 53 #ifdef CONFIG_DEBUG_FS 54 codec->component.init_debugfs = soc_init_codec_debugfs; 55 codec->component.debugfs_prefix = "codec"; 56 #endif 57 58 if (codec_drv->get_regmap) 59 codec->component.regmap = codec_drv->get_regmap(dev); 60 61 for (i = 0; i < num_dai; i++) { 62 fixup_codec_formats(&dai_drv[i].playback); 63 fixup_codec_formats(&dai_drv[i].capture); 64 } 65 66 ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);//注册codec dai,将dai介入component->dai_list中。详见linux-alsa详解5 asoc-platform 3.6
67 if (ret < 0) { 68 dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret); 69 goto err_cleanup; 70 } 71 72 list_for_each_entry(dai, &codec->component.dai_list, list) 73 dai->codec = codec; 74 75 mutex_lock(&client_mutex); 76 snd_soc_component_add_unlocked(&codec->component);//将codec中的component加入全局变量component_list中 77 list_add(&codec->list, &codec_list); 78 mutex_unlock(&client_mutex); 79 80 dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n", 81 codec->component.name); 82 return 0; 83 84 err_cleanup: 85 snd_soc_component_cleanup(&codec->component); 86 err_free: 87 kfree(codec); 88 return ret; 89 }
3.3 snd_soc_register_codec 调用的api
linux-alsa详解5 asoc-platform已讲解不再重复。
snd_soc_component_initialize:
用于初始化snd_soc_component和snd_soc_component->driver,同上platfrom,driver也是嵌入在component结构体中
snd_soc_component_driver是由platform_drv->component_driver获取的。
函数snd_soc_register_dais:
(1)新建了一个结构体snd_soc_dai
(2)调用函数soc_add_dai,初始化并返回snd_soc_dai。并见codec dai加入codec->component中的dai_list中
snd_soc_component_add_unlocked:
函数 snd_soc_component_add_unlocked
将上文中初始化的component加入链表component_list。
component_list是一个全局链表,定义位于snd_soc.c中
4 codec对应的platfrom_device
前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c.
WM8994本身具备多种功能,除了codec外,它还有作为LDO和GPIO使用,这几种功能共享一些IO和中断资源,linux为这种设备提供了一套标准的实现方法:mfd设备.其基本思想是为这些功能的公共部分实现一个父设备,以便共享某些系统资源和功能,然后每个子功能实现为它的子设备,这样既共享了资源和代码,又能实现合理的设备层次结构,主要利用到的API就是:mfd_add_devices(),mfd_remove_devices(),mfd_cell_enable(),mfd_cell_disable(),mfd_clone_cell().
回到wm8994-core.c中,因为WM8994使用I2C进行内部寄存器的存取,它首先注册了一个I2C驱动:
1 static struct i2c_driver wm8994_i2c_driver = { 2 .driver = { 3 .name = "wm8994", 4 .pm = &wm8994_pm_ops, 5 .of_match_table = of_match_ptr(wm8994_of_match), 6 }, 7 .probe = wm8994_i2c_probe, 8 .remove = wm8994_i2c_remove, 9 .id_table = wm8994_i2c_id, 10 }; 11 12 module_i2c_driver(wm8994_i2c_driver);
4.1 probe函数
进入wm8994_i2c_probe()函数,它先申请了一个wm8994结构的变量,该变量被作为这个I2C设备的driver_data使用,上面已经讲过,codec作为它的子设备,将会取出并使用这个driver_data.接下来,本函数利用devm_regmap_init_i2c()初始化并获得一个regmap结构,该结构主要用于后续基于regmap机制的寄存器I/O,关于regmap我们留在后面再讲.最后,通过wm8994_device_init()来添加mfd子设备
1 static int wm8994_i2c_probe(struct i2c_client *i2c, 2 const struct i2c_device_id *id) 3 { 4 const struct of_device_id *of_id; 5 struct wm8994 *wm8994; 6 int ret; 7 8 wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL); 9 if (wm8994 == NULL) 10 return -ENOMEM; 11 12 i2c_set_clientdata(i2c, wm8994); 13 wm8994->dev = &i2c->dev; 14 wm8994->irq = i2c->irq; 15 16 if (i2c->dev.of_node) { 17 of_id = of_match_device(wm8994_of_match, &i2c->dev); 18 if (of_id) 19 wm8994->type = (enum wm8994_type)of_id->data; 20 } else { 21 wm8994->type = id->driver_data; 22 } 23 24 wm8994->regmap = devm_regmap_init_i2c(i2c, &wm8994_base_regmap_config); 25 if (IS_ERR(wm8994->regmap)) { 26 ret = PTR_ERR(wm8994->regmap); 27 dev_err(wm8994->dev, "Failed to allocate register map: %d\n", 28 ret); 29 return ret; 30 } 31 32 return wm8994_device_init(wm8994, i2c->irq); 33 }
继续进入wm8994_device_init()函数,它首先为两个LDO添加mfd子设备
1 /* Add the on-chip regulators first for bootstrapping */ 2 ret = mfd_add_devices(wm8994->dev, -1, 3 wm8994_regulator_devs, 4 ARRAY_SIZE(wm8994_regulator_devs), 5 NULL, 0);
因为WM1811,WM8994,WM8958三个芯片功能类似,因此这三个芯片都使用了WM8994的代码,所以wm8994_device_init()接下来根据不同的芯片型号做了一些初始化动作,这部分的代码就不贴了.接着,从platform_data中获得部分配置信息
1 if (pdata) { 2 wm8994->irq_base = pdata->irq_base; 3 wm8994->gpio_base = pdata->gpio_base; 4 5 /* GPIO configuration is only applied if it's non-zero */ 6 ...... 7 }
最后,初始化irq,然后添加codec子设备和gpio子设备
1 wm8994_irq_init(wm8994); 2 3 ret = mfd_add_devices(wm8994->dev, -1, 4 wm8994_devs, ARRAY_SIZE(wm8994_devs), 5 NULL, 0);
经过以上这些处理后,作为父设备的I2C设备已经准备就绪,它的下面挂着4个子设备:ldo-0,ldo-1,codec,gpio.其中,codec子设备的加入,它将会和前面所讲codec的platform driver匹配,触发probe回调完成下面所说的codec驱动的初始化工作.
5 codec的初始化
Machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行一次声卡和codec,dai,platform的匹配绑定过程,这里所说的绑定,正如Machine驱动一文中所描述,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中.一旦绑定成功,将会使得codec和dai驱动的probe回调被调用,codec的初始化工作就在该回调中完成.对于WM8994,该回调就是wm8994_codec_probe函数
(1)取出父设备的driver_data,其实就是上一节的wm8994结构变量,取出其中的regmap字段,复制到codec的control_data字段中;
(2)申请一个wm8994_priv私有数据结构,并把它设为codec设备的driver_data;
(3)通过snd_soc_codec_set_cache_io初始化regmap io,完成这一步后,就可以使用API:snd_soc_read(),snd_soc_write()对codec的寄存器进行读写了;
(4)把父设备的driver_data(struct wm8994)和platform_data保存到私有结构wm8994_priv中;
(5)因为要同时支持3个芯片型号,这里要根据芯片的型号做一些特定的初始化工作;
(6)申请必要的几个中断;
(7)设置合适的偏置电平;
(8)通过snd_soc_update_bits修改某些寄存器;
(9)根据父设备的platform_data,完成特定于平台的初始化配置;
(10)添加必要的control,dapm部件进而dapm路由信息;
至此,codec驱动的初始化完成.
参考博文:https://www.cnblogs.com/jason-lu/archive/2013/06/07/3124051.htm