2/16/2016

Audio-ALSA SOC Framework-Codec Driver ( Kernel)

1. Hardware 기본구성 및 설정

  • TI McASP 와 AIC3x HW Codec 의 연결사항 



  • BCLK (BIT CLOCK)        :  Codec Clock 제공  (Master)
  • WCLK (WORD CLOCK)  :  Codec Frame Sync 제공 (Master) 
  • RX Data                     :  RX Data (Encoding Data)
  • TX Data                     :  TX Data (Decoding Data )

위 구성을 보면 AIC3x CODEC은 BCLK과 WCLK(Frame sync)를 제공하며, Data는 이 Frame Sync의 Timing에 맞추어 전송하고 받는다.
기본적인 통신은 Synchronous이며 설정에 따라 Interface의 방식은 변경되어 진다..

  http://processors.wiki.ti.com/index.php/Sitara_Linux_Audio_Hardware_Overview

1.1 ALSA SOC-DAIFMT 설정


아래의 설정은 ALSA에서 설정하는 SOC 설정모드이며, Codec 입장에서 기술된다.

  • CBM: Codec Clock Master:  Codec Chip Clock 제공
  • CFM: Codec Frame Sync Master: Codec Chip Frame Sync 제공

CODEC는 위와 같이 CLOCK과 Frame Sync(WCLK)을 제공을 한다.
그러므로, CODEC이 BCLK Master Frame Sync Master이므로, SND_SOC_DAIFMT_CBM_CFM 이다.

  • 둘 사이의 Master/Slave 관련결정 
$ vi include/sound/soc-dai.h
#define SND_SOC_DAIFMT_CBM_CFM          (0 << 12) /* codec clk & FRM master */
#define SND_SOC_DAIFMT_CBS_CFM          (1 << 12) /* codec clk slave & FRM master */
#define SND_SOC_DAIFMT_CBM_CFS          (2 << 12) /* codec clk master & frame slave */
#define SND_SOC_DAIFMT_CBS_CFS          (3 << 12) /* codec clk & FRM slave */

  • 둘 사이의 실제적인 Interface 결정 과 Clock 
$ vi include/sound/soc-dai.h
#define SND_SOC_DAIFMT_I2S              0 /* I2S mode */
#define SND_SOC_DAIFMT_RIGHT_J          1 /* Right Justified mode */
#define SND_SOC_DAIFMT_LEFT_J           2 /* Left Justified mode */
#define SND_SOC_DAIFMT_DSP_A            3 /* L data MSB after FRM LRC */
#define SND_SOC_DAIFMT_DSP_B            4 /* L data MSB during FRM LRC */
#define SND_SOC_DAIFMT_AC97             5 /* AC97 */


#define SND_SOC_DAIFMT_NB_NF            (0 << 8) /* normal bit clock + frame */
#define SND_SOC_DAIFMT_NB_IF            (1 << 8) /* normal BCLK + inv FRM */
#define SND_SOC_DAIFMT_IB_NF            (2 << 8) /* invert BCLK + nor FRM */
#define SND_SOC_DAIFMT_IB_IF            (3 << 8) /* invert BCLK + FRM */

가장 일반적인게 I2S이며, DSP Mode A/B 혹은 AC97인데, Codec Chip이 지원되는 거에 따라 변경해서 사용하자. 

  • DM368의 최종결정 
$ vi ./sound/soc/davinci/davinci-evm.c // 현재 DM368 설정

#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
                SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF) 



  • AIC3100 의 지원되는 I2S와 DSP Mode 
아래는 AIC3100이 DSP MODE Interface방식이며, 이는 McBSP or McASP와 같이 설정을 확인해봐야한다.
Offset은 McBSP의 Data Delay에 같이 설정해야하며
Offset을 주느냐에 따라 DSP_B모드가 되며,NB_NF의 선택 BCLK과 WCLK의 극성선택이다.




2. I2C driver 기본확인사항  

HW Codec의 I2C Driver는 거의 동일하며, 실제적인 Codec의 관련설정을 하는 것이므로, 사용시 반드시 Codec Datasheet와 함께 봐야한다.

2.1 Audio Codec 등록 확인
     
아래와 같이 기본 board에서 등록이 되어있는지 확인하고, Kernel config에서지원되는 것을 확인을 하자.
만약 지원이 되지 않는다면, 이제 Audio Codec Driver를 가져와서, 이를 Porting하자 .
아래와 같이 등록을 해야, 실제 Audio Codec Driver에서 발견가능하다.
보통 Codec Driver 들은 여러종류의 칩의 지원을 하므로, 아래와 같이 board에서 이름을 정확히 정해줘야한다.

현재 DM368에, AIC3100을 올려서 수정하고 있다.


       A. DM368

$ vi arch/arm/mach-davinci/board-dm368-ipnc.c 

static struct i2c_board_info i2c_info[] = {
        {
                I2C_BOARD_INFO("tlv320aic3x", 0x18), // AIC3100의 7bit Address는 0x18 설정 
        },
};

static void __init evm_init_i2c(void)
{
        davinci_init_i2c(&i2c_pdata);
        i2c_register_board_info(1, i2c_info, ARRAY_SIZE(i2c_info));
}


       B. DM355

아래와 같이, plaform_data에 argument를 넣어 전달이 가능하다.
그러면, i2c_probe할 때, struct i2c_client *i2c 에서  i2c->dev.platform_data 전달 받을 수 있다.

$ vi arch/arm/mach-davinci/board-dm355-evm.c 
static struct i2c_board_info dm355evm_i2c_info[] = {
        {       I2C_BOARD_INFO("dm355evm_msp", 0x25),
                .platform_data = dm355evm_mmcsd_gpios,
        },
        /* { plus irq  }, */
        { I2C_BOARD_INFO("tlv320aic33", 0x1b), },
};

static void __init evm_init_i2c(void)
{
        davinci_init_i2c(&i2c_pdata);

        gpio_request(5, "dm355evm_msp");
        gpio_direction_input(5);
        dm355evm_i2c_info[0].irq = gpio_to_irq(5);

        i2c_register_board_info(1, dm355evm_i2c_info,
                        ARRAY_SIZE(dm355evm_i2c_info));
}

2.2 Audio Codec Driver 기본동작 

    AIC3x Driver 즉 HW Codec Driver 예제이며 구조는 다음과 같다.
    1. module init 이 호출되면서, i2c_add_driver 호출하여, i2c driver 를 등록
    2. 이미 등록된 I2C device가 있으면, 이를 찾아 probe를 호출 (platform_driver 구조와 동일)
    3. aic3x_i2c_probe 는 snd_soc_register_codec 사용하여 Codec Driver를 등록한다.
    4. aic3x_dai를 snd_soc_dai를 등록하여 이는 codec_dai 와 cpu_dai와 mapping 동작함

    $ vi sound/soc/codecs/tlv320aic3x.c 
    
    static const struct i2c_device_id aic3x_i2c_id[] = {
            [AIC3X_MODEL_3X] = { "tlv320aic3x", 0 },
            [AIC3X_MODEL_33] = { "tlv320aic33", 0 },
            [AIC3X_MODEL_3007] = { "tlv320aic3007", 0 },
            { }
    };
    MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
    
    
    /* machine i2c codec control layer */
    static struct i2c_driver aic3x_i2c_driver = {
            .driver = {
                    .name = "tlv320aic3x-codec",
                    .owner = THIS_MODULE,
            },
            .probe  = aic3x_i2c_probe,       // 이미 i2c_register_board_info 했을 경우, 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 inline void aic3x_i2c_exit(void)
    {
            i2c_del_driver(&aic3x_i2c_driver);
    }
    
    
    static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
            .set_bias_level = aic3x_set_bias_level,  // Reset , 초기화 Codec의 상태별로 설정 
            .reg_cache_size = ARRAY_SIZE(aic3x_reg), // Cache Size
            .reg_word_size = sizeof(u8),                  
            .reg_cache_default = aic3x_reg,          // Codec Default Setting. 
            .probe = aic3x_probe,                    // aic3x_probe**  
            .remove = aic3x_remove,
            .suspend = aic3x_suspend,
            .resume = aic3x_resume,
    };
    
    
    #define AIC3X_RATES     SNDRV_PCM_RATE_8000_96000
    #define AIC3X_FORMATS   (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
                             SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
    
    static struct snd_soc_dai_ops aic3x_dai_ops = {
            .hw_params      = aic3x_hw_params,      // CODEC 관련 설정
            .digital_mute   = aic3x_mute,           // 
            .set_sysclk     = aic3x_set_dai_sysclk, // CLOCK 관련 설정
            .set_fmt        = aic3x_set_dai_fmt,    // Interface 설정 (format 설정)
    };
    
    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) // Codec Driver 등록
    { .......
    ret = snd_soc_register_codec(&i2c->dev,
                     &soc_codec_dev_aic3x, &aic3x_dai, 1);  //snd_soc_dai_driver 등록 이는 추후 SOC와 Mappping 
    ......
    }
    


    3. Codec Porting 시 체크사항 

    현재 AIC3100을 사용해야하는데, AIC3x와 정확히 호환되지 않는다.
    그리고, AIC3100 Driver (AM3xxx 계열지원) 지원이 되지만 DM368 Kernel ALSA Version과 다르다.
    커널 간에 ALSA의 아래사항을 확인해보자.


    3.1  ALSA Framework 확인 

    Kernel의 ALSA 기본 Framework 부분을 확인하자.

         A. ALSA 기본 header 체크사항 

            include/sound/soc.h   
            include/sound/soc-dapm.h

         B. Codec 에 의존된 header 체크 

          현재 TI TLV Series 사용하므로, 아래의 header를 체크하자. (AIC3100 사용)
          만약 Wolfson사의 WM Series를 사용한다면, 다른 header를 체크하자.

           include/sound/tlv.h

    3.2 I2C Mapping  확인

    Code driver 는 요즘 거의 Cache에 Mapping 시켜서 사용하는 것 같다.
    기본적으로  사용하고 싶은 register size 만큼 cache에 등록하고,
    별도의 kernel/drivers/base/regmap/regmap-i2c.ko 필요한 것 같은데,  불행히도 DM368은 지원하지 않는다.

    그래서 그냥 아래와 같이 직접 I2C에 연결해서 사용했다.
    Kernel config에 포함이 되어있지 않다면, 아래와 같이 직접 연결하여 사용해야한다.

    static int aic3100_read(struct snd_soc_codec *codec, unsigned int reg)
    {
            struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
            int ret=0;
                    
            reg = (reg & 0xff);        
            ret = i2c_smbus_read_byte_data(aic3x->control_data, reg);
            if (ret < 0) printk("%s error addr=%x \n",__func__,reg);
            return ret;
    }
    
    
    static int aic3100_write(struct snd_soc_codec *codec, unsigned int reg,
                           unsigned int value)
    {
            struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
            int ret=0;
            
            reg = (reg & 0xff);
            value = (value & 0xff);
    
            ret = i2c_smbus_write_byte_data(aic3x->control_data, reg, value);
            if (ret < 0) printk("%s error addr=%x val=%x \n",__func__,reg,value);
    
            return ret;
    }
    

    3.3 CODEC 과 CPU 의 Interface 설정

    TI Audio Driver는 현재 아래와 같은 구조로 동작하며, 다른 Driver 일 경우 변경 될 수 있다.
    모든 것이 hw_param 의해 설정이 되며, 아래와 같이 개별적으로 Driver 마다 존재한다.

    ALSA에서 hw_param가 호출이 되면, EVM 및 개별 machine, codec, cpu, platform driver의 hw_param이 호출이 된다.
    ALSA는 간단하지만 내부 Driver구조들은 제법 많이 복잡하다 관련내용은 추후 다시 설명 

    • evm_hw_param 인 경우 
    아래와 같이 Codec Driver의 CPU Driver의 set_fmt 설정 하고, Codec Driver의 sys_clk을 호출하여 설정한다.

    $ vi ./sound/soc/soc-core.c
    soc_pcm_hw_params  // 개별가 존재하면, hw_params 호출  machine  ,codec, cpu, plaftform, 
    
    
    $ vi ./sound/soc/davinci/davinci-evm.c 
    
    #define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
                    SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF) 
    
    static int evm_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *params)
    {
    .....
            /* set codec DAI configuration */
            ret = snd_soc_dai_set_fmt(codec_dai, AUDIO_FORMAT); // CODEC Driver 에  set_fmt 함수 호출, 
            if (ret < 0)
                    return ret;
    
           /* set cpu DAI configuration */
            ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT); // McBSP Driver 에  format 적용
            if (ret < 0)
                    return ret;
    
            /* set the codec system clock */
            ret = snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, SND_SOC_CLOCK_OUT); // Codec Driver 의 set_sysclk 호출
            if (ret < 0)
                    return ret;
    ...
    }