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

posted @ 2020-06-18 22:07  Action_er  阅读(1308)  评论(0编辑  收藏  举报