RK3288 + ADAU1977 연결

24530 단어 EmbeddedEmbedded

기존 작업물(RK3399를 사용하는 보드)에 올라가있는 오디오칩셋이 제대로 작동하지 않는 것 같아 디버깅 과정이 필요했고 테스트 보드를 하나 만들어야 했다..

핀맵이 호환되던 보드가 있었고 테스트 하기 좋다고 판단되어 Tinker Board를 사용하게되었다.

대부분의 칩셋은 데이터시트에 이름이 어떻게 나와있던간에 참고용 회로도를 제공함
대략 위와 같이 회로가 구성되어야함.

준비

Tinker Board 회로 확인

링크에 접속하여 Download 항목의 Schematics 을 다운로드 하면 됨.
보드에 있는 40핀짜리 헤더를 통해 메인 AP에 접근할 수 있도록 되어있음.


ADAU1977은 I2C로 컨트롤할 수 있는 ADC이다.
제어는 I2C, 데이터는 I2S 참조하면 될듯.
PIN 이름 기억해뒀다가 데이터시트 보면 된다.

RK3288 데이터시트 확인

I2S 시리얼 클럭(SCLK) LR클럭 (LRCK RX /TX) 시리얼 데이터 (SD I/O) 등… 확인 가능하다.

해야할 것

RK3288은 ADAU1977로부터 데이터를 받아야 하는 입장이다.

  • ADAU1977에서 나오는 SDATAOUT 라인을 RK3288의 i2s_sdi에 연결해야한다.
  • ADAU1977에서 나오는 BCLK 라인을 RK3288의 i2s_sclk에 연결해야한다.
  • RK3288이 데이터를 받는 입장이고 Master로 작동해야 하기 때문에
    ADAU1977에서 나오는 LRCLK 라인을 RK3288의 i2s_lrclkrx에 연결하고
    Direction은 Output이 되어야 한다.

  • AD11 - i2s_sclk - I2S_SCLK_GPIO6_A0
  • AG11 - i2s_lrckrx - I2S_LRCK_RX/GPIO6_A1
  • AE11 - i2s_sdi - I2S_SDI/GPIO6_A3

그리고 컨트롤을 위한 I2C는 SDA, SCL 연결

연결

  • I2S_SCLK_GPIO6_A0 는 GP6A0_PCM_CLK_R 이며 보드의 12번 핀에 연결된다.
  • I2S_LRCK_RX/GPIO6_A1 는 GP6A1_PCM_FS 이며 보드의 37번 핀에 연결된다.
  • I2S_SDI/GPIO6_A3 는 GP6A3_PCM_SD 이며 보드의 38번 핀에 연결된다.
  • SDA/SCL은 I2C 1에 연결하였다.

커널 설정 변경

디바이스를 추가하는 작업이기 때문에, 디바이스 트리를 수정해줘야한다.

작업 당시에는 Rockchip linux kernel에 adau1977 관련 소스가 없었다.
때문에 Raspberry pi의 커널소스를 가져왔다.

링크의 adau1977-adc.c 를 sound/soc/rockchip에 복사해준다.

빌드 준비

Kconfig 수정

config SND_SOC_ADAU1977_ADC  
     tristate "Support for ADAU1977 ADC"  
     depends on SND_SOC_ROCKCHIP_I2S  
     select SND_SOC_ADAU1977_I2S  
     help  
         Say Y or M if you want to add support for ADAU1977 ADC.

Makefile 수정

snd-soc-adau1977-adc-objs := adau1977-adc.o
 obj-$(CONFIG_SND_SOC_ADAU1977_ADC) += snd-soc-adau1977-adc.o

디바이스 드라이버 준비

링크의 adau1977-adc-overlay.dts 를 arch/arm/boot/dts/overlays에 복사해준다.

dts 수정

// Definitions for ADAU1977 ADC  
/dts-v1/;  
/plugin/;  
/ {   
    compatible = "brcm,bcm2708";   
 
    fragment@0 {
          target = <&i2c1>;
 
        __overlay__ {
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";
        
            adau1977: codec@11 {
                compatible = "adi,adau1977";
                reg = <0x11>;
                reset-gpios = <&gpio 5 0>;
                AVDD-supply = <&vdd_3v3_reg>;
            };
        };
    };
 
    fragment@1 {
        target = <&i2s>;
        __overlay__ {
            status = "okay";
        };
    };
    
    fragment@2 {
        target = <&sound>;
        __overlay__ {
            compatible = "adi,adau1977-adc";
            i2s-controller = <&i2s>;
            status = "okay";
        };
    };
};

Overlay 등록하여 사용시 디바이스가 안잡히는 문제가 있음.
rk3288-miniarm.dts에 fragment들을 직접 추가하는 것을 권장. (작동확인)

이후 defconfig 를 적용해주고 make menuconfig 실행하여 ADAU1977 활성화



config 설정을 마쳤다면 빌드 후 커널 설치경로에 이미지, 모듈, 디바이스트리 관련 파일들을 덮어쓰면 됨.

디바이스 연결 확인

보드 부팅 후 드라이버가 올라간 뒤 장치가 잡힌 것을 볼 수 있다.
다만 초기화 작업이 안되어서 드라이버 코드를 수정해줘야한다.

드라이버 코드 수정

AnalogDevice에서 제공하는 자료를 참고하여 수정하였다.

linux/sound/soc/bcm/adau1977-adc.c 파일을 수정한 뒤
linux/sound/soc/rockchip 디렉터리에 넣어 줘야함.

static int eval_adau1977_init(struct snd_soc_pcm_runtime *rtd)
{
    int ret;
    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    
    // I2S
    // ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0);
    
    // TDM
    ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0x0f, 8, 16);
 
    if (ret < 0)
        return ret;
 
    // I2S
    // return snd_soc_codec_set_sysclk(rtd->codec, ADAU1977_SYSCLK,
    //        ADAU1977_SYSCLK_SRC_LRCLK, 1228000, SND_SOC_CLOCK_IN);
 
    // TDM
    return snd_soc_codec_set_sysclk(rtd->codec, ADAU1977_SYSCLK,
            ADAU1977_SYSCLK_SRC_LRCLK, 1228000, SND_SOC_CLOCK_IN);
}

위와 같이 수정하여 사용할 수 있다.

나중에 필요할까봐 기록해두는 부분

모드 선택 (I2S/ TDM)

I2S/TDM 모드에 따라 전달할 파라미터가 다르다

int snd_soc_dai_set_tdm_slot(
	struct snd_soc_dai * dai**,
	unsigned int** tx_mask**,
	unsigned int** rx_mask**,
	int** slots**,
	int** slot_width**
);

링크 참조시 TDM모드는 다음과 같이 사용하라고 나와있다.

ret = snd_soc_dai_set_tdm_slot(
	snd_soc_dai = codec_dai,
	tx_mask = 0x00, // 0000 0000 중 선택된 비트에 순서대로 채널을 할당한다.
	rx_mask = 0x0f, // 0000 0000 중 선택된 비트에 순서대로 채널을 할당한다.
  slots = 4, // 슬롯 수이며 채널을 얼만큼 전송할 것인지 의미한다.
  slot_width = 32 // 슬롯당 데이터 길이를 의미한다.
);

위의 예시는 [0, 1, 2, 3] 슬롯에 [1, 2, 3, 4] 채널을 할당하고
각 슬롯당 데이터 길이를 32bit로 설정한 것이다.

static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = {
    {
    .name = "adau1977",
    .stream_name = "ADAU1977", 
    .cpu_dai_name = "bcm2708-i2s.0", 
    .codec_dai_name = "adau1977-hifi",
    .platform_name = "bcm2708-i2s.0",
    .codec_name = "adau1977.1-0011",
    .init = eval_adau1977_init,
 
    /* I2S
    .dai_fmt = SND_SOC_DAIFMT_I2S |
        SND_SOC_DAIFMT_NB_NF |
        SND_SOC_DAIFMT_CBM_CFM,
    },
    */
    
    // TDM
    .dai_fmt = SND_SOC_DAIFMT_DSP_B |
        SND_SOC_DAIFMT_NB_NF |
        SND_SOC_DAIFMT_CBS_CFS,
    },
};

여기서 변경된 부분은 위에서 다뤘던 것과 연관이 있다.
I2S인지 TDM인지에 따라서 설정이 다르다.

dai_fmt 는 디지털 오디오 인터페이스 포맷을 의미하며, 수정한 부분은 ADAU1977의 디지털 오디오 인터페이스 포맷을 어떻게 설정하여 사용할지를 설정하는 부분이다.

설정에 필요한 상수들은
ADAU1977의 기본적인 작동방식이 구현되어있는 adau1979.c 에 정의되어있다.

상수 관련내용

  • SND_SOC_DAIFMT_I2S
  • SND_SOC_DAIFMT_NB_NF
  • SND_SOC_DAIFMT_CBM_CFM

위 3가지 상수를 사용함

static int adau1977_set_dai_fmt(
	struct snd_soc_dai *dai,
	unsigned int fmt
)

위 함수에서 어떻게 작동하는지 확인 가능함.

SND_SOC_DAIFMT_I2S

switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    case SND_SOC_DAIFMT_I2S:
        ctrl0 |= ADAU1977_SAI_CTRL0_FMT_I2S;
        break;
    case SND_SOC_DAIFMT_LEFT_J:
        ctrl0 |= ADAU1977_SAI_CTRL0_FMT_LJ;
        invert_lrclk = !invert_lrclk;
        break;
    case SND_SOC_DAIFMT_RIGHT_J:
        ctrl0 |= ADAU1977_SAI_CTRL0_FMT_RJ_24BIT;
        adau1977->right_j = true;
        invert_lrclk = !invert_lrclk;
        break;
    case SND_SOC_DAIFMT_DSP_A:
        ctrl1 |= ADAU1977_SAI_CTRL1_LRCLK_PULSE;
        ctrl0 |= ADAU1977_SAI_CTRL0_FMT_I2S;
        invert_lrclk = false;
        break;
    case SND_SOC_DAIFMT_DSP_B:
        ctrl1 |= ADAU1977_SAI_CTRL1_LRCLK_PULSE;
        ctrl0 |= ADAU1977_SAI_CTRL0_FMT_LJ;
        invert_lrclk = false;
        break;
    default:
        return -EINVAL;
}
  • I2S
  • LEFT_J
  • RIGHT_J
  • DSP_A
  • DSP_B

SND_SOC_DAIFMT_NB_NF

switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
    invert_lrclk = false;
    break;
case SND_SOC_DAIFMT_IB_NF:
    block_power |= ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE;
    invert_lrclk = false;
    break;
case SND_SOC_DAIFMT_NB_IF:
    invert_lrclk = true;
    break;
case SND_SOC_DAIFMT_IB_IF:
    block_power |= ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE;
    invert_lrclk = true;
    break;
default:
    return -EINVAL;
}
  • NB_NF
  • IB_NF
  • NB_IF
  • IB_IF

여기서 눈여겨 볼 부분은
#define ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE BIT(6) 이다.

IB_ 일경우 block_power의 6번째 비트를 설정하는 부분인데

invert_lrclk과 block_power를 설정하는 부분인 것 같다.

Bit/LR Clock 의 Polarity 를 변경하는 부분인데 딱히 건들 이유는 없으니 변경 안해도 될 듯 하다.

SND_SOC_DAIFMT_CBM_CFM

switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
    case SND_SOC_DAIFMT_CBS_CFS:
        adau1977->master = false;
        break;
    case SND_SOC_DAIFMT_CBM_CFM:
        ctrl1 |= ADAU1977_SAI_CTRL1_MASTER;
        adau1977->master = true;
        break;
    default:
        return -EINVAL;
}

이전에 adau1977-adc.c에서 동작 주체가 Master인지 Slave인지 설정하는 부분을 확인했다.
(사용하는 경우에 따라 다르기 때문에 Slave로 작동한다는 가정하에 서술)

CBS_CFS, CBM_CFM 두 가지 사용가능하다.
동작 주체가 Slave인 경우에 파일 포맷 역시 Slave로 맞춰주어야한다.
그래서 Slave로 사용할 경우 SND_SOC_DAIFMT_CBS_CFS 사용.
마찬가지로 데이터시트에 해당 내용이 나와있다.

좋은 웹페이지 즐겨찾기