IMX6ul下tlv320aic3x音频驱动调试

原理图:

tlv320aic3x音频芯片

功率放大器

移植分为三步:

1.codec 驱动 sound/soc/codecs/tlv320aic3x.c
2.平台驱动 sound/soc/imx/imx-tlv320aic3x.c
3.添加板文件  

arch/arm/mach-mx6/board-mx6q_sabresd.c   和  arch/arm/mach-mx6/board-mx6q_sabresd.c


一、codec驱动是linux内核自带的,在sound/soc/codecs/下面有不同的音频芯片,根据开发的音频芯片选择或者修改。

在tlv320aic3x.c文件中有几个重要的结构体需要关注:

[cpp]  view plain  copy
  1. static struct snd_soc_dai_ops aic3x_dai_ops = {  
  2.     .hw_params    = aic3x_hw_params,  //硬件参数设置  
  3.     .digital_mute    = aic3x_mute,   //静音设置  
  4.     .set_sysclk    = aic3x_set_dai_sysclk,  //时钟设置  
  5.     .set_fmt    = aic3x_set_dai_fmt,  //格式设置  
  6. };  
  7.   
  8. static struct snd_soc_dai_driver aic3x_dai = {  
  9.     .name = "tlv320aic3x-hifi",  
  10.     .playback = {  
  11.         .stream_name = "Playback",  
  12.         .channels_min = 1,  
  13.         .channels_max = 2,  
  14.         .rates = AIC3X_RATES,  
  15.         .formats = AIC3X_FORMATS,},  
  16.     .capture = {  
  17.         .stream_name = "Capture",  
  18.         .channels_min = 1,  
  19.         .channels_max = 2,  
  20.         .rates = AIC3X_RATES,  
  21.         .formats = AIC3X_FORMATS,},  
  22.     .ops = &aic3x_dai_ops,  
  23.     .symmetric_rates = 1,  
  24. };  
需要注意的是这个.name,需要和sound/soc/imx/imx-tlv320aic3x.c中的  .codec_dai_name相同。

[cpp]  view plain  copy
  1. static struct snd_soc_dai_link imx_tlv320aic3x_dai[] = {  
  2.     {  
  3.         .name = "TLV320AIC3X",  
  4.         .stream_name = "AIC3X",  
  5.         .codec_dai_name = "tlv320aic3x-hifi",  
  6.         .codec_name = "tlv320aic3x-codec.0-0018",       //I2C-0  
  7.         .cpu_dai_name = "imx-ssi.1",              
  8.         .platform_name = "imx-pcm-audio.1",  
  9.           
  10.         .init =imx_3stack_tlv320aic3x_init,  
  11.         .ops = &imx_tlv320aic3x_hifi_ops,  
  12.     },  
  13. };  
在sound/soc/codecs/tlv320aic3x.c文件中,我们需要关注 .name,这个名字要和sound/soc/imx/imx-tlv320aic3x.c的 .codec_name关联,这样才会调用aic_i2c_probe探测函数。如果名字不同的话会发现编译好的内核找不到声卡。
[cpp]  view plain  copy
  1. /* machine i2c codec control layer */  
  2. static struct i2c_driver aic3x_i2c_driver = {  
  3.     .driver = {  
  4.         .name = "tlv320aic3x-codec",  
  5.         .owner = THIS_MODULE,  
  6.     },  
  7.     .probe  = aic3x_i2c_probe,  
  8.     .remove = aic3x_i2c_remove,  
  9.     .id_table = aic3x_i2c_id,  
  10. };  
  11.   
  12. static struct snd_soc_dai_link imx_tlv320aic3x_dai[] = {  
  13.     {  
  14.         .name = "TLV320AIC3X",  
  15.         .stream_name = "AIC3X",  
  16.         .codec_dai_name = "tlv320aic3x-hifi",  
  17.         .codec_name = "tlv320aic3x-codec.0-0018",        //I2C-0  
  18.         .cpu_dai_name = "imx-ssi.1",              
  19.         .platform_name = "imx-pcm-audio.1",  
  20.           
  21.         .init =imx_3stack_tlv320aic3x_init,  
  22.         .ops = &imx_tlv320aic3x_hifi_ops,  
  23.     },  
  24. };  
在static struct snd_soc_dai_link imx_tlv320aic3x_dai[]中,0-0018的含义是这个芯片是挂载在i2c0上,设备号为0x18,根据芯片的参数进行修改。其他地方基本不用改动。


因为是平台设备驱动,所以就要将平台驱动和平台设备进行关联。

平台驱动在sound/soc/imx/imx-tlv320aic3x.c文件中:

[cpp]  view plain  copy
  1. static struct platform_driver imx_tlv320aic3x_audio_driver = {  
  2.     .probe = imx_tlv320aic3x_probe,  
  3.     .remove = imx_tlv320aic3x_remove,  
  4.     .driver = {  
  5.            .name = "imx-tlv320",  
  6.     },  
  7. };  
与之相关联的设备在板文件arch/arm/mach-mx6/board-mx6q_sabresd.c中:

[cpp]  view plain  copy
  1. static struct platform_device mx6_audio_tlv320_device = {  
  2.         .name = "imx-tlv320",  
  3. };  
因为是通过名字进行匹配,所以二者名字一定要相同。
在板文件中,要进行i2c0的注册所和用到的引脚的配置。

 在arch/arm/mach-mx6/board-mx6q_sabresd.c中:

[cpp]  view plain  copy
  1. static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {  
  2.     {  
  3.         I2C_BOARD_INFO("ds1338", 0x68),  
  4.     },  
  5.     {  
  6.         I2C_BOARD_INFO("ov5640_mipi", 0x3c),  
  7.         .platform_data = (void *)&mipi_csi2_data,  
  8.     },  
  9.     {  
  10.         I2C_BOARD_INFO("tlv320aic3x", 0x18),  
  11.     },  
  12. };  
将音频设备挂在i2c0上,tlv320aic3x是设备名,0x18是从设备号,可以参考数据手册查找。挂在好进行注册:

[cpp]  view plain  copy
  1. imx6q_add_imx_i2c(0, &mx6q_qy_imx6s_i2c_data);  
  2. i2c_register_board_info(0, mxc_i2c0_board_info,  
  3.             ARRAY_SIZE(mxc_i2c0_board_info));  
在arch/arm/mach-mx6/board-mx6q_sabresd.h文件中配置引脚:
[cpp]  view plain  copy
  1. static iomux_v3_cfg_t mx6q_qy_imx6s_i2c_pads[] =  
  2. {  
  3.     MX6Q_PAD_CSI0_DAT8__I2C1_SDA,//i2c  
  4.     MX6Q_PAD_CSI0_DAT9__I2C1_SCL,,  
  5. };  
  6.   
  7. static iomux_v3_cfg_t mx6q_qy_imx6s_audio_pads[] = {  
  8.     /*MX6Q_PAD_CSI0_DAT4__AUDMUX_AUD3_TXC, 
  9.     MX6Q_PAD_CSI0_DAT5__AUDMUX_AUD3_TXD, 
  10.     MX6Q_PAD_CSI0_DAT6__AUDMUX_AUD3_TXFS, 
  11.     MX6Q_PAD_CSI0_DAT7__AUDMUX_AUD3_RXD,*/  
  12.   
  13.     MX6Q_PAD_GPIO_0__CCM_CLKO,   //CLK ouput  
  14.     MX6Q_PAD_SD2_CMD__GPIO_1_11, //AUDIO SD  
  15.     MX6Q_PAD_SD2_CLK__GPIO_1_10, //AUDIO REST  
  16.       
  17.     MX6Q_PAD_SD2_DAT0__AUDMUX_AUD4_RXD,  
  18.     MX6Q_PAD_SD2_DAT1__AUDMUX_AUD4_TXFS,  
  19.     MX6Q_PAD_SD2_DAT2__AUDMUX_AUD4_TXD,  
  20.     MX6Q_PAD_SD2_DAT3__AUDMUX_AUD4_TXC,  
  21. };  
因为我的开发板是外接喇叭,所以配置了功放:MX6Q_PAD_SD2_CMD__GPIO_1_11, //AUDIO SD

在arch/arm/mach-mx6/board-mx6q_sabresd.c写功能函数:

1.定义功放和复位

[cpp]  view plain  copy
  1. //audio   
  2. #define AUDIO_REST      IMX_GPIO_NR(1 , 10)  
  3. #define AUDIO_SD        IMX_GPIO_NR(1 , 11)  
  4.   
  5. static void tlv320_gpio_set(void)  
  6. {  
  7.     static void __iomem *audio_gpio_base = IO_ADDRESS(0x020e0740);//配置引脚寄存器第13位为 0=keeper  
  8.     writel(0x90B0,audio_gpio_base);  
  9.   
  10.     gpio_request(AUDIO_SD, "audio_sd");    
  11.     gpio_direction_output(AUDIO_SD, 0); //低电平有效   
  12.     msleep(1);    
  13.     gpio_set_value(AUDIO_SD, 0);  
  14.       
  15.     gpio_request(AUDIO_REST, "audio_rest");    
  16.     gpio_direction_output(AUDIO_REST, 1);    
  17.     msleep(1);    
  18.     gpio_set_value(AUDIO_REST, 1);  
  19. }  
在函数static int mxc_tlv320_init(void){}中调用:

[cpp]  view plain  copy
  1. static int mxc_tlv320_init(void)  
  2. {  
  3.         //struct clk *clko;  
  4.         //struct clk *new_parent;  
  5.         int rate;  
  6.   
  7.         clko = clk_get(NULL, "clko_clk");  
  8.         if (IS_ERR(clko)) {  
  9.                 pr_err("can't get CLKO clock.\n");  
  10.                 return PTR_ERR(clko);  
  11.         }  
  12.           
  13.         /* both audio codec and comera use CLKO clk*/  
  14.         rate = clk_round_rate(clko, 24000000);  
  15.         clk_set_rate(clko, rate);  
  16.         tlv320_data.sysclk = rate;  
  17.         clk_enable(clko);  
  18.           
  19.         tlv320_gpio_set();  
  20.         return 0;  
  21. }  

下面放上板文件和imx-tlv320aic3x.c的代码片段

arch/arm/mach-mx6/board-mx6q_sabresd.c:

[cpp]  view plain  copy
  1. static struct regulator_consumer_supply qy_imx6s_vmmc_consumers[] = {  
  2.     REGULATOR_SUPPLY("vmmc""sdhci-esdhc-imx.1"),  
  3.     REGULATOR_SUPPLY("vmmc""sdhci-esdhc-imx.2"),  
  4.     REGULATOR_SUPPLY("vmmc""sdhci-esdhc-imx.3"),  
  5.     REGULATOR_SUPPLY("vcc""spi4.1"),  
  6.     REGULATOR_SUPPLY("IOVDD""0-0018"),  
  7.     REGULATOR_SUPPLY("AVDD""0-0018"),  
  8.     REGULATOR_SUPPLY("DRVDD""0-0018"),  
  9. };  
  10.   
  11. static struct regulator_init_data qy_imx6s_vmmc_init = {  
  12.     .num_consumer_supplies = ARRAY_SIZE(qy_imx6s_vmmc_consumers),  
  13.     .consumer_supplies = qy_imx6s_vmmc_consumers,  
  14. };  
  15.   
  16. static struct fixed_voltage_config qy_imx6s_vmmc_reg_config = {  
  17.     .supply_name        = "vmmc",  
  18.     .microvolts     = 3300000,  
  19.     .gpio           = -1,  
  20.     .init_data      = &qy_imx6s_vmmc_init,  
  21. };  
  22.   
  23. static struct platform_device qy_imx6s_vmmc_reg_devices = {  
  24.     .name   = "reg-fixed-voltage",  
  25.     .id = 3,  
  26.     .dev    = {  
  27.         .platform_data = &qy_imx6s_vmmc_reg_config,  
  28.     },  
  29. };  
  30.   
  31. static struct platform_device mx6_audio_tlv320_device = {  
  32.         .name = "imx-tlv320",  
  33. };  
  34.   
  35. static int tlv320_clk_enable(int enable)  
  36. {  
  37.         if (enable)  
  38.                 clk_enable(clko);  
  39.         else  
  40.                 clk_disable(clko);  
  41.   
  42.         return 0;  
  43. }  
  44. static struct mxc_audio_platform_data tlv320_data;  
  45.   
  46. static void tlv320_gpio_set(void)  
  47. {  
  48.     static void __iomem *audio_gpio_base = IO_ADDRESS(0x020e0740);//13位  0=keeper  
  49.     writel(0x90B0,audio_gpio_base);  
  50.   
  51.     gpio_request(AUDIO_SD, "audio_sd");    
  52.     gpio_direction_output(AUDIO_SD, 0);    
  53.     msleep(1);    
  54.     gpio_set_value(AUDIO_SD, 0);  
  55.       
  56.     gpio_request(AUDIO_REST, "audio_rest");    
  57.     gpio_direction_output(AUDIO_REST, 1);    
  58.     msleep(1);    
  59.     gpio_set_value(AUDIO_REST, 1);  
  60. }  
  61. static int mxc_tlv320_init(void)  
  62. {  
  63.         //struct clk *clko;  
  64.         //struct clk *new_parent;  
  65.         int rate;  
  66.   
  67.         clko = clk_get(NULL, "clko_clk");  
  68.         if (IS_ERR(clko)) {  
  69.                 pr_err("can't get CLKO clock.\n");  
  70.                 return PTR_ERR(clko);  
  71.         }  
  72.           
  73.         /* both audio codec and comera use CLKO clk*/  
  74.         rate = clk_round_rate(clko, 24000000);  
  75.         clk_set_rate(clko, rate);  
  76.         tlv320_data.sysclk = rate;  
  77.         clk_enable(clko);  
  78.           
  79.         tlv320_gpio_set();  
  80.         return 0;  
  81. }  
  82. static struct mxc_audio_platform_data tlv320_data = {  
  83.         .ssi_num = 1,  
  84.         .src_port = 2,  
  85.         .ext_port = 4,//control playing stop  
  86.         .init = mxc_tlv320_init,  
  87.         .clock_enable = tlv320_clk_enable,  
  88. };  
  89.   
  90. static struct imx_ssi_platform_data mx6_ssi_pdata = {  
  91.         .flags = IMX_SSI_DMA | IMX_SSI_SYN,  
  92. };  
  93.   
  94.   
  95. static struct regulator_consumer_supply qy_imx6s_tlv320_consumers[] = {  
  96.     REGULATOR_SUPPLY("DVDD""0-0018"),  
  97. };  
  98.   
  99. static struct regulator_init_data qy_imx6s_tlv320_init = {  
  100.     .num_consumer_supplies = ARRAY_SIZE(qy_imx6s_tlv320_consumers),  
  101.     .consumer_supplies = qy_imx6s_tlv320_consumers,  
  102. };  
  103.   
  104. static struct fixed_voltage_config qy_imx6s_tlv320_reg_config = {  
  105.     .supply_name    = "DVDD",  
  106.     .microvolts     = 1800000,  
  107.     .gpio           = -1,  //if changed no soundcard exist  
  108.     .init_data      = &qy_imx6s_tlv320_init,  
  109. };  
  110.   
  111. static struct platform_device qy_imx6s_tlv320_reg_devices = {  
  112.     .name   = "reg-fixed-voltage",  
  113.     .id     = 4,  
  114.     .dev    = {  
  115.         .platform_data = &qy_imx6s_tlv320_reg_config,  
  116.     },  
  117. };  
  118.   
  119. static int __init imx6q_init_audio(void)  
  120. {  
  121.     platform_device_register(&qy_imx6s_tlv320_reg_devices);  
  122.     mxc_register_device(&mx6_audio_tlv320_device,  
  123.                                     &tlv320_data);  
  124.     imx6q_add_imx_ssi(1, &mx6_ssi_pdata);  
  125.     mxc_tlv320_init();  
  126.     return 0;  
  127. }  
  128.   
  129. static void __init mx6_qy_imx6s_board_init(void)  
  130. {  
  131.     ……………  
  132.     ……………  
  133.     imx6q_init_audio();  
  134.     ……………  
  135.     ……………  
  136. }  
imx-tlv320aic3x.c

[cpp]  view plain  copy
  1. #include <linux/module.h>  
  2. #include <linux/moduleparam.h>  
  3. #include <linux/device.h>  
  4. #include <linux/i2c.h>  
  5. #include <linux/fsl_devices.h>  
  6. #include <linux/gpio.h>  
  7. #include <sound/core.h>  
  8. #include <sound/pcm.h>  
  9. #include <sound/soc.h>  
  10. #include <sound/jack.h>  
  11. #include <sound/soc-dapm.h>  
  12. #include <asm/mach-types.h>  
  13. #include <mach/audmux.h>  
  14.   
  15. #include "../codecs/tlv320aic23.h"  
  16. #include "imx-ssi.h"  
  17.   
  18. #define CODEC_CLOCK 24000000  
  19.   
  20. static int qiyang_tlv320_hw_params(struct snd_pcm_substream *substream,  
  21.                 struct snd_pcm_hw_params *params)  
  22. {  
  23.     struct snd_soc_pcm_runtime *rtd = substream->private_data;  
  24.     struct snd_soc_dai *codec_dai = rtd->codec_dai;  
  25.     struct snd_soc_dai *cpu_dai = rtd->cpu_dai;  
  26.     int ret;  
  27.   
  28.     ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |  
  29.                   SND_SOC_DAIFMT_NB_NF |  
  30.                   SND_SOC_DAIFMT_CBM_CFM);  
  31.     if (ret) {  
  32.         pr_err("%s: failed set cpu dai format\n", __func__);  
  33.         return ret;  
  34.     }  
  35.   
  36.     ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |  
  37.                   SND_SOC_DAIFMT_NB_NF |  
  38.                   SND_SOC_DAIFMT_CBM_CFM);  
  39.     if (ret) {  
  40.         pr_err("%s: failed set codec dai format\n", __func__);  
  41.         return ret;  
  42.     }  
  43.   
  44.     ret = snd_soc_dai_set_sysclk(codec_dai, 0,  
  45.                      CODEC_CLOCK, SND_SOC_CLOCK_OUT);  
  46.     if (ret) {  
  47.         pr_err("%s: failed setting codec sysclk\n", __func__);  
  48.         return ret;  
  49.     }  
  50.     snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);  
  51.   
  52.     ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,  
  53.                 SND_SOC_CLOCK_IN);  
  54.     if (ret) {  
  55.         pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");  
  56.         return ret;  
  57.     }  
  58.   
  59.     return 0;  
  60. }  
  61.   
  62. static struct snd_soc_ops qiyang_tlv320_snd_ops = {  
  63.     .hw_params  = qiyang_tlv320_hw_params,  
  64. };  
  65.   
  66. static struct snd_soc_dai_link qiyang_tlv320_dai = {  
  67.     .name       = "tlv320aic3x",  
  68.     .stream_name    = "TLV320AIC3X",  
  69.     .codec_dai_name = "tlv320aic3x-hifi",  
  70.     .codec_name = "tlv320aic3x-codec.0-0018",//i2c0  
  71.     .platform_name  = "imx-pcm-audio.1",  
  72.     .cpu_dai_name   = "imx-ssi.1",  
  73.     .ops        = &qiyang_tlv320_snd_ops,  
  74. };  
  75.   
  76. static struct snd_soc_card qiyang_tlv320 = {  
  77.     .name       = "tlv320-audio",  
  78.     .dai_link   = &qiyang_tlv320_dai,  
  79.     .num_links  = 1,  
  80. };  
  81. static int imx_audmux_config(int slave, int master)  
  82. {  
  83.         unsigned int ptcr, pdcr;  
  84.         slave = slave - 1;  
  85.         master = master - 1;  
  86.   
  87.         // SSI0 mastered by port 4   
  88.         ptcr = MXC_AUDMUX_V2_PTCR_SYN |  
  89.                 MXC_AUDMUX_V2_PTCR_TFSDIR |  
  90.                 MXC_AUDMUX_V2_PTCR_TFSEL(master) |  
  91.                 MXC_AUDMUX_V2_PTCR_TCLKDIR |  
  92.                 MXC_AUDMUX_V2_PTCR_TCSEL(master);  
  93.         pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);  
  94.         mxc_audmux_v2_configure_port(slave, ptcr, pdcr);  
  95.   
  96.         ptcr = MXC_AUDMUX_V2_PTCR_SYN;  
  97.         pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);  
  98.         mxc_audmux_v2_configure_port(master, ptcr, pdcr);  
  99.   
  100.         return 0;  
  101. }  
  102. static int __devinit imx_tlv320_probe(struct platform_device *pdev)  
  103. {  
  104.     //printk("*****************888imx_tlv320_probe\n");  
  105.         struct mxc_audio_platform_data *plat = pdev->dev.platform_data;  
  106.   
  107.         int ret = 0;  
  108.   
  109.         imx_audmux_config(plat->src_port, plat->ext_port);  
  110.   
  111.         ret = -EINVAL;  
  112.         if (plat->init && plat->init())  
  113.                 return ret;  
  114.         return 0;  
  115. }  
  116. static int imx_tlv320_remove(struct platform_device *pdev)  
  117. {  
  118.         struct mxc_audio_platform_data *plat = pdev->dev.platform_data;  
  119.   
  120.         if (plat->finit)  
  121.                 plat->finit();  
  122.   
  123.         return 0;  
  124. }  
  125.   
  126. static struct platform_driver imx_tlv320_audio_driver = {  
  127.         .probe = imx_tlv320_probe,  
  128.         .remove = imx_tlv320_remove,  
  129.         .driver = {  
  130.                    .name = "imx-tlv320",  
  131.                    },  
  132. };  
  133.   
  134. static struct platform_device *qiyang_tlv320_snd_device;  
  135.   
  136. static int __init qiyang_tlv320_init(void)  
  137. {  
  138.     int ret;  
  139.     //printk("********************************************************11111!%d\n",machine_arch_type);  
  140.     ret = platform_driver_register(&imx_tlv320_audio_driver);  
  141.         if (ret)  
  142.                 return -ENOMEM;  
  143.     qiyang_tlv320_snd_device = platform_device_alloc("soc-audio", 6);  
  144.     if (!qiyang_tlv320_snd_device)  
  145.         return -ENOMEM;  
  146.       
  147.     platform_set_drvdata(qiyang_tlv320_snd_device, &qiyang_tlv320);  
  148.     ret = platform_device_add(qiyang_tlv320_snd_device);  
  149.       
  150.       
  151.       
  152.     if (ret) {  
  153.         printk(KERN_ERR "ASoC: Platform device allocation failed\n");  
  154.         platform_device_put(qiyang_tlv320_snd_device);  
  155.     }  
  156.     //printk("********************************************************2222222!\n");  
  157.     return ret;  
  158. }  
  159.   
  160. static void __exit qiyang_tlv320_exit(void)  
  161. {  
  162.     platform_driver_unregister(&imx_tlv320_audio_driver);  
  163.     platform_device_unregister(qiyang_tlv320_snd_device);  
  164. }  
  165.   
  166. module_init(qiyang_tlv320_init);  
  167. module_exit(qiyang_tlv320_exit);  
  168.   
  169.   
  170. MODULE_AUTHOR("allen young <yangcenhao@gmail.com>");  
  171. MODULE_DESCRIPTION("FUCK ALSA SoC driver");  
  172. <p>ODULE_LICENSE("GPL");</p><p>调试</p><p>/usr/test/test.wav</p><p>1、录音arecord -f dat test.wav</p><p>2、播放录音aplay test.wav</p>  
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值