2017년 1월 22일 일요일

Atmel SAM-ICE를 이용한 u-boot & linux kernel debugging

이번 post에서는 Atmel SAM-ICE JTAG emulator를 이용하여 SAMA5D3 Xplained board 상에서 u-boot 및 linux kernel을 debugging하는 방법을 소개해 보고자 한다.

SAM-ICE JTAG emulator + SAMA5D3 Xplained board + Eclipse + U-Boot or Linux kernel

 

JTAG을 활용한 debugging이 필요한 경우는, 두말할 것 없이 초기 board를 살리고자 하는 경우(예: u-boot을 수정해서 돌렸는데, console 상으로 아무런 message도 출력되지 않는 경우)나, 전혀 예상치 못한 곳에서 system이 뻗어 버리는 경우(예: chip vendor가 제공한 코드에서 가뭄에 콩나듯 한번씩 죽는 상황) 등을 해결하기 위해서일 것이다(그 밖의 경우는 직접 code를 보는게 답일 수 있다. Debugger가 모든 걸 해결해 주지는 않는다). 
JTAG debugger로는 Trace32ARM DS-5와 같은 값 비싼 tool도 생각해 볼 수 있겠으나, 현실적으로 접하는데 어려움이 있을 수 있으니, (개인적으로 사용하기에도)저렴하면서도 충분한 효과를 볼 수 있는 방법을 고민해 볼 필요가 있어 보인다.

<저렴하고, 효율적인 JTAG debugger>
a) Segger J-Link EDU + Eclipse(gdb)
  => 일반적인 ARM Cortex board에 적용 가능 
b) OpenOCD 기반 JTAG emulator + Eclipse(gdb)
  => open source임.
...


<목차>
1. Atmel SAM-ICE 소개
2. Eclipse Debugging 환경 구축하기
  => ARM cross toolchain, JLink GDB server, Eclipse & plug-ins
3. JTAG을 이용한 AT91Bootstrap Loader Debugging 절차 소개
4. JTAG을 이용한 U-Boot Debugging 절차 소개
5. JTAG을 이용한 Kernel Debugging 절차 소개
6. KGDB를 이용한 Kernel Debugging 방법 소개


1. Atmel SAM-ICE 소개
SAME-ICE(Atmel 장비 전용)는 Segger 사에서 만든 J-Link를 기반으로 만든(OEM) JTAG emulator로, SAM-ICE를 사용하기 위해서는 J-Link website로 부터 관련 s/w를 내려 받아 설치해야만 한다.

그림 1.1 Atmel SAM-ICE JTAG emulator 외관 

그림 1.2 Atmel SAM-ICE pinout 

SAM-ICE의 주요 특징을 열거해 보면 다음과 같다.

아래 그림 1.3은  SAMA5D3 Xplained board의 주요 device를 그림으로 표현한 것으로, 좌측 하단에 20 pin JTAG port가 있음을 알 수 있다.

그림 1.3 SAMA5D3 Xplained board - 좌측 하단  JTAG interface(20 pin)

그림 1.4 SAMA5D3 Xplained board JTAG 회로도(출처 - 참고문헌 [4])

참고) JTAG과 관련한 사항(debugging 관련 기법 포함)은 필자가 오래전에 작성해 둔 아래 문서를 참고하기 바란다.

Atmel SAM-ICE(AT91SAM-ICE)는 아래 site에서 150$에 구매가 가능하다.


2. Eclipse Debugging 환경 구축하기 
이번 절에서는 SAM-ICE(J-Link)와 Eclipse를 이용하여, ARM target board에서 bootloader & kernel을 debugging하기 위해 필요한 환경 설정에 관하여 정리해 보고자 한다. 아래 그림 2.1은 Host PC(GDB, GDB Server)와 JTAG emulator(J-Link) 및 ARM target board로 구성된 전체 디버깅 환경을 하나의 그림으로 표현해 본 것이다. 이 그림에서 GDB Server는 GDB 명령을 JTAG emulator(= J-Link)가 알아볼 수 있는 형태로 전환하여 전달하는 역할을 담당한다.

Eclipse -> GDB -> (tcp/ip) -> GDB Server -> JTAG emulator -> JTAG -> Target ARM CPU

그림 2.1 JTAG을 이용한 Cross Platform Debugging 환경 개요(출처 - 참고문헌[3])

참고) 위의 구성에서 Eclipse가 필요한 이유는 사용자 친화적인 debugging 환경을 제공해 주기 때문이다.

이번 절에서 구체적으로 설명할 내용을 정리해 보면 다음과 같다.
a) cross toolchain(gdb) 설치 방법 소개
b) SAM-ICE 장치 연결(to SAMA5D3 Xplained board) 방법 소개
c) JLink GDB server 설치 방법 소개
d) Eclipse 및 Eclipse GNU ARM plug-in 및 CDT plug-in 설치 방법 소개 

2.1 ARM Cross Toolchain 설치하기
본 blog에서는 buildroot 환경을 토대로 debugging을 시도할 예정이다. 따라서, toolchain은 아래의 것을 활용하기로 한다. 물론 다른 toolchain을 사용해도 무방하다.
buildroot/output/host/usr/bin/arm-linux-*

위의 toolchain 파일 중, 실제로 debugging을 위해 필요한 파일은 arm-linux-gdb인데, buildroot에서는 이 cross gdb가 기본적으로 disable되어 있으므로, 아래와 같이 Toolchain 메뉴에서 먼저 enable 시켜 주어야만 한다.

그림 2.2 Buildroot cross gdb 선택 화면

$ make
$ cd output/host/usr/bin
$ ls -l arm-linux-gdb
  => arm-linux-gdb가 생성었는지를 확인해 본다.

참고) 이 절에서 만든 arm-linux-gdb 파일은 Eclipse에서 사용하게 될 것이다.

2.2 SAMA5D3 Xplained board에 SAM-ICE 연결하기
SAM-ICE 장치를 아래 그림과 같이 SAMA5D3 Xplained board에 연결하도록 하자.

그림 2.3 SAM-ICE를 SAMA5D3 Xplained board에 연결한 모습

그림 2.4 SAMA5D3 Xplained board의 JTAG port(20 pins)

주의) 케이블 연결시에는 🔺 모양이 표시된 부분이 반드시 JTAG port의 1번 pin에 연결될 수 있도록 주의하여야 한다. 연결 후에는 JLink tool(얘: JLinkExe)을 사용하여 제대로 연결되었는지를 확인해 볼 수 있다.

2.3 JLink GDB server 설치하기
이미 1절에서도 언급했다시피, Atmel SAM-ICE JTAG emulator는 Segger사의 JLink를 기반으로 만들어졌다. 따라서 관련 S/W(J-Link Software and Documentation Pack)Segger website에서 download 받아 사용해야 한다.

그림 2.5 J-Link Software and Documentation Pack 다운로드

아래 내용은 64bit Linux Host PC(x86_64)용 S/W를 내려 받아 설치하는 과정을 정리한 것이다(Windows 사용자라면 Windows용 version을 내려 받아 설치하면 된다).

$ tar xvzf ./JLink_Linux_V612f_x86_64.tgz
$ cd JLink_Linux_V612f_x86_64
$ tar cvf 1.tar ./libjlinkarm.so*
$ sudo tar xvf 1.tar -C /usr/lib; rm 1.tar
$ sudo cp ./99-jlink.rules /etc/udev/rules.d/
  => PC(Linux)를 재부팅해 주어야 한다.

PC를 재부팅한 후, SAM-ICE 장비가 제대로 인식되는지를 확인하기 위해서는 아래와 같이 JLinkExe 명령을 실행해 보면 된다.

그림 2.6 JLinkExe 실행 모습

또한, 아래와 같이 앞으로 실제로 사용하게 될 JLink GDB server가 제대로 구동되는지도 확인해 보도록 하자.

$ cd JLink_Linux_V612f_x86_64
./JLinkGDBServer -device ATSAMA5D36

그림 2.7 JLinkGDBServer 실행 모습

참고) JLinkGDBServer 명령 실행 시, device를 지정해 주지 않았더니, JLinkGDBServer가 죽어 버리는 문제가 있었다. JLinkGDBServer에서 사용 가능한 option으로는 아래와 같은 내용을 생각해 볼 수 있겠다. 관련해서는 참고 문헌 [3]을 참조하기 바란다.
./JLinkGDBServer -if JTAG -endian little -device ATSAMA5D36 -select USB -noir -noreset

그림 2.7을 보면 알 수 있듯이, JLinkGDBServer는 GDB와의 통신을 위해 2331 port를 사용하고 있다. 이 값은 나중에 eclipse 설정 시 필요한 정보이니, 잘 기억해 둘 필요가 있겠다.

2.4 Eclipse & Plug-in 설치하기
먼저 Eclipse website로 부터 "Eclipse IDE for C/C++ Developers for Linux"를 내려 받아 설치를 진행하도록 한다. Eclipse 설치와 관련해서는 인터넷에 관련 내용이 많이 알려져 있는 만큼, 여기서는 별도로 설명을 하지는 않겠다(참고로 필자는 Eclipse Neon.2, 4.6.2 version을 설치했음).

 Eclipse가 정상적으로 설치된 경우, 아래와 같이 eclipse 명령을 실행하도록 하자(주의: 아래와 같이 앞 부분에 option을 준 이유는 Eclipse menu가 정상 출력되도록 하기 위해서임).

chyi@earth:~/eclipse/cpp-neon/eclipse$ UBUNTU_MENUPROXY= ./eclipse

다음으로 해야할 일은 GNU ARM Eclipse plug-in을 설치하는 일인데, 이와 관련해서는 아래 site 내용을 참조하기 바란다.

그림 2.8 GNU ARM Plug-in 설치 방법 관련 site 내용

위의 site 내용을 간단히 요약해 보면, Eclipse Menu 중 Help -> Eclipse Marketplace를 선택한 상태에서, 위의 site의 "Install" 버튼을 Marketplace 팝업 위에 가져다 놓으면(drag-and-drop), 설치가 자동으로 진행되게 된다는 것이다. 아래 그림은 Marketplace를 이용하여 설치하는 과정을 보여준다.

그림 2.9 Eclipse Marketplace를 이용해 GNU ARM plug-in을 설치하는 모습

이번에는 CDT(C/C++ Development Tooling) plug-in을 설치할 차례이다. 이를 설치하는 절차는 간단하다. Eclipse Menu -> Help -> Install New Software를 선택한 상태에서 아래 그림과 같이 Work with: 항목을 CDT - http://download.eclipse.org/tools/cdt/releases/9.2으로 선택 한 후, 설치를 진행해 주면 된다.

그림 2.10 Eclipse CDT plug 설치 모습

지금까지 설치한 plug-in을 확인하기 위해서는 Eclipse Menu -> Help -> About Eclipse -> Installation Details 버튼을 눌러 결과를 확인해 보기 바란다.

그림 2.11 Eclipse Plug-in 설치 내용 확인

이상으로 SAM-ICE 및 Eclipse 디버깅 환경을 위한 모든 준비가 마무리 되었다. 이제 부터는 본격적으로 debugging 절차에 들어가 보도록 하자.


3. JTAG을 이용한 AT91Bootstrap Loader Debugging 절차 소개
이번 절에서는 secondary bootloader인 AT91Bootstrap loader를 debugging해 보는 절차를 소개하도록 하겠다.

a) Project 생성하기
File -> New -> Makefile Project with Existing Code 선택 후, 아래 그림 3.1과 같이 적절히 값을 입력하여 project를 하나 생성해 본다.

그림 3.1 AT91Bootstrap loader용 project 생성 모습

b) build하기
Project -> Build Project를 선택하여 build를 진행한다.

그림 3.2 AT91Bootstrap loader project build 모습

c) Debugging 환경 설정하기
Run -> Debug Configurations... 선택 후, 좌측 GDB Hardware Debugging을 선택(mouse 우 click) 후, New하여 새로운 debugging 환경을 생성한다. 이후 아래(그림 3.3 ~ 3.6)와 같이 값을 차례로 입력하도록 한다.

그림 3.3 at91bootstrap_configuration - Main tab 설정

그림 3.4 at91bootstrap_configuration - Debugger tab 설정


그림 3.5 at91bootstrap_configuration - Startup tab 설정


그림 3.6 at91bootstrap_configuration - Common tab 설정

이제 모든 준비가 끝났다. Debugging을 시작하기 전에, 다시 아래 조건을 새롭게 확인한 상태에서 "Debug" 버튼(위 그림 3.6 우측 하단 버튼)을 눌러 debugging을 시작해 보도록 하자.

<debugging 시작 조건>
a) SAMA5D3 Xplained board를 재시작하고, u-boot prompt 상태에서 멈추도록 한다.
b) ./JLinkGDBServer -device ATSAMA5D36를 다시 시작한다.

Debug 버튼을 선택한다. 잠시 후, JLink GDB Server가 아래와 같이 동작(elf file을 loading하고, read 명령 등을 실행함)을 시작해야 한다.

그림 3.7 JLink GDB Server의 동작 모습

약간의 시간이 경과한 후, 아래와 같은 화면이 뜨면 성공이다.

 그림 3.8 AT91Bootstrap loader debugging 화면(초기 모습)

<F6> 키를 눌러 다음 단계로 Step Over해 보도록 하자.

그림 3.9 AT91Bootstrap loader debugging 화면(F6 키 선택 후 모습)

이후 부터는 본인이 원하는 형태로 다양한 debugging을 시도해 볼 수 있을 것이다.


4. JTAG을 이용한 U-Boot Debugging 절차 소개
U-boot을 debugging하는 절차는 AT91bootstrap loader의 그것과 동일하다. 다만, 아래의 차이가 있을 뿐이다.

Existing Code Location: /home/chyi/Atmel/buildroot/output/build/uboot-linux4sam_5.5
C/C++ Application명: /home/chyi/Atmel/buildroot/output/build/uboot-linux4sam_5.5/u-boot

참고) debugging에 사용되는 u-boot 파일의 형식은 ELF(Executable and Linkable Format)인데, ELF는 실행 파일, object 파일, shared library 및 core dump를 위한 표준 파일 형식을 말한다.

U-boot debugging을 시도하기에 앞서, 대략적은 u-boot bootloader의 코드 흐름을 분석해 보고, debugger로 분석한 내용과 비교해 보는 것도 좋을 듯 싶다.

$ grep -rl "sama5" *
  => grep으로 u-boot code 중 SAMA5D3 Xplained board와 관련 내용을 추려 본다.
  => 이 밖에도 코드를 이것 저것 살펴 보면서 SAMA5D3 Xplained board와 연관되는 string을 찾아낸 후, 다시 grep을 시도해 보도록 한다.

코드 4.1 SAMA5D3 Xplained 보드용 u-boot 주요 코드 검색


대략적인 코드 흐름은 다음과 같다(예상된다).

arch/arm/cpu/armv7/start.S    ...... 코드 시작 Point !
|
V


코드 4.2 SAMA5D3 Xplained 용 u-boot code 흐름(main code flow)

아래 내용은 Eclipse로 u-boot을 debugging하는 모습(F6, F5 키를 적절히 선택하면서 다음 단계로 진행)을 보여준다.

<debugging 시작 조건>
a) SAMA5D3 Xplained board를 재시작하고, u-boot prompt 상태에서 멈추도록 한다.
b) ./JLinkGDBServer -device ATSAMA5D36를 다시 시작한다.

이후 Eclipse를 다시 구동하고, Debug 버튼을 선택한다.

그림 4.1 u-boot 디버깅 화면 1

그림 4.2 u-boot 디버깅 화면 2


그림 4.3 u-boot 디버깅 화면 3

 그림 4.4 u-boot 디버깅 화면 4

그림 4.5 u-boot 디버깅 화면 5

<TBD> 현재 breakpoint 명령이 제대로 먹히지 않는 것 같다. 추가 검토가 필요하다.


5. JTAG을 이용한 Kernel Debugging 절차 소개
Linux kernel을 debugging하는 절차 역시 AT91bootstrap loader or u-boot bootloader와 동일하다. 차이점을 정리해 보면 다음과 같다.

Existing Code Location: /home/chyi/Atmel/buildroot/output/build/linux-linux4sam_5.5
C/C++ Application명: /home/chyi/Atmel/buildroot/output/build/linux-linux4sam_5.5/vmlinux

또한 linux kernel을 debugging 하기 위해서는 반드시 아래 kernel feature를 enable해 주어야 한다.

$ make linux-menuconfig

Kernel hacking -->
    Compile-time checks and compiler options --->
        [*] Compile the kernel with debug info

그림 5.1 kernel debug feature를 enable 시키는 모습

Linux kernel의 경우는 size가 매우 크므로, JLinkGDBServer를 통해 vmlinux 파일을 memory로 load하는 시간이 매우 오래 걸린다. 또한 at91bootstrap loader나 u-boot 처럼, debugging 절차가 단순하지도 않다. 따라서 보다 세밀한 접근 방법이 필요해 보인다. 이 부분은 다음 시간에 추가로 검토해 보도록 하겠다.

<debugging 시작 조건>
a) SAMA5D3 Xplained board를 재시작하고, u-boot prompt 상태에서 멈추도록 한다.
b) ./JLinkGDBServer -device ATSAMA5D36를 다시 시작한다.
c) u-boot을 먼저 debugging한다 - 단, kernel 시작 전에 멈추도록 한다(breakpoint 지정)

<TBD> 실제 vmlinux debugging 모습 정리해야 함.


6. KGDB를 이용한 Kernel Debugging 방법 소개
KGDB를 이용한 kernel debugging 방법은 JTAG이 없을 경우 kernel을 효과적으로 debugging할 수 있는 방법이다. 하지만, 아래와 같은 제약 사항을 안고 있는 것도 사실이다. 이런 제약 사항에도 불구하고, booting 중 특정 device driver에서 문제(panic, Oops)가 발생할 경우, 효과적으로 debugging할 수 있는 방법임에는 틀림이 없을 것 같다. 따라서 이번 절에서는 KGDB를 이용한 kernel debugging 기법을 소개하고자 한다.

<제약 조건>
a) bootloader 등 kernel booting 이전 단계에 대해서는 debugging이 불가하다.
b) kernel의 경우도 serial console이 초기화되기 전까지의 상황에 대해서는 역시 debugging이 불가하다.

6.1 KGDB를 위해 kernel 재 build하기
먼저 KGDB를 사용하기 위해서는 몇가지 kernel feature를 enable시켜 주어야 한다.

$ cd buildroot
make linux-menuconfig
Kernel hacking --->
   Compile-time checks and compiler options  --->
       [*] Compile the kernel with debug info

   [*] KGDB: kernel debugger  --->
       <*>   KGDB: use kgdb over the serial console

그림 6.1 KGDB enable 모습

make
  => output/build/linux-linux4sam_5.5/vmlinux 파일이 생성될 것임.

6.2 KGBD 동작 확인하기 
SAMA5D3 Xplained board의 u-boot prompt 상에서 아래 명령을 실행해 준다(NFS booting을 하기 위함임). NFS booting과 관련해서는 이전 blog(2절)를 참조하기 바란다.

<SAMA5D3 Xplained target board - u-boot 명령>
=> setenv nb1 'setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath} ip=${ipaddr}:${serverip}::${netmask}::eth0:off console=ttyS0,115200 kgdboc=ttyS0,115200 kgdbwait'

=> setenv nb2 'tftp 0x22000000 zImage; tftp 0x21000000 at91-sama5d3_xplained_pda4.dtb; bootz 0x22000000 - 0x21000000'

=> run nb1; run nb2

그림 6.2 kgdbwait에 의해 부팅 중 대기 상태에서 빠진 모습

위의 그림에서도 알 수 있듯이 "kgdboc=ttyS0,115200 kgdbwait" command line 명령에 의해, kernel은 아래 message를 마지막으로 출력한 채 대기 상태에 빠지게 된다.

...
console [ttyS0] enabled
KGDB: Registered I/O driver kgdboc
KGDB: Waiting for connection from remote gdb...
  <= 이 상태에서 대기하게 됨.
=============================================

이 상태에서 Host PC에서 gdb(arm cross gdb)를 이용하여 vmlinux(ELF file)에 대한 debugging을 시도해 보면 다음과 같다.

<Host PC - gdb 명령>
export PATH=/home/chyi/Atmel/buildroot/output/host/usr/bin:$PATH
$ cd build/linux-linux4sam_5.5
arm-linux-gdb ./vmlinux
GNU gdb (GDB) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-buildroot-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./vmlinux...done.
(gdb) target remote /dev/ttyUSB0    <= kgdb와 /dev/ttyUSB0 포트를 통해 연결
Remote debugging using /dev/ttyUSB0
0xc0069a2c in arch_kgdb_breakpoint () at kernel/debug/debug_core.c:1070
1070 wmb(); /* Sync point before breakpoint */
(gdb) list
1065  * the debugger.
1066  */
1067 noinline void kgdb_breakpoint(void)
1068 {
1069 atomic_inc(&kgdb_setting_breakpoint);
1070 wmb(); /* Sync point before breakpoint */
1071 arch_kgdb_breakpoint();
1072 wmb(); /* Sync point after breakpoint */
1073 atomic_dec(&kgdb_setting_breakpoint);
1074 }
(gdb)

<TBD> gdb 대신 eclipse로 debugging하는 내용 정리해야 함.


이상으로 JTAG을 활용한 debugging 기법에 관하여 간략히 정리해 보았다. 내용 중 미비한 부분은 추가로 정리 & 검토해 볼 것을 약속하며 이번 post를 마무리 하고자 한다.


References
1. http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91BootstrapDebugEclipse
2. AT91SAM-ICE_User_Guide.pdf, Atmel.
3. https://www.segger.com/admin/uploads/productDocs/UM08001_JLink.pdf
4. Atmel-11269-32-bit-Cortex-A5-Microcontroller-SAMA5D3-Xplained_User-Guide.pdf, Atmel.
5. Mastering Embedded Linux Programming, Chris Simmonds, PACKT publishng.

<Internet>
6. https://www.element14.com/community/community/designcenter/single-board-computers/riotboard/blog/2014/07/24/debugging-u-boot-on-riotboard-using-eclipse-and-jlink-under-linux
7. http://electro-don.tistory.com/entry/Eclipse-Yagarto-Segger-JLink-%EB%A1%9C-ARM-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-%EC%A0%95%EB%A6%AC
8. http://www.sw-at.com/blog/2011/02/11/linux-kernel-development-and-debugging-using-eclipse-cdt/
9. http://stackoverflow.com/questions/13085211/linux-debugging-with-jtag-arm9at91sam9g25-amontec-openocd-gdb-eclipse
10. https://boundarydevices.com/debugging-using-segger-j-link-jtag/


Slowboot


2017년 1월 11일 수요일

RIoT Board Device Tree Analysis

Device Tree는 최신 ARM board를 bring-up하는 단계에서 반드시 이해해야 하는 중요한 개념임에도 불구하고 관련 자료가 매우 부족한 편이다. 따라서 이번 blog에서는 RIoT board(freescale)의 device tree를 상세히 분석해 봄으로써, device tree에 대한 이해를 돕고자 한다. Device Tree 관련해서는 이미 몇 차례에 걸쳐 소개한 바 있는데, 관련해서는 아래 내용을 참조해 주기 바란다.

참고) Device Tree는 원래 SPARC, PowerPC 등에서 사용되던 개념으로, 이제는 x86(예: CE4100)은 물론 MIPS에서도 사용하도록 영역을 넓혀 가고 있는 추세이다.

<기초편 - 배경 설명>
a) https://github.com/ChunghanYi/linux_kernel_hacks/blob/master/android_kernel_hacks/AndroidKernelHacks_Chapter4.pdf

<기초편 & BBB device tree 분석>
b) http://slowbootkernelhacks.blogspot.kr/2014/03/beaglebone-linux-kernel310x-programming.html

<Atmel SAMA5D3 Xplained board device tree 분석>
c) http://slowbootkernelhacks.blogspot.kr/2016/11/atmel-sama5d3-xplained-board-i2c-device.html

<목차>
1. Freescale i.MX6Q Machine 초기화 코드 분석
2. Freescale i.MX6Q Device Tree 개요
3. RIoT Board Device Tree 상세 분석
4. RIoT Board LED Device Driver 분석
5. RIoT Board GPIO Controller Device Driver 분석


1. Freescale i.MX6Q Machine 초기화 코드 분석 
먼저 Yocto project(이전 blog 참조) 내에서 kernel source의 위치를 찾아 보자.

$ bitbake -e virtual/kernel | grep ^S=
S="/home/chyi/IoT/RIoTBoard/yocto/fsl-community-bsp/imx6dl-riotboard/tmp/work/imx6dl_riotboard-poky-linux-gnueabi/linux-fslc/4.4+gitAUTOINC+928f8d55df-r0/git"

$ cd tmp/work/imx6dl_riotboard-poky-linux-gnueabi/linux-fslc/4.4+gitAUTOINC+928f8d55df-r0/git

or

bitbake -c devshell virtual/kernel
  => 새로운 terminal 창이 뜨면서 자동으로 linux kernel source 디렉토리로 이동하게 된다.
  => 여기서 source code를 수정한다.

# bitbake -C compile virtual/kernel
  => compile & 이후 작업(install)을 진행한다(대문자 C에 주의).

Yocto 내에서의 liunx kernel source의 위치를 찾았으니, 이제는 RIoT board를 어디에서 초기화(machine 초기화)하는지를 살펴  보도록 하자.

$ cd arch/arm/mach-imx
$ ls -l mach-imx6*.c
-rw-r--r-- 1 chyi chyi 10168  1월  2 11:12 mach-imx6q.c
-rw-r--r-- 1 chyi chyi  2001  1월  2 11:12 mach-imx6sl.c
-rw-r--r-- 1 chyi chyi  2511  1월  2 11:12 mach-imx6sx.c
-rw-r--r-- 1 chyi chyi  2362  1월  2 11:12 mach-imx6ul.c

이 중 어느 file을 사용할까 ?

그림 1.1 target board에서 cat /proc/cpuinfo 명령 실행 결과

위의 내용(그림 1.1)으로 봐서는 mach-imx6q.c이 맞는 것 같다. 아래 device tree의 compatible string을 가지고 검색해 본 결과(코드 1.1)도 일치한다.

$ grep -rl "fsl,imx6dl" *
mach-imx6q.c
pm-imx6.c


코드 1.1 arch/arm/boot/dts/imx6dl-riotboard.dts 파일 - root note 중 일부 발췌

그럼 이제 부터는 mach-imx6q.c의 주요 코드를 살펴 보도록 하자. 우선 가장 먼저 눈에 들어 오는 부분은 RIoT board를 위한 compatible string인 "fsl,imx6dl" 부분(dt_compat)일 것이다.

코드 1.2 mach-imx6q.c 코드 중, machine start 설정 정보(struct __mach_desc_IMX6Q)

코드 1.2의 내용을 토대로 해 볼 때, imx6q_init_machine 함수가 가장 먼저 호출되는 함수임을 알 수 있다.

코드 1.3 init_machine callback 함수

코드 1.3의 imx6q_init_machine() 함수에서 중요한 부분은 아래 코드 부분이다. 아래 함수에서 하는 일을 한 마디로 요약하자면, device tree를 구성하는 모든 node에 대해 device로 등록(linux device 모델)하는 것으로 이해하면 된다.

parent = imx_soc_device_init();
    /* soc device(root node)를 device로 등록한다 */

of_platform_populate(NULL, of_default_bus_match_table, NULL, parent);
    /* device tree를 따라가면서 모든 node를 찾아 device로 등록해 준다. platform_bus_probe() 함수의 역할과 비슷하다고 볼 수 있음. */


코드 1.4 struct device_node 내용 - include/linux/of.h

linux의 device model(struct device, struct device_driver)과 관련해서는 (좀 오래전에 정리한 내용이기는 하지만) 아래 link의 4장 "통합 Device 모델과 Sysfs" 편을 참조해 보기 바란다.


위의 내용(device model)에는 device node(struct device_node)에 관한 설명은 빠져 있으나, 결국 이 device model (struct device)과 device node가 상호 묶여 있음을 알 수 있다.


2. Freescale i.MX6Q Device Tree 개요
이번 절에서는 i.MX6 device tree의 대략적인 모습을 살펴 보기로 하겠다. 더불어 device tree의 일반적인 개념 중, 꼭 필요하다고 판단되는 부분이 있다면 부연 설명하도록 하겠다.

먼저 그림 2.1과 2.2를 보면, i.MX6Q & RIoT board를 위한 device tree의 계층 구조를 한눈에 파악할 수 있다.

그림 2.1 i.MX6 device tree 개요도[출처 - 참고문헌 2]


imx6qdl.dtsi
|
V
imx6dl.dtsi
|
V
imx6dl-riotboard.dts
그림 2.2 RIoT board의 device tree 계층도

다음으로 device tree를 구체적으로 분석하기에 앞서, i.MX 6 Solo/6DualLite의 system block 도(그림 2.3)와 memory map(그림 2.4)을 간략히 정리해 보았다(memory map의 경우는 내용이 길어져 일부분만을 옮겨 놓았음). 이중 system block도는 각종 device와 이들 간을 연결하는 bus 등을 확인하기 위해, memory map은 device tree 상에 표현된 각종 주솟값의 의미를 확인하기 위해 필요하다. 그 밖에도 SoC 내의 bus 구조를 그림 2.5에, PMU & clock framework을 그림 2.6에, IOMUX(= pin mux) 관련 내용을 그림 2.7 및 2.8에 정리해 보았다. 이밖에도 device tree를 제대로 이해하기 위해서는 관련 data sheet(참고 문헌 5)를 면밀히 분석해 볼 필요가 있겠다.


그림 2.3 i.MX 6 Solo/6DualLite Processor Block 도[출처 - 참고문헌 5]


그림 2.4 i.MX 6 Solo/6DualLite Processor memory map 내용 중 일부 발췌[출처 - 참고문헌 5]

그림 2.5 i.MX 6 Solo/6DualLite bus 구조[출처 - 참고문헌 5]

그림 2.6 Power and clock management framework[출처 - 참고문헌 5]

그림 2.7 IOMUX 구조[출처 - 참고문헌 5]



그림 2.8 Pin mux(IOMUX) table 예[출처 - 참고문헌 5]

그럼, 그림 2.2의 순서대로 각각의 dts(i) file의 내용을 하나씩 살펴 보도록 하자. 실제 파일 내용을 살펴보면 알겠지만, device tree의 내용이 꽤나 방대하다. 따라서 모든 내용을 일일이 설명하는 것은 현실적으로 무리가 있다. 따라서 여기서는 아래 내용을 집중적으로 살펴보도록 하겠다.

a) 전체 구조 관련(soc, memory, bus)
b) 몇몇 주요 device controller 관련
c) pin control 설정 관련
d) gpio & led 관련
e) interrupt 관련 !

<arch/arm/boot/dts/imx6qdl.dtsi 파일>
  => 실제 파일 내용이 너무 길어 많은 부분을 생략하여 간결하게 표시하였다.
  => 아래 dtsi 파일에서는 soc의 전체 구조를 개략적으로 파악해 볼 수 있을 듯하다.
==============================================================
#include <dt-bindings/clock/imx6qdl-clock.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>

#include "skeleton.dtsi"

/ {             /* 이 dtsi file은 주로 soc 내의 bus 및 각종 주변 장치 controller를 기술하고 있음 */
aliases {   /* 알아보기 쉽도록 별명을 달아 준다. 이 dtsi 파일이나, child dts file에서 이 별명을 사용하게 됨. */
ethernet0 = &fec;
can0 = &can1;
can1 = &can2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
gpio5 = &gpio6;
gpio6 = &gpio7;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
mmc2 = &usdhc3;
mmc3 = &usdhc4;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
serial3 = &uart4;
serial4 = &uart5;
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
usbphy0 = &usbphy1;
usbphy1 = &usbphy2;
};

intc: interrupt-controller@00a01000 {   /* interrupt controller 정의 */
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;   /* interrupts property에 있는 cell의 갯수가 3개임을 의미 */
interrupt-controller;  /* interrupt controller임을 선언 */
reg = <0x00a01000 0x1000>,
     <0x00a00100 0x100>;
interrupt-parent = <&intc>;
};

clocks {  /* clock을 정의함 */
#address-cells = <1>;
#size-cells = <0>;

ckil { };
ckih1 { };
osc { };
};

soc { /* soc 내의 각종 IP와 bus, device controller 등의 관계를 기술해 주고 있음 */
#address-cells = <1>;      /* reg property의 base 주소를 구성하기 위해 1개의 cell(32 bit)이 필요함을 의미. 즉, 32bit machine임을 의미 */
#size-cells = <1>;
compatible = "simple-bus";   /* simple memory mapped bus = child node가 platform device로 등록됨을 의미 */
interrupt-parent = <&gpc>;
ranges;   /* parent와 child bus 간에 주소 변환이 필요 없음을 뜻함 */

dma_apbh: dma-apbh@00110000 { };

gpmi: gpmi-nand@00112000 { };

hdmi: hdmi@0120000 { };

timer@00a00600 { };

L2: l2-cache@00a02000 { };

pcie: pcie@0x01000000 { };

pmu { };

  /* 2개의 64bit AXI bus가 있음 */
aips-bus@02000000 { /* AIPS1 bus에 연결된 device controller를 기술하고 있음 */
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;

spba-bus@02000000 {  /* spba(shared peripheral bus arbiter)에 연결된 device controller를 기술해주고 있음 ==> */
spdif: spdif@02004000 { };

ecspi1: ecspi@02008000 { };

ecspi2: ecspi@0200c000 { };

ecspi3: ecspi@02010000 { };

ecspi4: ecspi@02014000 { };

uart1: serial@02020000 { };

esai: esai@02024000 { };

ssi1: ssi@02028000 { };

ssi2: ssi@0202c000 { };

ssi3: ssi@02030000 { };

asrc: asrc@02034000 { };

spba@0203c000 { };
};  /* <== */

vpu: vpu@02040000 { };

aipstz@0207c000 { };

pwm1: pwm@02080000 { };

pwm2: pwm@02084000 { };

pwm3: pwm@02088000 { };

pwm4: pwm@0208c000 { };

can1: flexcan@02090000 { };

can2: flexcan@02094000 { };

gpt: gpt@02098000 { };

gpio1: gpio@0209c000 { };

gpio2: gpio@020a0000 { };

gpio3: gpio@020a4000 { };

gpio4: gpio@020a8000 { };

gpio5: gpio@020ac000 { };

gpio6: gpio@020b0000 { };

gpio7: gpio@020b4000 { };

kpp: kpp@020b8000 { };

wdog1: wdog@020bc000 { };

wdog2: wdog@020c0000 { };

clks: ccm@020c4000 { };

anatop: anatop@020c8000 {
regulator-1p1@110 { };

regulator-3p0@120 { };

regulator-2p5@130 { };

reg_arm: regulator-vddcore@140 { };

reg_pu: regulator-vddpu@140 { };

reg_soc: regulator-vddsoc@140 { };
};

tempmon: tempmon { };

usbphy1: usbphy@020c9000 { };

usbphy2: usbphy@020ca000 { };

snvs: snvs@020cc000 { };

epit1: epit@020d0000 { }; /* EPIT1 */

epit2: epit@020d4000 { }; /* EPIT2 */

src: src@020d8000 { };

gpc: gpc@020dc000 { };

gpr: iomuxc-gpr@020e0000 { };

iomuxc: iomuxc@020e0000 { };

ldb: ldb@020e0008 { };

dcic1: dcic@020e4000 { };

dcic2: dcic@020e8000 { };

sdma: sdma@020ec000 { };
};  /* 여기까지 aips1 bus에 연결된 device controller를 기술한 것임 */


aips-bus@02100000 { /* AIPS2 bus에 연결된 device controller를 기술해 주고 있음 */
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000 0x100000>;
ranges;

crypto: caam@2100000 { };

aipstz@0217c000 { }; /* AIPSTZ2 */

usbotg: usb@02184000 { };

usbh1: usb@02184200 { };

usbh2: usb@02184400 { };

usbh3: usb@02184600 { };

usbmisc: usbmisc@02184800 { };

fec: ethernet@02188000 { };

mlb@0218c000 { };

usdhc1: usdhc@02190000 { };

usdhc2: usdhc@02194000 { };

usdhc3: usdhc@02198000 { };

usdhc4: usdhc@0219c000 { };

i2c1: i2c@021a0000 { };

i2c2: i2c@021a4000 { };

i2c3: i2c@021a8000 { };

romcp@021ac000 { };

mmdc0: mmdc@021b0000 { }; /* MMDC0 */

mmdc1: mmdc@021b4000 { }; /* MMDC1 */

weim: weim@021b8000 { };

ocotp: ocotp@021bc000 { };

tzasc@021d0000 { }; /* TZASC1 */

tzasc@021d4000 { }; /* TZASC2 */

audmux: audmux@021d8000 { };

mipi_csi: mipi@021dc000 { };

mipi_dsi: mipi@021e0000 { };

vdoa@021e4000 { };

uart2: serial@021e8000 { };

uart3: serial@021ec000 { };

uart4: serial@021f0000 { };

uart5: serial@021f4000 { };
};  /* 여기까지 aips2 bus에 연결된 device controller를 기술한 것임 */

ipu1: ipu@02400000 { };
};  /* soc의 끝 임 */
}; /* dtsi file의 끝 임 */
==============================================================

다음으로 imx6dl.dtsi 파일을 분석해 보도록 하자.

<arch/arm/boot/dts/imx6dl.dtsi 파일>
  => 여기서는 앞선 파일에서 누락되어 있던, cpu 관련 내용이 등장한다.
==============================================================
#include <dt-bindings/interrupt-controller/irq.h>
#include "imx6dl-pinfunc.h"
#include "imx6qdl.dtsi"

/ {
aliases {
i2c3 = &i2c4;
};

cpus {  /* 2개의 cpu core가 있음 - 참고로, RIoT board는 이중 cpu0만 사용함 */
#address-cells = <1>;  /* 32bit cpu임을 표시함 */
#size-cells = <0>;

cpu@0 {
compatible = "arm,cortex-a9";  /* ARM cortex-a9 */
device_type = "cpu";
reg = <0>;
next-level-cache = <&L2>;
operating-points = <
/* kHz    uV */
996000  1250000
792000  1175000
396000  1075000
>;
fsl,soc-operating-points = <
/* ARM kHz  SOC-PU uV */
996000 1175000
792000 1175000
396000 1175000
>;
clock-latency = <61036>; /* two CLK32 periods */
clocks = <&clks IMX6QDL_CLK_ARM>,
<&clks IMX6QDL_CLK_PLL2_PFD2_396M>,
<&clks IMX6QDL_CLK_STEP>,
<&clks IMX6QDL_CLK_PLL1_SW>,
<&clks IMX6QDL_CLK_PLL1_SYS>;
clock-names = "arm", "pll2_pfd2_396m", "step",
     "pll1_sw", "pll1_sys";
arm-supply = <&reg_arm>;
pu-supply = <&reg_pu>;
soc-supply = <&reg_soc>;
};

cpu@1 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <1>;
next-level-cache = <&L2>;
};
};

soc {  /* soc 내의 각종 IP와 bus, device controller 등의 관계를 기술해 주고 있음.  imx6qdl.dtsi 내에 이미 자세히 기술되어 있으며, 여기서는 빠진 내용을 보강해주고 있음. */
ocram: sram@00900000 {  /* SRAM을 기술해 주고 있음 */
compatible = "mmio-sram";
reg = <0x00900000 0x20000>;
clocks = <&clks IMX6QDL_CLK_OCRAM>;
};

aips1: aips-bus@02000000 {  /* aips1(AXI IP slave 1) bus에 연결된 slave device controller를 기술해 주고 있음 - imx6qdl.dtsi 의 내용을 확장 */
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6dl-iomuxc";
};

pxp: pxp@020f0000 {
reg = <0x020f0000 0x4000>;
interrupts = <0 98 IRQ_TYPE_LEVEL_HIGH>;
};

epdc: epdc@020f4000 {
reg = <0x020f4000 0x4000>;
interrupts = <0 97 IRQ_TYPE_LEVEL_HIGH>;
};

lcdif: lcdif@020f8000 {
reg = <0x020f8000 0x4000>;
interrupts = <0 39 IRQ_TYPE_LEVEL_HIGH>;
};
};

aips2: aips-bus@02100000 { /* aips1(AXI IP slave 2) bus에 연결된 slave device controller를 기술해 주고 있음 - imx6qdl.dtsi 의 내용을 확장 */
i2c4: i2c@021f8000 {  /* i2c4를 추가로 정의해 주고 있음(imx6qdl.dtsi에는 없음) */
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021f8000 0x4000>;
interrupts = <0 35 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6DL_CLK_I2C4>;
status = "disabled";
};
};
};  /* soc의 끝 */

display-subsystem {
compatible = "fsl,imx-display-subsystem";
ports = <&ipu1_di0>, <&ipu1_di1>;
};
}; /* root node의 끝 */

/* 아래 내용은 imx6qdl.dtsi에 기 정의된 내용을 overriding하는 설정임 */
&gpt {
compatible = "fsl,imx6dl-gpt", "fsl,imx6q-gpt";
};

&hdmi {
compatible = "fsl,imx6dl-hdmi";
};

&ldb {
clocks = <&clks IMX6QDL_CLK_LDB_DI0_SEL>, <&clks IMX6QDL_CLK_LDB_DI1_SEL>,
<&clks IMX6QDL_CLK_IPU1_DI0_SEL>, <&clks IMX6QDL_CLK_IPU1_DI1_SEL>,
<&clks IMX6QDL_CLK_LDB_DI0>, <&clks IMX6QDL_CLK_LDB_DI1>;
clock-names = "di0_pll", "di1_pll",
     "di0_sel", "di1_sel",
     "di0", "di1";
};

&vpu {
compatible = "fsl,imx6dl-vpu", "cnm,coda960";
};

==============================================================

3. RIoT Board Device Tree 상세 분석
이번 절에서는 2절의 내용에 이어, 마지막으로 RIoT board의 dts file을 분석해 보도록 하겠다. 내용을 보면 알겠지만, SoC 관련 대부분의 내용이 이미 imx6qdl.dtsi 및 imx6dl.dtsi에 정의되어 있기 때문에, imx6dl-riotboard.dts 파일에서는 RIoT board에서 특별히 추가된 사항만을 기술해 주고 있음을 알 수 있다.

그림 3.1 RIoT board의 block diagram

자, 그럼 지금부터는 RIoT board의 DTS file을 분석해 보도록 하자.

<arch/arm/boot/dts/imx6dl-riotboard.dts 파일>
  => pin control 관련 내용이 많이 존재함을 알 수 있다.
==============================================================
/dts-v1/;
#include "imx6dl.dtsi"
#include <dt-bindings/gpio/gpio.h>

/ {
model = "RIoTboard i.MX6S";  /* arch/arm/mach-imx/cpu.c의 imx_soc_device_init() 함수에서 사용됨 */
compatible = "riot,imx6s-riotboard", "fsl,imx6dl";

memory {  /* DRAM */
reg = <0x10000000 0x40000000>;   /* 시작 주소 = 0x10000000, size 1GB */
};

regulators {  /* 레귤레이터를 기술해 주고 있음 */
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <0>;

reg_2p5v: regulator@0 {
compatible = "regulator-fixed";
reg = <0>;
regulator-name = "2P5V";
regulator-min-microvolt = <2500000>;
regulator-max-microvolt = <2500000>;
};

reg_3p3v: regulator@1 {
compatible = "regulator-fixed";
reg = <1>;
regulator-name = "3P3V";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};

reg_usb_otg_vbus: regulator@2 {
compatible = "regulator-fixed";
reg = <2>;
regulator-name = "usb_otg_vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpio = <&gpio3 22 0>;
enable-active-high;
};
};

leds {  /* LED를 기술하고 있음 - 그 중 2개의 user led를 기술 */
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;

led0: user1 {
label = "user1";
gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;  /* GPIO130, active low */
default-state = "on";
linux,default-trigger = "heartbeat";  /* led 출력 방식 : heartbeat */
};

led1: user2 {
label = "user2";
gpios = <&gpio3 28 GPIO_ACTIVE_LOW>;  /* GPIO 92, active low */
default-state = "off";
                                     /* led 출력(trigger) 방식: none */
};
};

sound {
compatible = "fsl,imx-audio-sgtl5000";
model = "imx6-riotboard-sgtl5000";
ssi-controller = <&ssi1>;
audio-codec = <&codec>;
audio-routing =
"MIC_IN", "Mic Jack",
"Mic Jack", "Mic Bias",
"Headphone Jack", "HP_OUT";
mux-int-port = <1>;
mux-ext-port = <3>;
};
};

&audmux {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_audmux>;
status = "okay";
};

&fec {  /* 1G ethernet(PHY)을 정의하고 있음 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet>;
phy-mode = "rgmii";
phy-reset-gpios = <&gpio3 31 0>;
interrupts-extended = <&gpio1 6 IRQ_TYPE_LEVEL_HIGH>,
     <&intc 0 119 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};

&hdmi {
ddc-i2c-bus = <&i2c2>;
status = "okay";
};

&i2c1 {  /* i2c1 controller에는 code, pmic slave 장치가 2개 붙어 있음 */
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";

codec: sgtl5000@0a {
compatible = "fsl,sgtl5000";
reg = <0x0a>;   /* i2c slave address */
clocks = <&clks 201>;
VDDA-supply = <&reg_2p5v>;
VDDIO-supply = <&reg_3p3v>;
};

pmic: pf0100@08 {  /* PMIC PF0100에 관한 내용을 기술 */
compatible = "fsl,pfuze100";
reg = <0x08>;  /* i2c slave address */
interrupt-parent = <&gpio5>;
interrupts = <16 8>;

regulators {  /* PMIC PF0100 내에는 regulator가 15개 가량 붙어 있음 */
reg_vddcore: sw1ab { /* VDDARM_IN */
regulator-min-microvolt = <300000>;
regulator-max-microvolt = <1875000>;
regulator-always-on;
};

reg_vddsoc: sw1c { /* VDDSOC_IN */
regulator-min-microvolt = <300000>;
regulator-max-microvolt = <1875000>;
regulator-always-on;
};

reg_gen_3v3: sw2 { /* VDDHIGH_IN */
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};

reg_ddr_1v5a: sw3a { /* NVCC_DRAM, NVCC_RGMII */
regulator-min-microvolt = <400000>;
regulator-max-microvolt = <1975000>;
regulator-always-on;
};

reg_ddr_1v5b: sw3b { /* NVCC_DRAM, NVCC_RGMII */
regulator-min-microvolt = <400000>;
regulator-max-microvolt = <1975000>;
regulator-always-on;
};

reg_ddr_vtt: sw4 { /* MIPI conn */
regulator-min-microvolt = <400000>;
regulator-max-microvolt = <1975000>;
regulator-always-on;
};

reg_5v_600mA: swbst { /* not used */
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5150000>;
};

reg_snvs_3v: vsnvs { /* VDD_SNVS_IN */
regulator-min-microvolt = <1500000>;
regulator-max-microvolt = <3000000>;
regulator-always-on;
};

vref_reg: vrefddr { /* VREF_DDR */
regulator-boot-on;
regulator-always-on;
};

reg_vgen1_1v5: vgen1 { /* not used */
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1550000>;
};

reg_vgen2_1v2_eth: vgen2 { /* pcie ? */
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1550000>;
regulator-always-on;
};

reg_vgen3_2v8: vgen3 { /* not used */
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
};
reg_vgen4_1v8: vgen4 { /* NVCC_SD3 */
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};

reg_vgen5_2v5_sgtl: vgen5 { /* Pwr LED & 5V0_delayed enable */
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};

reg_vgen6_3v3: vgen6 { /* #V#_DELAYED enable, MIPI */
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
};
};
}; /* i2c1의 끝 */

&i2c2 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
};

/* 이상하게도 i2c3이 없군 ... */
&i2c4 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c4>;
clocks = <&clks 116>;
status = "okay";
};

&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};

&pwm2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm2>;
status = "okay";
};

&pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
status = "okay";
};

&pwm4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm4>;
status = "okay";
};

&ssi1 {
status = "okay";
};

/* uart는 총 5개 */
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};

&uart2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2>;
status = "okay";
};

&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};

&uart4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart4>;
status = "okay";
};

&uart5 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart5>;
status = "okay";
};

&usbh1 {
dr_mode = "host";
disable-over-current;
status = "okay";
};

&usbotg {
vbus-supply = <&reg_usb_otg_vbus>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usbotg>;
disable-over-current;
dr_mode = "otg";
status = "okay";
};

&usdhc2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc2>;
cd-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
wp-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
vmmc-supply = <&reg_3p3v>;
status = "okay";
};

&usdhc3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc3>;
cd-gpios = <&gpio7 0 GPIO_ACTIVE_LOW>;
wp-gpios = <&gpio7 1 GPIO_ACTIVE_HIGH>;
vmmc-supply = <&reg_3p3v>;
status = "okay";
};

&usdhc4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc4>;
vmmc-supply = <&reg_3p3v>;
non-removable;
status = "okay";
};

&iomuxc {  /* IOMUX 즉, pin mux or pin control 관련 내용의 정의하고 있음 */
pinctrl-names = "default";

imx6-riotboard {
  /* audio mux 관련 */
pinctrl_audmux: audmuxgrp {
fsl,pins = <
MX6QDL_PAD_CSI0_DAT7__AUD3_RXD 0x130b0
MX6QDL_PAD_CSI0_DAT4__AUD3_TXC 0x130b0
MX6QDL_PAD_CSI0_DAT5__AUD3_TXD 0x110b0
MX6QDL_PAD_CSI0_DAT6__AUD3_TXFS 0x130b0
MX6QDL_PAD_GPIO_0__CCM_CLKO1 0x130b0 /* CAM_MCLK */
>;
};

  /* SPI 관련 - 3개 */
pinctrl_ecspi1: ecspi1grp {
fsl,pins = <
MX6QDL_PAD_EIM_D16__ECSPI1_SCLK 0x100b1
MX6QDL_PAD_EIM_D17__ECSPI1_MISO 0x100b1
MX6QDL_PAD_EIM_D18__ECSPI1_MOSI 0x100b1
MX6QDL_PAD_DISP0_DAT23__GPIO5_IO17 0x000b1 /* CS0 */
>;
};

pinctrl_ecspi2: ecspi2grp {
fsl,pins = <
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x000b1 /* CS1 */
MX6QDL_PAD_DISP0_DAT16__ECSPI2_MOSI 0x100b1
MX6QDL_PAD_DISP0_DAT17__ECSPI2_MISO 0x100b1
MX6QDL_PAD_DISP0_DAT18__GPIO5_IO12 0x000b1 /* CS0 */
MX6QDL_PAD_DISP0_DAT19__ECSPI2_SCLK 0x100b1
>;
};

pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6QDL_PAD_DISP0_DAT0__ECSPI3_SCLK 0x100b1
MX6QDL_PAD_DISP0_DAT1__ECSPI3_MOSI 0x100b1
MX6QDL_PAD_DISP0_DAT2__ECSPI3_MISO 0x100b1
MX6QDL_PAD_DISP0_DAT3__GPIO4_IO24 0x000b1 /* CS0 */
MX6QDL_PAD_DISP0_DAT4__GPIO4_IO25 0x000b1 /* CS1 */
>;
};

  /* Ethernet Giga PHY 관련 */
pinctrl_enet: enetgrp {
fsl,pins = <
MX6QDL_PAD_ENET_MDIO__ENET_MDIO 0x1b0b0
MX6QDL_PAD_ENET_MDC__ENET_MDC 0x1b0b0
MX6QDL_PAD_RGMII_TXC__RGMII_TXC 0x1b0b0
MX6QDL_PAD_RGMII_TD0__RGMII_TD0 0x1b0b0
MX6QDL_PAD_RGMII_TD1__RGMII_TD1 0x1b0b0
MX6QDL_PAD_RGMII_TD2__RGMII_TD2 0x1b0b0
MX6QDL_PAD_RGMII_TD3__RGMII_TD3 0x1b0b0
MX6QDL_PAD_RGMII_TX_CTL__RGMII_TX_CTL 0x1b0b0
MX6QDL_PAD_ENET_REF_CLK__ENET_TX_CLK 0x0a0b1 /* AR8035 CLK_25M --> ENET_REF_CLK (V22) */
MX6QDL_PAD_RGMII_RXC__RGMII_RXC 0x1b0b0 /* AR8035 pin strapping: IO voltage: pull up */
MX6QDL_PAD_RGMII_RD0__RGMII_RD0 0x130b0 /* AR8035 pin strapping: PHYADDR#0: pull down */
MX6QDL_PAD_RGMII_RD1__RGMII_RD1 0x130b0 /* AR8035 pin strapping: PHYADDR#1: pull down */
MX6QDL_PAD_RGMII_RD2__RGMII_RD2 0x1b0b0 /* AR8035 pin strapping: MODE#1: pull up */
MX6QDL_PAD_RGMII_RD3__RGMII_RD3 0x1b0b0 /* AR8035 pin strapping: MODE#3: pull up */
MX6QDL_PAD_RGMII_RX_CTL__RGMII_RX_CTL 0x130b0 /* AR8035 pin strapping: MODE#0: pull down */
MX6QDL_PAD_GPIO_16__ENET_REF_CLK 0x4001b0a8 /* GPIO16 -> AR8035 25MHz */
       MX6QDL_PAD_EIM_D31__GPIO3_IO31 0x130b0 /* RGMII_nRST */
MX6QDL_PAD_ENET_TX_EN__GPIO1_IO28 0x180b0 /* AR8035 interrupt */
MX6QDL_PAD_GPIO_6__ENET_IRQ 0x000b1
>;
};

  /* i2c 관련 - 4개 */
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6QDL_PAD_CSI0_DAT8__I2C1_SDA 0x4001b8b1
MX6QDL_PAD_CSI0_DAT9__I2C1_SCL 0x4001b8b1
>;
};

pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6QDL_PAD_KEY_COL3__I2C2_SCL 0x4001b8b1
MX6QDL_PAD_KEY_ROW3__I2C2_SDA 0x4001b8b1
>;
};

pinctrl_i2c3: i2c3grp {
fsl,pins = <
MX6QDL_PAD_GPIO_5__I2C3_SCL 0x4001b8b1
MX6QDL_PAD_GPIO_6__I2C3_SDA 0x4001b8b1
>;
};

pinctrl_i2c4: i2c4grp {
fsl,pins = <
MX6QDL_PAD_GPIO_7__I2C4_SCL             0x4001b8b1
MX6QDL_PAD_GPIO_8__I2C4_SDA             0x4001b8b1
>;
};

  /* user LED 관련 */
pinctrl_led: ledgrp {
fsl,pins = <
MX6QDL_PAD_EIM_A25__GPIO5_IO02 0x1b0b1 /* user led0 */
MX6QDL_PAD_EIM_D28__GPIO3_IO28 0x1b0b1 /* user led1 */
>;
};

  /* PWM 관련 - 4개 */
pinctrl_pwm1: pwm1grp {
fsl,pins = <
MX6QDL_PAD_DISP0_DAT8__PWM1_OUT 0x1b0b1
>;
};

pinctrl_pwm2: pwm2grp {
fsl,pins = <
MX6QDL_PAD_DISP0_DAT9__PWM2_OUT 0x1b0b1
>;
};

pinctrl_pwm3: pwm3grp {
fsl,pins = <
MX6QDL_PAD_SD1_DAT1__PWM3_OUT 0x1b0b1
>;
};

pinctrl_pwm4: pwm4grp {
fsl,pins = <
MX6QDL_PAD_SD1_CMD__PWM4_OUT 0x1b0b1
>;
};

  /* UART 관련 - 5개 */
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6QDL_PAD_CSI0_DAT10__UART1_TX_DATA 0x1b0b1
MX6QDL_PAD_CSI0_DAT11__UART1_RX_DATA 0x1b0b1
>;
};

pinctrl_uart2: uart2grp {
fsl,pins = <
MX6QDL_PAD_EIM_D26__UART2_TX_DATA 0x1b0b1
MX6QDL_PAD_EIM_D27__UART2_RX_DATA 0x1b0b1
>;
};

pinctrl_uart3: uart3grp {
fsl,pins = <
MX6QDL_PAD_EIM_D24__UART3_TX_DATA 0x1b0b1
MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1
>;
};

pinctrl_uart4: uart4grp {
fsl,pins = <
MX6QDL_PAD_KEY_COL0__UART4_TX_DATA 0x1b0b1
MX6QDL_PAD_KEY_ROW0__UART4_RX_DATA 0x1b0b1
>;
};

pinctrl_uart5: uart5grp {
fsl,pins = <
MX6QDL_PAD_KEY_COL1__UART5_TX_DATA 0x1b0b1
MX6QDL_PAD_KEY_ROW1__UART5_RX_DATA 0x1b0b1
>;
};

  /* USB OTG 관련 */
pinctrl_usbotg: usbotggrp {
fsl,pins = <
MX6QDL_PAD_ENET_RX_ER__USB_OTG_ID 0x17059
MX6QDL_PAD_EIM_D22__GPIO3_IO22 0x000b0 /* MX6QDL_PAD_EIM_D22__USB_OTG_PWR */
MX6QDL_PAD_EIM_D21__USB_OTG_OC 0x1b0b0
>;
};

  /* SD/MMC 관련 - 3개 */
pinctrl_usdhc2: usdhc2grp {
fsl,pins = <
MX6QDL_PAD_SD2_CMD__SD2_CMD 0x17059
MX6QDL_PAD_SD2_CLK__SD2_CLK 0x10059
MX6QDL_PAD_SD2_DAT0__SD2_DATA0 0x17059
MX6QDL_PAD_SD2_DAT1__SD2_DATA1 0x17059
MX6QDL_PAD_SD2_DAT2__SD2_DATA2 0x17059
MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x17059
MX6QDL_PAD_GPIO_4__GPIO1_IO04 0x1b0b0 /* SD2 CD */
MX6QDL_PAD_GPIO_2__GPIO1_IO02 0x1f0b0 /* SD2 WP */
>;
};

pinctrl_usdhc3: usdhc3grp {
fsl,pins = <
MX6QDL_PAD_SD3_CMD__SD3_CMD 0x17059
MX6QDL_PAD_SD3_CLK__SD3_CLK 0x10059
MX6QDL_PAD_SD3_DAT0__SD3_DATA0 0x17059
MX6QDL_PAD_SD3_DAT1__SD3_DATA1 0x17059
MX6QDL_PAD_SD3_DAT2__SD3_DATA2 0x17059
MX6QDL_PAD_SD3_DAT3__SD3_DATA3 0x17059
MX6QDL_PAD_SD3_DAT5__GPIO7_IO00 0x1b0b0 /* SD3 CD */
MX6QDL_PAD_SD3_DAT4__GPIO7_IO01 0x1f0b0 /* SD3 WP */
>;
};

pinctrl_usdhc4: usdhc4grp {
fsl,pins = <
MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059
MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059
MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059
MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059
MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059
MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059
MX6QDL_PAD_NANDF_ALE__GPIO6_IO08 0x17059 /* SD4 RST (eMMC) */
>;
};
};
};
==============================================================

i.MX6의 Pin control 방식에 관하여 설명할 차례가 되었다. 앞서 소개했던 LED의 pin control 부분을 예로 하여 설명을 시작해 보도록 하자.

그림 3.2 RIoT board 2 User LED 회로도(출처 - 참고문헌 7)


그림 3.3 RIoT board MPU(CPU) EIM_A25 pin(user led1)(출처 - 참고문헌 7)


그림 3.4 RIoT board MPU(CPU) EIM_D28 pin(user led2)(출처 - 참고문헌 7)

==============================================================
    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_led>;

        led0: user1 {
            label = "user1";
            gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
            default-state = "on";
            linux,default-trigger = "heartbeat";
        };

        led1: user2 {
            label = "user2";
            gpios = <&gpio3 28 GPIO_ACTIVE_LOW>;
            default-state = "off";
        };
    };

...

        pinctrl_led: ledgrp {
            fsl,pins = <
                MX6QDL_PAD_EIM_A25__GPIO5_IO02      0x1b0b1 .... (A) /* user led0 */
                MX6QDL_PAD_EIM_D28__GPIO3_IO28      0x1b0b1            /* user led1 */
            >;
        };
==============================================================

위의 pinctrl_led의 내용 중, (A) 부분이 의미하는 것이 과연 무엇일까 ? 다시말해 아래 형식이 의미하는 바가 무엇일까 ?

fsl,pins = <PIN_FUNC_ID  CONFIG>

먼저 MX6QDL_PAD_EIM_A25__GPIO5_IO02의 source를 따라가 보니, 그 내용이 아래와 같다. 

==============================================================
<arch/arm/boot/dts/imx6dl-pinfunc.h>
/*
 * The pin function ID is a tuple of
 * <mux_reg conf_reg input_reg mux_mode input_val>
 */
#define MX6QDL_PAD_CSI0_DAT10__IPU1_CSI0_DATA10     0x04c 0x360 0x000 0x0 0x0
...
#define MX6QDL_PAD_EIM_A25__EIM_ADDR25              0x134 0x504 0x000 0x0 0x0
#define MX6QDL_PAD_EIM_A25__ECSPI4_SS1              0x134 0x504 0x000 0x1 0x0
#define MX6QDL_PAD_EIM_A25__ECSPI2_RDY              0x134 0x504 0x000 0x2 0x0
#define MX6QDL_PAD_EIM_A25__IPU1_DI1_PIN12          0x134 0x504 0x000 0x3 0x0
#define MX6QDL_PAD_EIM_A25__IPU1_DI0_D1_CS          0x134 0x504 0x000 0x4 0x0
#define MX6QDL_PAD_EIM_A25__GPIO5_IO02              0x134 0x504 0x000 0x5 0x0
#define MX6QDL_PAD_EIM_A25__HDMI_TX_CEC_LINE        0x134 0x504 0x85c 0x6 0x0
#define MX6QDL_PAD_EIM_A25__EPDC_DATA15             0x134 0x504 0x000 0x8 0x0
#define MX6QDL_PAD_EIM_A25__EIM_ACLK_FREERUN        0x134 0x504 0x000 0x9 0x0
...
==============================================================

PIN_FUNC_ID는 pin(pad)의 기능(용도)을 정의하는 것으로 보이는데, 위의 내용에 따르면 실제로는 아래와 같은 내용으로 다시 구성되어 있음을 알 수 있다.
<mux_reg  conf_reg  input_reg  mux_mode  input_val>

이 내용이 어떤 의미를 갖는지를 이해하기 위해서는, 먼저 i.MX6의 IOMUXC가 어떤 식으로 동작하는지를 먼저 파악해야 할 듯 보인다. 아래 내용은 참고 문헌 5에서 발췌한 내용으로, IOMUXC를 구성하는 register인 mux control register(= mux_reg), pad control register(= conf_reg), input register(= input_reg) 및 general purpose register 등에 관하여 언급하고 있다. 이중, 위의 예제와 관련된 register는 control register와 pad control register이며, input register는 사용하지 않는 것으로 보면 된다.


이를 토대로 위의 값의 의미를 정리해 보면 다음과 같다.
----------------------------------------------------------------------------------------------------------------
mux_reg = 0x134        <= mux control register, 그림 3.6  참조(IOMUXC는 0x020E_로 시작함)
conf_reg = 0x504        <= pad control register, 그림 3.7 참조
input_reg = 0x000       <= 0x020E_0000이 input register의 base 주소임. 따라서 input register를 사용하지 않겠다는 의미로 보임.
mux_mode = 0x5         <= 그림 3.6 참조(ATL5 - GPIO5_IO02에 해당함)
input_value = 0x0        <= input register를 사용할 경우에 필요한 값을 지정해 줌. 그렇지 않을 경우는 0으로 설정 
----------------------------------------------------------------------------------------------------------------

참고 사항)  mux control register가 서로 다른 용도로 사용되는 pad(예: SPI, UART, GPIO ...)중 하나를 선택하는 용도로 사용된다고 보면, input register는 동일한 목적을 위해 사용되는 2개 이상의 pad(예를 들어 SD1_DAT1, SD2_DAT1) 중 하나를 선택(select)하는 경우에 사용되는 것으로 이해하면 된다.

각각이 의미하는 바를 제대로 이해하기 위해서는 아래 열거한 그림(3.5 ~ 3.7)을  반드시 함께 이해해야 한다.


그림 3.5 IOMUX 관련 Pin 할당표(EIM_A25 주목)[출처 - 참고문헌 5]


그림 3.6 Pad Mux Register - IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR25[출처 - 참고문헌 5]


그림 3.7 Pad Control Register - IOMUXC_SW_PAD_CTL_PAD_EIM_ADDR25[출처 - 참고문헌 5]

지금까지 매우 복잡한 방식으로 PIN_FUNC_ID 즉, MX6QDL_PAD_EIM_A25__GPIO5_IO02가 의미하는 바를 파악해 보았다. 이상의 내용을 간단히 요약하자면, "pin mux로 사용 가능한 pin(pad) EIM_A25를 GPIO5_02로 사용하기 위해서는 mode 값을 ALT5로 지정해 주어야 한다" 정도가 될 듯하다. 만일 GPIO가 아니라 다른 용도로 해당 pad(mux output)를 사용하고자 한다면, 그림 3.6에서 mux mode를 원하는 값으로 지정해 주면 된다.

다음으로 CONFIG는 pad setting 값(쉽게 말해 pull-up, pull-down 등을 지정하는 것으로 보면 됨)을 지정해 주는 것으로, 그림 3.5의  Pad Settings 열을 확인해 보는 것이 이해에 도움을 줄 것으로 보인다.

위의 예에서 "0x1b0b1"을 이진수로 표현하면, "0001 1011 0000 1011 0001"이 되므로, 아래 fsl,imx6dl-pinctrl.txt 파일 내용을 토대로 할 때, 이는 PAD_CTL_HYS, PAD_CTL_SRE_SLOW, PAD_CTL_DSE_40ohm, PAD_CTL_SPEED_MED, PAD_CTL_PUS_100K_UP, PAD_CTL_PUE and PAD_CTL_PKE 조건을 갖는 것으로 해석될 수 있다.

<Documentation/devicetree/bindings/pinctrl/fsl,imx6dl-pinctrl.txt>
-------------------------------------------------------------------------------------
PAD_CTL_HYS                     (1 << 16)
PAD_CTL_PUS_100K_DOWN           (0 << 14)
PAD_CTL_PUS_47K_UP              (1 << 14)
PAD_CTL_PUS_100K_UP             (2 << 14)
PAD_CTL_PUS_22K_UP              (3 << 14)
PAD_CTL_PUE                     (1 << 13)
PAD_CTL_PKE                     (1 << 12)
PAD_CTL_ODE                     (1 << 11)
PAD_CTL_SPEED_LOW               (1 << 6)
PAD_CTL_SPEED_MED               (2 << 6)
PAD_CTL_SPEED_HIGH              (3 << 6)
PAD_CTL_DSE_DISABLE             (0 << 3)
PAD_CTL_DSE_240ohm              (1 << 3)
PAD_CTL_DSE_120ohm              (2 << 3)
PAD_CTL_DSE_80ohm               (3 << 3)
PAD_CTL_DSE_60ohm               (4 << 3)
PAD_CTL_DSE_48ohm               (5 << 3)
PAD_CTL_DSE_40ohm               (6 << 3)
PAD_CTL_DSE_34ohm               (7 << 3)
PAD_CTL_SRE_FAST                (1 << 0)
PAD_CTL_SRE_SLOW                (0 << 0)
-------------------------------------------------------------------------------------

이상으로 RIoT board의 pin control 동작 원리를 분석해 보았다. 다른 pin control의 경우도 동일한 원리로 분석이 가능할 것으로 보인다.


4. RIoT Board LED Device Driver 분석 
이번 절에서는 RIoT 관련 여러 device driver 중, 간단 기본적인 driver라고 볼 수 있는 LED(gpio 포함) driver를 분석해 보기로 하겠다. 엄밀히 말하면 RIoT LED 드라이버가 아니라 linux led framework에 관한 부분이라고 볼 수 있는데, 앞서 언급했던 led 관련 device tree와 상호간에 어떻게 연결되는지도 눈여겨 살펴 볼 필요가 있어 보인다.

먼저, LED driver의 code를 분석하기 전에 LED driver의 역할을 간략히 정리해 봄으로써, LED driver의 전체 구조를 가늠해 보도록 하자.
<LED 드라이버의 개요>
a) LED는 GPIO(output)를 이용하여 구현한다.
b) LED는 사용자 영역(sysfs)에서 제어될 수 있어야 한다.
c) LED operation 1: LED를 켜거나 끌 수 있어야 한다.
d) LED operation 2: LED의 밝기를 조절할 수 있어야 한다.
e) LED operation3: LED가 깜빡(blink)이는 주기를 제어할 수 있어야 한다.
f) LED operation 3: LED가 켜지는 조건(trigger)을 변경할 수 있어야 하며, 각각의 조건(상황)에 맞게  LED가 동작되어야 한다.

GPIO LED driver는 주로 아래 파일과 연관이 있다.
include/linux/leds.h  /* gpio led를 위한 data structure 정의 */
drivers/leds/leds-gpio.c /* gpio led main code */
drivers/leds/led-class.c  /* sysfs 관련 코드 */
drivers/leds/led-core.c
drivers/leds/led-triggers.c  /* led trigger 관련 base code - 다른 driver에서 호출하여 사용 가능 */
drivers/leds/trigger/ledtrig-backlight.c
drivers/leds/trigger/ledtrig-camera.c
drivers/leds/trigger/ledtrig-cpu.c
drivers/leds/trigger/ledtrig-default-on.c
drivers/leds/trigger/ledtrig-gpio.c
drivers/leds/trigger/ledtrig-heartbeat.c
drivers/leds/trigger/ledtrig-ide-disk.c
drivers/leds/trigger/ledtrig-oneshot.c
drivers/leds/trigger/ledtrig-timer.c
drivers/leds/trigger/ledtrig-transient.c

==============================================================
<LED 관련 device tree 내용>
    leds {
        compatible = "gpio-leds";   /* device driver와 matching되는 string */
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_led>;

        led0: user1 {
            label = "user1";   /* sysfs에 표시되는 name */
            gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;  /* GPIO5_2, output, active low */
            default-state = "on";   /* default state 지정: on, off, keep 중 on 선택 */
            linux,default-trigger = "heartbeat";   /* trigger 방식 중, heartbeat 방식 선택, 값을 지정하지 않으면 none으로 표시됨. */
        };

        led1: user2 {
            label = "user2";
            gpios = <&gpio3 28 GPIO_ACTIVE_LOW>;
            default-state = "off";
        };
    };
==============================================================
<LED 관련 주요 data structures>
struct gpio_led {  /* leds-gpio driver 관련 - led 당 부여되는 정보 */
    const char *name;
    const char *default_trigger;
    unsigned    gpio;
    unsigned    active_low : 1;
    unsigned    retain_state_suspended : 1;
    unsigned    default_state : 2;
    /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
    struct gpio_desc *gpiod;
};

struct gpio_leds_priv {
    int num_leds;   /* led 갯수 */
    struct gpio_led_data leds[];
};

struct gpio_led_data {
    struct led_classdev cdev;
    struct gpio_desc *gpiod;
    struct work_struct work;
    u8 new_level;
    u8 can_sleep;
    u8 blinking;
    int (*platform_gpio_blink_set)(struct gpio_desc *desc, int state,
            unsigned long *delay_on, unsigned long *delay_off);
};

struct led_classdev {  /* led sysfs 관련 */
    const char      *name;
    enum led_brightness  brightness;
    enum led_brightness  max_brightness;
    int          flags;
    void        (*brightness_set)(struct led_classdev *led_cdev,
                      enum led_brightness brightness);
    int     (*brightness_set_sync)(struct led_classdev *led_cdev,
                    enum led_brightness brightness);
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

    int     (*blink_set)(struct led_classdev *led_cdev,
                     unsigned long *delay_on,
                     unsigned long *delay_off);

    struct device       *dev;
    const struct attribute_group    **groups;

    struct list_head     node;          /* LED Device list */
    const char      *default_trigger;   /* Trigger to use */

    unsigned long        blink_delay_on, blink_delay_off;
    struct timer_list    blink_timer;
    int          blink_brightness;
    void            (*flash_resume)(struct led_classdev *led_cdev);

    struct work_struct  set_brightness_work;
    int         delayed_set_value;

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    struct rw_semaphore  trigger_lock;

    struct led_trigger  *trigger;
    struct list_head     trig_list;
    void            *trigger_data;
    /* true if activated - deactivate routine uses it to do cleanup */
    bool            activated;
#endif

    struct mutex        led_access;
};

struct led_trigger {
    /* Trigger Properties */
    const char   *name;
    void        (*activate)(struct led_classdev *led_cdev);
    void        (*deactivate)(struct led_classdev *led_cdev);

    /* LEDs under control by this trigger (for simple triggers) */
    rwlock_t      leddev_list_lock;
    struct list_head  led_cdevs;
 
    /* Link to next registered trigger */
    struct list_head  next_trig;
};
==============================================================
<LED 관련 주요 코드>

<drivers/leds/leds-gpio.c>
static const struct of_device_id of_gpio_leds_match[] = {
    { .compatible = "gpio-leds", },
    {},
};

MODULE_DEVICE_TABLE(of, of_gpio_leds_match);

static struct platform_driver gpio_led_driver = {
    .probe      = gpio_led_probe,      /* (1) */
    .remove     = gpio_led_remove,
    .shutdown   = gpio_led_shutdown,
    .driver     = {
        .name   = "leds-gpio",
        .of_match_table = of_gpio_leds_match,
    },
};

module_platform_driver(gpio_led_driver);

/* gpio led probe 함수 */
static int gpio_led_probe(struct platform_device *pdev)    /* (2) */
{
    struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
    struct gpio_leds_priv *priv;
    int i, ret = 0;

    if (pdata && pdata->num_leds) {
        priv = devm_kzalloc(&pdev->dev,
                sizeof_gpio_leds_priv(pdata->num_leds),
                    GFP_KERNEL);
        if (!priv)
            return -ENOMEM;

        priv->num_leds = pdata->num_leds;
        for (i = 0; i < priv->num_leds; i++) {
            ret = create_gpio_led(&pdata->leds[i],
                          &priv->leds[i],
                          &pdev->dev, pdata->gpio_blink_set);
            if (ret < 0) {
                /* On failure: unwind the led creations */
                for (i = i - 1; i >= 0; i--)
                    delete_gpio_led(&priv->leds[i]);
                return ret;
            }
        }
    } else {  /* device tree를 사용할 경우는 이 코드를 사용하게 됨. */
        priv = gpio_leds_create(pdev);    /* (3) */
        if (IS_ERR(priv))
            return PTR_ERR(priv);
    }

    platform_set_drvdata(pdev, priv);

    return 0;
}

/* device tree의 각각의 led node에 대해 create_gpio_led() 함수를 호출함 */
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)   /* (4) */
{
    struct device *dev = &pdev->dev;
    struct fwnode_handle *child;
    struct gpio_leds_priv *priv;
    int count, ret;
    struct device_node *np;

    count = device_get_child_node_count(dev);
    if (!count)
        return ERR_PTR(-ENODEV);

    priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
    if (!priv)
        return ERR_PTR(-ENOMEM);

    device_for_each_child_node(dev, child) {
        struct gpio_led led = {};
        const char *state = NULL;

        led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
        if (IS_ERR(led.gpiod)) {
            fwnode_handle_put(child);
            ret = PTR_ERR(led.gpiod);
            goto err;
        }

        np = to_of_node(child);

        if (fwnode_property_present(child, "label")) {
            fwnode_property_read_string(child, "label", &led.name);
        } else {
            if (IS_ENABLED(CONFIG_OF) && !led.name && np)
                led.name = np->name;
            if (!led.name) {
                ret = -EINVAL;
                goto err;
            }
        }
        fwnode_property_read_string(child, "linux,default-trigger",
                        &led.default_trigger);

        if (!fwnode_property_read_string(child, "default-state",
                         &state)) {
            if (!strcmp(state, "keep"))
                led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
            else if (!strcmp(state, "on"))
                led.default_state = LEDS_GPIO_DEFSTATE_ON;
            else
                led.default_state = LEDS_GPIO_DEFSTATE_OFF;
        }

        if (fwnode_property_present(child, "retain-state-suspended"))
            led.retain_state_suspended = 1;

        ret = create_gpio_led(&led, &priv->leds[priv->num_leds],    /* (5) */
                      dev, NULL);
        if (ret < 0) {
            fwnode_handle_put(child);
            goto err;
        }
        priv->num_leds++;
    }

    return priv;

err:
    for (count = priv->num_leds - 1; count >= 0; count--)
        delete_gpio_led(&priv->leds[count]);
    return ERR_PTR(ret);
}

/* led를 gpio output으로 설정한 후,  led_classdev_register() 함수를 호출함 */
static int create_gpio_led(const struct gpio_led *template,     /* (6) */
    struct gpio_led_data *led_dat, struct device *parent,
    int (*blink_set)(struct gpio_desc *, int, unsigned long *,
             unsigned long *))
{
    int ret, state;

    led_dat->gpiod = template->gpiod;
    if (!led_dat->gpiod) {
        /*
         * This is the legacy code path for platform code that
         * still uses GPIO numbers. Ultimately we would like to get
         * rid of this block completely.
         */
        unsigned long flags = 0;

        /* skip leds that aren't available */
        if (!gpio_is_valid(template->gpio)) {
            dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
                    template->gpio, template->name);
            return 0;
        }

        if (template->active_low)
            flags |= GPIOF_ACTIVE_LOW;

        ret = devm_gpio_request_one(parent, template->gpio, flags,
                        template->name);
        if (ret < 0)
            return ret;

        led_dat->gpiod = gpio_to_desc(template->gpio);
        if (IS_ERR(led_dat->gpiod))
            return PTR_ERR(led_dat->gpiod);
    }

    led_dat->cdev.name = template->name;
    led_dat->cdev.default_trigger = template->default_trigger;
    led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
    led_dat->blinking = 0;
    if (blink_set) {
        led_dat->platform_gpio_blink_set = blink_set;
        led_dat->cdev.blink_set = gpio_blink_set;
    }
    led_dat->cdev.brightness_set = gpio_led_set;
    if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
        state = !!gpiod_get_value_cansleep(led_dat->gpiod);
    else
        state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
    led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
    if (!template->retain_state_suspended)
        led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;

    ret = gpiod_direction_output(led_dat->gpiod, state);
    if (ret < 0)
        return ret;

    INIT_WORK(&led_dat->work, gpio_led_work);

    return led_classdev_register(parent, &led_dat->cdev);    /* (7) */
}

/* drivers/leds/led-class.c */
/* led_classdev class에 대한 새로운 object를 등록해 준다 - sysfs로 led 제어하기 위해서 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)    /* (8) */
{
    char name[64];
    int ret;

    ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
    if (ret < 0)
        return ret;

    /* device를 하나 만들고, sysfs로 등록한다 */
    led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
                led_cdev, led_cdev->groups, "%s", name);
    if (IS_ERR(led_cdev->dev))
        return PTR_ERR(led_cdev->dev);

    if (ret)
        dev_warn(parent, "Led %s renamed to %s due to name collision",
                led_cdev->name, dev_name(led_cdev->dev));

#ifdef CONFIG_LEDS_TRIGGERS
    init_rwsem(&led_cdev->trigger_lock);
#endif
    mutex_init(&led_cdev->led_access);
    /* add to the list of leds */
    down_write(&leds_list_lock);
    list_add_tail(&led_cdev->node, &leds_list);
    up_write(&leds_list_lock);
    
    if (!led_cdev->max_brightness) 
        led_cdev->max_brightness = LED_FULL;
    
    led_cdev->flags |= SET_BRIGHTNESS_ASYNC;
    
    led_update_brightness(led_cdev);

    led_init_core(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev);
#endif

    dev_dbg(parent, "Registered led device: %s\n",
            led_cdev->name);

    return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);
==============================================================

sysfs로 LED를 제어하기 위해서는 아래와 같이 하면 된다.

# ls -la /sys/class/leds
   => RIoT board에서 user가 제어할 수 있는 led를 2개(user1, user2) 제공하고 있다.
drwxr-xr-x    2 root     root             0 Jan  2 09:50 .
drwxr-xr-x   48 root     root             0 Jan  2 09:50 ..
lrwxrwxrwx    1 root     root             0 Jan  2 09:50 mmc0:: -> ../../devices/soc0/soc/2100000.aips-bus/2194000.usdhc/leds/mmc0::
lrwxrwxrwx    1 root     root             0 Jan  2 09:50 mmc1:: -> ../../devices/soc0/soc/2100000.aips-bus/2198000.usdhc/leds/mmc1::
lrwxrwxrwx    1 root     root             0 Jan  2 09:50 mmc2:: -> ../../devices/soc0/soc/2100000.aips-bus/219c000.usdhc/leds/mmc2::
lrwxrwxrwx    1 root     root             0 Jan  2 09:50 user1 -> ../../devices/soc0/leds/leds/user1
lrwxrwxrwx    1 root     root             0 Jan  2 09:50 user2 -> ../../devices/soc0/leds/leds/user2

<LED를 켜고자 할 경우>
# echo 255 > /sys/class/leds/user_led/brightness
<LED를 끄고자 할 경우>
# echo 0 > /sys/class/leds/user_led/brightness

<LED의 trigger 방식 - LED 불이 켜지는 원칙 -을 확인하고자 할 경우>
root@imx6dl-riotboard:/sys/devices/soc0/leds/leds/user1# cat trigger 
   => user led 디렉토리 내용 중 trigger의 값을 살펴 보면, led 출력 방식을 알 수 있다.
none rc-feedback kbd-scrollock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock nand-disk mmc0 mmc1 mmc2 timer oneshot [heartbeat] backlight gpio
==============================================================


5. RIoT Board GPIO Controller Device Driver 분석 
이번 절에서는 RIoT board GPIO controller를 위한 device driver code를 간략히 분석해 보도록 하겠다. RIoT board에는 7개의 GPIO bank(controller라고 해도 무방할 듯)가 있으며, 이를 device tree로 표현해 보면 코드 5.1과 같다.

코드 5.1 Freescale i.MX6Q gpio controller device tree(7개중 일부 발췌)

위의 device tree 내용에 의하면, gpio1 ~ gpio7의 구성이 모두 동일하므로, 이 중 gpio2 만을 따로 정리해 보기로 하자.
==============================================================
<imx6qdl.dtsi>
 aliases {
     ethernet0 = &fec;
     can0 = &can1;
     can1 = &can2;
     gpio0 = &gpio1;
     gpio1 = &gpio2;
     gpio2 = &gpio3;
     gpio3 = &gpio4;
     gpio4 = &gpio5;
     gpio5 = &gpio6;

     gpio6 = &gpio7;
     ...
};

gpio2: gpio@020a0000 {
     compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
     reg = <0x020a0000 0x4000>;
     interrupts = <0 68 IRQ_TYPE_LEVEL_HIGH>,    /* interrupt request(인터럽트를 사용하는 장치) */
                         <0 69 IRQ_TYPE_LEVEL_HIGH>;
     gpio-controller;   /* gpio controller임을 선언 */
     #gpio-cells = <2>;
   interrupt-controller;    /* interrupt controller임을 선언, interrupt 요청자인 동시에 interrupt controller이기도 함. */
   #interrupt-cells = <2>;   /* interrupt를 표현하기 위해 2개의 cell(32bit)이 필요하다는 의미 */

                                            /* interrupt-parent가 생략된 경우는 parent node에서 기술한 정보 사용 - gpc가 interrupt-parent가 됨 */
 };

<imx6dl-riotboard.dts - gpio interrupt 사용 예>
&fec {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_enet>;
    phy-mode = "rgmii";
    phy-reset-gpios = <&gpio3 31 0>;
    interrupts-extended = <&gpio1 6 IRQ_TYPE_LEVEL_HIGH>,   /* interrupt parent가 gpio1과 intc 2개임 */
                  <&intc 0 119 IRQ_TYPE_LEVEL_HIGH>;
    status = "okay";
};
==============================================================

gpio2를 중심으로 위의 내용을 요약해 보면, 아래와 같다고 말할 수 있다.
fec(ethernet controller) -> (interrupt 6) -> gpio1 controller -> (interrupt 66) -> gpc

즉, gpio controller는 다른 주변 장치로 부터의 interrupt를 받아 처리하는 역할(interrupt controller)도 해야 하지만, 이를 받아 실제 interrupt controller에게 interrupt를 요청하는 device로써의 역할도 해야 함을 알 수 있다.

그림 5.1 cat /proc/interrupts( 39:      22402  gpio-mxc   6 Level     2188000.ethernet)

GPIO 관련 device tree 내용을 살펴 보았으니, 이제는 실제로 device driver가 어떻게 구현되어 있는지를 살펴볼 차례이다. GPIO driver와 관련해서는 아래와 같이 크게 두가지를 눈여겨 보아야 할 것으로 보인다.
a) gpio framework에 맞게 구현되어 있는가 ?
  => 즉, sysfs 등으로 제어가 가능해야 하며, 타 driver내에서 gpio kernel API를 사용할 수 있도록 해 주어야 한다. 
b) interrupt 관련 code가 구비되어 있는가 ?
   => gpio controller를 interrupt controller로 사용할 수 있으므로 이와 관련된 코드가 준비되어야 한다.

이러한 내용을 감안한 후, gpio driver code를 분석해 보도록 하자. 먼저, GPIO controller와 관련된 code를 나열해 보면 대략 다음과 같다.
include/linux/gpio/driver.h
include/linux/basic_mmio_gpio.h
drivers/gpio/gpio-mxc.c
drivers/gpio/gpio-generic.c
drivers/gpio/gpiolib.c
drivers/gpio/gpiolib-of.c
kernel/irq/irqdomain.c
kernel/irq/generic-chip.c


다음으로, freescale gpio driver에서 사용하는 주요 data structure와 driver code를 정리해 보면 다음과 같다.
==============================================================
<주요 data structures>

struct gpio_chip {   /* include/linux/gpio/driver.h, gpio library 관련 header */
    const char      *label;
    struct device       *dev;
    struct device       *cdev;
    struct module       *owner;
    struct list_head        list;

    int         (*request)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*free)(struct gpio_chip *chip,
                        unsigned offset);
    int         (*get_direction)(struct gpio_chip *chip,
                        unsigned offset);
    int         (*direction_input)(struct gpio_chip *chip,
                        unsigned offset);
    int         (*direction_output)(struct gpio_chip *chip,
                        unsigned offset, int value);
    int         (*get)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*set)(struct gpio_chip *chip,
                        unsigned offset, int value);
    void            (*set_multiple)(struct gpio_chip *chip,
                        unsigned long *mask,
                        unsigned long *bits);
    int         (*set_debounce)(struct gpio_chip *chip,
                        unsigned offset,
                        unsigned debounce);

    int         (*to_irq)(struct gpio_chip *chip,
                        unsigned offset);

    void            (*dbg_show)(struct seq_file *s,
                        struct gpio_chip *chip);
    int         base;
    u16         ngpio;
    struct gpio_desc    *desc;
    const char      *const *names;
    bool            can_sleep;
    bool            irq_not_threaded;

#ifdef CONFIG_GPIOLIB_IRQCHIP
    struct irq_chip     *irqchip;
    struct irq_domain   *irqdomain;
    unsigned int        irq_base;
    irq_flow_handler_t  irq_handler;
    unsigned int        irq_default_type;
    int         irq_parent;
    struct lock_class_key   *lock_key;
#endif

#if defined(CONFIG_OF_GPIO)
    struct device_node *of_node;
    int of_gpio_n_cells;
    int (*of_xlate)(struct gpio_chip *gc,
            const struct of_phandle_args *gpiospec, u32 *flags);
#endif
#ifdef CONFIG_PINCTRL
    struct list_head pin_ranges;
#endif
}

struct bgpio_chip {   /* include/linux/basic_mmio_gpio.h,  memory-mapped GPIO controllers */
    struct gpio_chip gc;
 
    unsigned long (*read_reg)(void __iomem *reg);
    void (*write_reg)(void __iomem *reg, unsigned long data);
 
    void __iomem *reg_dat;
    void __iomem *reg_set;
    void __iomem *reg_clr;
    void __iomem *reg_dir;

    int bits;
    unsigned long (*pin2mask)(struct bgpio_chip *bgc, unsigned int pin);
    spinlock_t lock;
    unsigned long data;
    unsigned long dir;
};

struct mxc_gpio_port {           /* drivers/gpio/gpio-mxc.c, freescale gpio port 정보 */
    struct list_head node;
    void __iomem *base;
    int irq;
    int irq_high;
    struct irq_domain *domain;
    struct bgpio_chip bgc;
    u32 both_edges;
};
==============================================================
<주요 gpio controller driver 요약>
<drivers/gpio/gpio-mxc.c>
static const struct of_device_id mxc_gpio_dt_ids[] = {
    { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
    { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
    { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
    { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
    { /* sentinel */ }
};

static struct platform_driver mxc_gpio_driver = {
    .driver     = {
        .name   = "gpio-mxc",
        .of_match_table = mxc_gpio_dt_ids,
    },
    .probe      = mxc_gpio_probe,    /* (4) */
    .id_table   = mxc_gpio_devtype,
};

static int __init gpio_mxc_init(void)   /* (2) */
{
    return platform_driver_register(&mxc_gpio_driver);   /* (3) */
}
postcore_initcall(gpio_mxc_init);   /* (1) */

static int mxc_gpio_probe(struct platform_device *pdev)   /* (5) */
{
    struct device_node *np = pdev->dev.of_node;
    struct mxc_gpio_port *port;
    struct resource *iores;
    int irq_base;
    int err;

    mxc_gpio_get_hw(pdev);

    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
    if (!port)
        return -ENOMEM;

    iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    port->base = devm_ioremap_resource(&pdev->dev, iores);
    if (IS_ERR(port->base))
        return PTR_ERR(port->base);

    port->irq_high = platform_get_irq(pdev, 1);
    port->irq = platform_get_irq(pdev, 0);
    if (port->irq < 0)
        return port->irq;

    /* disable the interrupt and clear the status */
    writel(0, port->base + GPIO_IMR);
    writel(~0, port->base + GPIO_ISR);

    if (mxc_gpio_hwtype == IMX21_GPIO) {
        /*
         * Setup one handler for all GPIO interrupts. Actually setting
         * the handler is needed only once, but doing it for every port
         * is more robust and easier.
         */
        irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
    } else {
        /* setup one handler for each entry */
        irq_set_chained_handler_and_data(port->irq,            /* gpio interrupt에 대한 handler를 등록한다 */
                         mx3_gpio_irq_handler, port);
        if (port->irq_high > 0)
            /* setup handler for GPIO 16 to 31 */
            irq_set_chained_handler_and_data(port->irq_high,
                             mx3_gpio_irq_handler,
                             port);
    }

    err = bgpio_init(&port->bgc, &pdev->dev, 4,   /* gpio operation 초기화 */
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL,
             BGPIOF_READ_OUTPUT_REG_SET);
    if (err)
        goto out_bgio;

    port->bgc.gc.to_irq = mxc_gpio_to_irq;
    port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
                         pdev->id * 32;

    err = gpiochip_add(&port->bgc.gc);   /* (6) */ /* 각각의 gpio bank를 gpio chip으로 등록함 */
    if (err)
        goto out_bgpio_remove;

    irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
    if (irq_base < 0) {
        err = irq_base;
        goto out_gpiochip_remove;
    }

    port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
                         &irq_domain_simple_ops, NULL);
    if (!port->domain) {
        err = -ENODEV;
        goto out_irqdesc_free;
    }

    /* gpio-mxc can be a generic irq chip */
    err = mxc_gpio_init_gc(port, irq_base);   /* Setup a range of interrupts with a generic chip */
    if (err < 0)
        goto out_irqdomain_remove;

    list_add_tail(&port->node, &mxc_gpio_ports);

    return 0;

out_irqdomain_remove:
    irq_domain_remove(port->domain);
out_irqdesc_free:
    irq_free_descs(irq_base, 32);
out_gpiochip_remove:
    gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
    bgpio_remove(&port->bgc);
out_bgio:
    dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
    return err;
}

/* register a gpio_chip */
int gpiochip_add(struct gpio_chip *chip)   /* (7) */
{
    unsigned long   flags;
    int     status = 0;
    unsigned    id;
    int     base = chip->base;
    struct gpio_desc *descs;

    descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL);
    if (!descs)
        return -ENOMEM;

    spin_lock_irqsave(&gpio_lock, flags);

    if (base < 0) {
        base = gpiochip_find_base(chip->ngpio);
        if (base < 0) {
            status = base;
            spin_unlock_irqrestore(&gpio_lock, flags);
            goto err_free_descs;
        }
        chip->base = base;
    }

    status = gpiochip_add_to_list(chip);
    if (status) {
        spin_unlock_irqrestore(&gpio_lock, flags);
        goto err_free_descs;
    }

    for (id = 0; id < chip->ngpio; id++) {
        struct gpio_desc *desc = &descs[id];

        desc->chip = chip;

        /* REVISIT: most hardware initializes GPIOs as inputs (often
         * with pullups enabled) so power usage is minimized. Linux
         * code should set the gpio direction first thing; but until
         * it does, and in case chip->get_direction is not set, we may
         * expose the wrong direction in sysfs.
         */
        desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0;
    }

    chip->desc = descs;

    chip->desc = descs;

    spin_unlock_irqrestore(&gpio_lock, flags);

#ifdef CONFIG_PINCTRL
    INIT_LIST_HEAD(&chip->pin_ranges);
#endif

    if (!chip->owner && chip->dev && chip->dev->driver)
        chip->owner = chip->dev->driver->owner;

    status = gpiochip_set_desc_names(chip);
    if (status)
        goto err_remove_from_list;

    status = of_gpiochip_add(chip);
    if (status)
        goto err_remove_chip;

    acpi_gpiochip_add(chip);

    status = gpiochip_sysfs_register(chip);
    if (status)
        goto err_remove_chip;

    pr_debug("%s: registered GPIOs %d to %d on device: %s\n", __func__,
        chip->base, chip->base + chip->ngpio - 1,
        chip->label ? : "generic");

    return 0;

err_remove_chip:
    acpi_gpiochip_remove(chip);
    gpiochip_free_hogs(chip);
    of_gpiochip_remove(chip);
err_remove_from_list:
    spin_lock_irqsave(&gpio_lock, flags);
    list_del(&chip->list);
    spin_unlock_irqrestore(&gpio_lock, flags);
    chip->desc = NULL;
err_free_descs:
    kfree(descs);

    /* failures here can mean systems won't boot... */
    pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__,
        chip->base, chip->base + chip->ngpio - 1,
        chip->label ? : "generic");
    return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);
==============================================================

여기서 잠깐 ! i.MX6 GPIO에 관하여
i.MX6에는 GPIO1 ~ GPIO7까지 총 7개의 GPIO bank가 있으며, 한개의 GPIO bank는 각각 32개의 GPIO line으로 구성되어 있다. 따라서, linux kernel에서 GPIO 번호를 할당 방식을 맞추기 위해서는 아래의 산술식을 이용해야만 한다.

Linux GPIO number = (<X> - 1) * 32 + <Y>
(단, <X>는 GPIO bank 번호, <Y>는 GPIO bank내의 GPIO line 번호를 의미함.)

이러한 내용을 기초로 할 때, user space에서 sysfs를 이용하여 GPIO를 제어(사용)하는 예를 들어 보면 다음과 같다.

ex) GPIO 97 = GPIO bank 4(= 96) + 1

<GPIO97을 gpio output으로  사용할 경우>
$ echo 97 > /sys/class/gpio/export
   => /sys/class/gpio/gpio97 디렉토리가 생성됨.
$ echo out > /sys/class/gpio/gpio97/direction 
   => gpio 방향(output)을 지정해 줌.
$ echo 1 > /sys/class/gpio/gpio97/value 
   => gpio 값을 1로 설정해 줌. 
$ echo 0 > /sys/class/gpio/gpio97/value 
   => gpio 값을 0으로 설정해 줌.

<GPIO97을 gpio input으로 사용할 경우>
$ echo 97 > /sys/class/gpio/export 
$ echo in > /sys/class/gpio/gpio97/direction 
$ cat /sys/class/gpio/gpio97/value
==============================================================

이상으로 RIoT board를 위한 device tree 분석 작업을 마치고자 한다. 부족한 부분은 추후 다른 post를 통해  좀더 보충해 보도록 하겠다.


References
1. Linux kernel 4.4.38
2. IMX-6_BSP_Manual.pdf, PHYTEC Messtechnik GmbH.
3. Embedded Linux Porjects Using Yocto Project Cookbook, Alex Gonzalez, PACKT Pubhsing.
4. devicetree-specification-v0.1-20160524.pdf, devicetree.org
5. IMX-6Solo-Processor-Reference-Manual.pdf, Freescale Semiconductor, Inc.
6. RIoTboard-User-Manual-V2.1.pdf, riotboard.org, Embest Tech Co. LTD.
7. RIoTboard-Schematics-v1.0.pdf, Embest Tech Co. LTD.

Slowboot