기존 작업물(RK3399를 사용하는 보드)에 올라가있는 오디오칩셋이 제대로 작동하지 않는 것 같아 디버깅 과정이 필요했고 테스트 보드를 하나 만들어야 했다..
핀맵이 호환되던 보드가 있었고 테스트 하기 좋다고 판단되어 Tinker Board를 사용하게되었다.
대부분의 칩셋은 데이터시트에 이름이 어떻게 나와있던간에 참고용 회로도를 제공함
대략 위와 같이 회로가 구성되어야함.
링크에 접속하여 Download 항목의 Schematics 을 다운로드 하면 됨.
보드에 있는 40핀짜리 헤더를 통해 메인 AP에 접근할 수 있도록 되어있음.
ADAU1977은 I2C로 컨트롤할 수 있는 ADC이다.
제어는 I2C, 데이터는 I2S 참조하면 될듯.
PIN 이름 기억해뒀다가 데이터시트 보면 된다.
I2S 시리얼 클럭(SCLK) LR클럭 (LRCK RX /TX) 시리얼 데이터 (SD I/O) 등… 확인 가능하다.
RK3288은 ADAU1977로부터 데이터를 받아야 하는 입장이다.
i2s_sdi
에 연결해야한다.i2s_sclk
에 연결해야한다.i2s_lrclkrx
에 연결하고그리고 컨트롤을 위한 I2C는 SDA, SCL 연결
디바이스를 추가하는 작업이기 때문에, 디바이스 트리를 수정해줘야한다.
작업 당시에는 Rockchip linux kernel에 adau1977 관련 소스가 없었다.
때문에 Raspberry pi의 커널소스를 가져왔다.
링크의 adau1977-adc.c 를 sound/soc/rockchip에 복사해준다.
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.
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에 복사해준다.
// 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 모드에 따라 전달할 파라미터가 다르다
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 에 정의되어있다.
위 3가지 상수를 사용함
static int adau1977_set_dai_fmt(
struct snd_soc_dai *dai,
unsigned int fmt
)
위 함수에서 어떻게 작동하는지 확인 가능함.
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;
}
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;
}
여기서 눈여겨 볼 부분은
#define ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE BIT(6)
이다.
IB_ 일경우 block_power의 6번째 비트를 설정하는 부분인데
invert_lrclk과 block_power를 설정하는 부분인 것 같다.
Bit/LR Clock 의 Polarity 를 변경하는 부분인데 딱히 건들 이유는 없으니 변경 안해도 될 듯 하다.
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 사용.
마찬가지로 데이터시트에 해당 내용이 나와있다.