아래는 DM368 IPNC의 기준으로 보겠으며 다른 SoC로 가면 전체구조가 변경이 될 수도 있겠지만,
거의 근본적으로 구성은 AP/SoC의 관련 Driver 만들어서 ALSA Interface에 맞게 연결하여 동작하는 구조가 비슷하므로, 관련부분을 알아보도록하자.
http://processors.wiki.ti.com/index.php/Processor_SDK_Linux_Audio
2. ALSA의 SOC 기본 Driver 구조
DM368의 SOC를 세부적으로 본다며, 아래와 같으며, 각 설명 Driver의 구성은 다음과 같다.
2.1 Machine Driver
TI-SOC의 구성 , 아래와 같이 Machine Driver가 기본설정하고 , Platform과 Codec을 연결을 해준다.
정확히 cpu_dai와 codec_dai를 mapping을 하여 동작한다.
2.2 Platform Driver
TI에서 제공하는 Audio Driver McBSP or McASP 이며 이는 EDMA와 연결하여 동작한다.
그리고, 아래와 같이 PCM Driver와 연동이 되어 동작이 된다.
APP에서 PCM을 OPEN할 경우부터 기본설정되는 부분이 아래에 다 들어가 있다.
McBSP가 HW Codec과 연결되어 실질적인 역할하며, Interface는 I2S이지만, 다른 것으로 변경가능하다 (DSP Mode)
2.3 HW Codec Driver
Codec Driver는 구조가 거의 비슷하며, I2C Driver로 Codec을 Control 하게되어 각 설정을 변경하고, 등록을 하는 것이다.
이는 soc_dai를 등록하여 동작하게 한다.
Codec를 동작하게 하기위해서는 미리 I2C Device를 등록을 해야 동작이 가능하다.
(Audio-ALSA SOC Codec Driver 참조)
http://processors.wiki.ti.com/index.php/AM335x_Audio_Driver's_Guide
http://processors.wiki.ti.com/index.php/DM81xx_AM38xx_Audio_Driver_User_Guide
3. ALSA Card 등록 및 확인
DM368인 경우, TLVAIC3x 코덱을 사용을 하면, 2개의 McBSP Driver의 충돌로 인하여 제대로 등록이 되지 않는다.
이중 davinci-i2s.c 선택하여 사용함
User에서는 ALSA만 보면 되니 복잡하지 않지만, Driver 개발자에서는 제법 복잡한 구조이다.
OSS 와 ALSA 전체구조 및 ALSA 설명
TI ALSA Codec Driver 세부구성파악
- 각 디렉토리 구성
sound sound/core/ sound/soc/codecs sound/soc/davinci sound/soc
- 각 디렉토리의 사용되어지는 파일구성
./sound/core/pcm_lib.o ./sound/core/snd-pcm.o ./sound/core/init.o ./sound/core/memory.o ./sound/core/jack.o ./sound/core/sound.o ./sound/core/timer.o ./sound/core/pcm.o ./sound/core/pcm_timer.o ./sound/core/device.o ./sound/core/control.o ./sound/core/pcm_memory.o ./sound/core/misc.o ./sound/core/snd-page-alloc.o ./sound/core/info.o ./sound/core/snd-timer.o ./sound/core/pcm_misc.o ./sound/core/snd.o ./sound/core/memalloc.o ./sound/core/pcm_native.o ./sound/soc/soc-dapm.o ./sound/soc/soc-utils.o ./sound/soc/codecs/tlv320aic3x.o ./sound/soc/codecs/snd-soc-tlv320aic3x.o ./sound/soc/davinci/snd-soc-davinci.o ./sound/soc/davinci/davinci-evm.o ./sound/soc/davinci/davinci-i2s.o ./sound/soc/davinci/davinci-pcm.o ./sound/soc/davinci/snd-soc-davinci-i2s.o ./sound/soc/davinci/snd-soc-evm.o ./sound/soc/soc-core.o ./sound/soc/snd-soc-core.o ./sound/soc/soc-cache.o ./sound/soc/soc-jack.o ./sound/soundcore.o ./sound/sound_core.o ./sound/last.o
- TI에서 제공하는 Audio 관련 부분 설명
http://processors.wiki.ti.com/index.php/Processor_SDK_Linux_Audio
2. ALSA의 SOC 기본 Driver 구조
DM368의 SOC를 세부적으로 본다며, 아래와 같으며, 각 설명 Driver의 구성은 다음과 같다.
- Codec Driver : sound/soc/codecs/tlv320aic3x.c
- Platform Driver: sound/soc/davinci/davinci-i2s.c
- Machine Driver: sound/soc/davinci/davinci-evm.c
- SOC의 기본구조 설명
2.1 Machine Driver
TI-SOC의 구성 , 아래와 같이 Machine Driver가 기본설정하고 , Platform과 Codec을 연결을 해준다.
정확히 cpu_dai와 codec_dai를 mapping을 하여 동작한다.
$ vi sound/soc/davinci/davinci-evm.c #define AUDIO_FORMAT ( SND_SOC_DAIFMT_DSP_A | \ SND_SOC_DAIFMT_CBM_CFM | \ SND_SOC_DAIFMT_IB_NF) static struct snd_soc_dai_link dm365_evm_dai = { #ifdef CONFIG_SND_DM365_AIC3X_CODEC .name = "TLV320AIC3X", .stream_name = "AIC3X", .cpu_dai_name = "davinci-mcbsp", // CPU에서 사용하는 Audio Driver name , sound/soc/davinci/davinci-i2s.c .codec_dai_name = "tlv320aic3x-hifi", // CODEC에서 등록한 SOC DAI 이름 sound/soc/codecs/tlv320aic3x.c .init = evm_aic3x_init, .codec_name = "tlv320aic3x-codec.1-0018", .ops = &evm_ops, #elif defined(CONFIG_SND_DM365_VOICE_CODEC) .name = "Voice Codec - CQ93VC", .stream_name = "CQ93", .cpu_dai_name = "davinci-vcif", .codec_dai_name = "cq93vc-hifi", .codec_name = "cq93vc-codec", #endif .platform_name = "davinci-pcm-audio", // sound/soc/davinci/davinci-pcm.c ( platform driver ) }; /* davinci dm365 evm audio machine driver */ static struct snd_soc_card dm365_snd_soc_card_evm = { // SOC Card 등록 .name = "DaVinci DM365 EVM", .dai_link = &dm365_evm_dai, .num_links = 1, }; static int __init evm_init(void) { ....... } else if (machine_is_davinci_dm365_evm() || machine_is_davinci_dm365_ipnc() || machine_is_davinci_dm368_ipnc()) { evm_snd_dev_data = &dm365_snd_soc_card_evm; index = 0; } evm_snd_device = platform_device_alloc("soc-audio", index); ///sound/soc/soc-core.c 와 연결 if (!evm_snd_device) return -ENOMEM; platform_set_drvdata(evm_snd_device, evm_snd_dev_data); ret = platform_device_add(evm_snd_device); if (ret) platform_device_put(evm_snd_device); .... } module_init(evm_init); // module init 하게 되면 아래 순서 soc_probe -> snd_soc_register_card -> snd_soc_instantiate_cards -> snd_soc_instantiate_card(struct snd_soc_card *card)
- 아래에서 Kernel Log에서 확인 가능
ALSA device list: #0: DaVinci DM365 EVM
2.2 Platform Driver
TI에서 제공하는 Audio Driver McBSP or McASP 이며 이는 EDMA와 연결하여 동작한다.
그리고, 아래와 같이 PCM Driver와 연동이 되어 동작이 된다.
- PCM Driver
APP에서 PCM을 OPEN할 경우부터 기본설정되는 부분이 아래에 다 들어가 있다.
$ vi ./sound/soc/davinci/davinci-pcm.c // PCM Driver로 File 정보를 여기서 처리 그리고, EDMA 처리 등을 담당. static struct snd_pcm_hardware pcm_hardware_playback = { // ALSA의 기본 Playback 관련설정 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), .formats = (SNDRV_PCM_FMTBIT_S16_LE), .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), .formats = (SNDRV_PCM_FMTBIT_S16_LE), .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_KNOT), .rate_min = 8000, .rate_max = 96000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128 * 1024, .period_bytes_min = 32, .period_bytes_max = 8 * 1024, .periods_min = 16, .periods_max = 255, .fifo_size = 0, }; static struct snd_pcm_hardware pcm_hardware_capture = { // ALSA의 기본 Capture 관련설정 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE), .formats = (SNDRV_PCM_FMTBIT_S16_LE), .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_KNOT), .rate_min = 8000, .rate_max = 96000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128 * 1024, .period_bytes_min = 32, .period_bytes_max = 8 * 1024, .periods_min = 16, .periods_max = 255, .fifo_size = 0, }; /* PCM 기본 동작 Driver , PCM OPEN시, HW_PARAM 정보, TRIGGER, 이는 곳 EDMA와 연결을 시킨다. */ static struct snd_pcm_ops davinci_pcm_ops = { .open = davinci_pcm_open, .close = davinci_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = davinci_pcm_hw_params, //HW_PARAM 설정 , ALSA APP에 호출될 때 호출 . .hw_free = davinci_pcm_hw_free, .prepare = davinci_pcm_prepare, // TI EDMA 준비 .trigger = davinci_pcm_trigger, // TI EDMA Control .pointer = davinci_pcm_pointer, .mmap = davinci_pcm_mmap, // DMA 관련 MMAP }; static struct snd_soc_platform_driver davinci_soc_platform = { .ops = &davinci_pcm_ops, .pcm_new = davinci_pcm_new, .pcm_free = davinci_pcm_free, }; static int __devinit davinci_soc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &davinci_soc_platform); } static struct platform_driver davinci_pcm_driver = { .driver = { .name = "davinci-pcm-audio", .owner = THIS_MODULE, }, .probe = davinci_soc_platform_probe, .remove = __devexit_p(davinci_soc_platform_remove), };
- CPU AUDIO Driver
McBSP가 HW Codec과 연결되어 실질적인 역할하며, Interface는 I2S이지만, 다른 것으로 변경가능하다 (DSP Mode)
$ vi ./sound/soc/davinci/davinci-i2s.c //MCBSP의 Main Controller 로, 실제 CPU Dai에 연결된다. static struct snd_soc_dai_ops davinci_i2s_dai_ops = { .startup = davinci_i2s_startup, .shutdown = davinci_i2s_shutdown, .prepare = davinci_i2s_prepare, .trigger = davinci_i2s_trigger, .hw_params = davinci_i2s_hw_params, // HW_PARAM 설정 .set_fmt = davinci_i2s_set_dai_fmt, .set_clkdiv = davinci_i2s_dai_set_clkdiv, }; static struct snd_soc_dai_driver davinci_i2s_dai = { // Capture/Playback I2S Interface의 동작범위 와 Format 정의 .playback = { .channels_min = 2, .channels_max = 2, .rates = DAVINCI_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .capture = { .channels_min = 2, .channels_max = 2, .rates = DAVINCI_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .ops = &davinci_i2s_dai_ops, }; static int davinci_i2s_probe(struct platform_device *pdev) { ..... EDMA 관련 초기화 ret = snd_soc_register_dai(&pdev->dev, &davinci_i2s_dai); if (ret != 0) goto err_free_mem; } static struct platform_driver davinci_mcbsp_driver = { .probe = davinci_i2s_probe, .remove = davinci_i2s_remove, .driver = { .name = "davinci-mcbsp", .owner = THIS_MODULE, }, }; static int __init davinci_i2s_init(void) { return platform_driver_register(&davinci_mcbsp_driver); } module_init(davinci_i2s_init);
2.3 HW Codec Driver
Codec Driver는 구조가 거의 비슷하며, I2C Driver로 Codec을 Control 하게되어 각 설정을 변경하고, 등록을 하는 것이다.
이는 soc_dai를 등록하여 동작하게 한다.
Codec를 동작하게 하기위해서는 미리 I2C Device를 등록을 해야 동작이 가능하다.
(Audio-ALSA SOC Codec Driver 참조)
$ vi ./sound/soc/codecs/tlv320aic3x.c // Codec Driver 현재 AIC3100으로 변경 중. 서로 호환이 되지 않음
static struct snd_soc_dai_driver aic3x_dai = {
.name = "tlv320aic3x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.ops = &aic3x_dai_ops,
};
static int aic3x_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
......
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_aic3x, &aic3x_dai, 1);
}
static struct i2c_driver aic3x_i2c_driver = {
.driver = {
.name = "tlv320aic3x-codec",
.owner = THIS_MODULE,
},
.probe = aic3x_i2c_probe,
.remove = aic3x_i2c_remove,
.id_table = aic3x_i2c_id,
};
static inline void aic3x_i2c_init(void)
{
int ret;
ret = i2c_add_driver(&aic3x_i2c_driver);
if (ret) printk(KERN_ERR "%s: error regsitering i2c driver, %d\n", __func__, ret);
}
static int __init aic3x_modinit(void)
{
int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
ret = i2c_add_driver(&aic3x_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
ret);
}
#endif
return ret;
}
module_init(aic3x_modinit);
- AM335x 와 DM81xx Driver Guide
http://processors.wiki.ti.com/index.php/AM335x_Audio_Driver's_Guide
http://processors.wiki.ti.com/index.php/DM81xx_AM38xx_Audio_Driver_User_Guide
3. ALSA Card 등록 및 확인
- Kernel Log 확인
DM368인 경우, TLVAIC3x 코덱을 사용을 하면, 2개의 McBSP Driver의 충돌로 인하여 제대로 등록이 되지 않는다.
ALSA device list: #0: DaVinci DM365 EVM
- ALSA Card 등록확인
$ vi sound/soc/davinci/davinci-evm.c /* davinci dm365 evm audio machine driver */ static struct snd_soc_card dm365_snd_soc_card_evm = { .name = "DaVinci DM365 EVM", .dai_link = &dm365_evm_dai, .num_links = 1, }; static int __init evm_init(void) { ......... } else if (machine_is_davinci_dm365_evm() || machine_is_davinci_dm365_ipnc() || machine_is_davinci_dm368_ipnc()) { evm_snd_dev_data = &dm365_snd_soc_card_evm; index = 0; } evm_snd_device = platform_device_alloc("soc-audio", index); if (!evm_snd_device) return -ENOMEM; platform_set_drvdata(evm_snd_device, evm_snd_dev_data); ret = platform_device_add(evm_snd_device); if (ret) platform_device_put(evm_snd_device); ........ } module_init(evm_init); module_exit(evm_exit);
- ALSA AUDIO Driver 등록 확인
이중 davinci-i2s.c 선택하여 사용함
- ./sound/soc/davinci/davinci-i2s.c // 이 McBSP는 일반 Codec 사용
- ./arch/arm/mach-davinci/mcbsp.c // 이 McBSP는 Voice Codec을 사용시 사용
$ vi ./arch/arm/mach-davinci/board-dm368-ipnc.c
static struct snd_platform_data dm365_evm_snd_data = {
.asp_chan_q = EVENTQ_3,
};
$ vi ./arch/arm/mach-davinci/dm365.c
static struct resource dm365_asp_resources[] = {
{
.start = DAVINCI_DM365_ASP0_BASE,
.end = DAVINCI_DM365_ASP0_BASE + SZ_8K - 1,
.flags = IORESOURCE_MEM,
},
{
.start = DAVINCI_DMA_ASP0_TX,
.end = DAVINCI_DMA_ASP0_TX,
.flags = IORESOURCE_DMA,
},
{
.start = DAVINCI_DMA_ASP0_RX,
.end = DAVINCI_DMA_ASP0_RX,
.flags = IORESOURCE_DMA,
},
};
static struct platform_device dm365_asp_device = {
.name = "davinci-mcbsp", // 현재 충돌 중. McBSP Driver 가 두 개
.id = -1,
.num_resources = ARRAY_SIZE(dm365_asp_resources),
.resource = dm365_asp_resources,
};
platform_device_register(&dm365_asp_device);