2/22/2016

Audio-ALSA SOC Framework (Kernel)

1. ALSA KERNEL Driver 전체구성

아래는 DM368 IPNC의 기준으로 보겠으며 다른 SoC로 가면 전체구조가 변경이 될 수도 있겠지만, 
거의 근본적으로 구성은 AP/SoC의 관련 Driver 만들어서 ALSA Interface에 맞게 연결하여 동작하는 구조가 비슷하므로, 관련부분을 알아보도록하자.

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 관련 부분 설명 
아래의 Link는 보면, 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의 기본구조 설명  
      http://processors.wiki.ti.com/index.php/Sitara_Linux_Audio_Driver_Overview
   

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 
PCM Interface Driver로 , Platform Driver 라고 부른다. Machine Driver와 연결이 되어 있으며, PCM의 관련처리를 이곳에서 처리한다.
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
TI사에 제공하는 SOC Driver (McBSP)이며, 이는 PCM Interface Driver와 같이 동작하며,실질적인 SoC의 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
아래의 사이트를 보면, 각 CPU 마다 Driver의 구조 및 FLOW  ,구조를 설명을 하고 있다.
  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 확인 
linux kernel log에서 아래와 같이 ALSA device에 추가 되었는지 확인을 하자.
DM368인 경우, TLVAIC3x 코덱을 사용을 하면, 2개의 McBSP Driver의 충돌로 인하여 제대로 등록이 되지 않는다.

ALSA device list:
             #0: DaVinci DM365 EVM


  • ALSA Card 등록확인
ALSA에 등록하여 사용하도록 하자 
$ 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-mcbsp platform_device를 등록을 하였지만,이 platfor_driver가 현재 두개가 존재하여 문제가 됨
이중  davinci-i2s.c  선택하여 사용함
  1. ./sound/soc/davinci/davinci-i2s.c     // 이 McBSP는 일반 Codec 사용
  2. ./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);