2014년 3월 25일 화요일

Beaglebone으로 알아보는 linux kernel(3.10.x) programming

본 고에서는 open source hardware로 유명한 beaglebone black(BBB)을 이용하여 최신 linux kernel(3.10.x)에서 device driver를 어떻게 작성하는지에 관하여 정리해 보고자 한다.

목차는 다음과 같다.

1. BBB 개요
2. BBB용 kernel source download & build 절차 소개
3. Device Tree 소개
4. GPIO driver example - LED driver
5. i2c driver 구현 - Wii nunchuk i2c slave driver
6. UART adapter driver 구현
7. SPI driver 구현
8. USB driver 구현
9. SD/MMC driver 구현
References


1. BBB 개요

Beaglebone Black은 TI AM335x 1Gz ARM Cortex-A8 processor를 탑재하였으며, 45$라는 저렴한 가격의 opensource hardware(제 2의 라즈베리파이로 불림)로, 아래 site에서 자세한 내용을 확인할 수 있다.




그림 1.1 BBB board hardware 개요

본 고에서 BBB를 선택하여 설명하는 이유는 다음과 같다.
1) Opensource hardware이므로, hardware 관련 자료(TRM 문서, SRM 문서, schematic 등)를 쉽게 얻을 수 있다.
2) Cortex-A8 기반의 ARM SoC를 채용하고 있으며, 최신 Linux kernel(3.10.x, 3.8.x)이 porting되어 있어, device tree 관련 내용을 테스트하기 용이하다.
  : device tree 관련 작업을 하는데 있어서 라즈베리파이 보다 배울 점이 많을 듯 보인다.
3) 여러 주변 장치를 맘껏 붙여 테스트해 볼 수가 있다(돈이 좀 들지만 ...).
4) 집에서도 테스트가 가능하다 .... 등등

대부분의 BBB 관련 내용이 사용자 영역에서의 programming(C, Python, Javascript 등 활용)에 촛점을 맞추고 있으나, 본 고에서는 kernel/device driver programming 관점에서 내용 전개를 진행할 예정이다. 이 글을 통해 최신 kernel(3.x) 환경에서 kernel/device driver programming이 어떻게 이루어지는지 확인해 보기 바란다.



그림 1.2 BBB LED 출력 화면(userspace에서 제어한 예제)


2. BBB용 kernel source download & build 절차 소개
이번 절에서는 Robert Nelson이 작성한 아래 site 내용을 토대로 BBB에 Debian 7(Wheezy)을 올리는 방법을 소개하고자 한다.



2.1) ARM Toolchain 설치
<생략>

2.2)  U-Boot bootloader source download & build 절차 소개
<생략>

2.3) Linux kernel source download & build 절차 소개
<생략>


그림 2.1 linux kernel source & build


2.4) Debian 7 용 rootfs download 절차 소개
<생략>

2.5) microSD card에 image 설치 절차 소개
<생략>

2.6) u-boot Environment txt 파일 소개
아래 내용은 u-boot가 booting에 사용하는 환경 설정 파일의 내용을 정리한 것이다. 아래 빨간색 글씨로 표시한 부분을 보면, kernel image와 fdt(flattened device tree - dtb로 보면 됨) image를 RAM에 별도로 loading한 후, bootz 명령으로 booting을 진행하고 있음을 알 수 있다.

#################################################################

$ cat uEnv.txt
kernel_file=zImage
initrd_file=uInitrd

loadzimage=load mmc ${mmcdev}:${mmcpart} ${loadaddr} ${kernel_file}

loadinitrd=load mmc ${mmcdev}:${mmcpart} 0x81000000 ${initrd_file}; setenv initrd_size ${filesize}

loadfdt=load mmc ${mmcdev}:${mmcpart} ${fdtaddr} /dtbs/${fdtfile}

console=ttyO0,115200n8
mmcroot=/dev/mmcblk0p2 ro
mmcrootfstype=ext4 rootwait fixrtc
mmcargs=setenv bootargs console=${console} root=${mmcroot} rootfstype=${mmcrootfstype} ${optargs}

#zImage:
uenvcmd=run loadzimage; run loadfdt; run mmcargs; bootz ${loadaddr} - ${fdtaddr}

#################################################################


3. Device Tree 소개

이 절에서 소개할 내용은 참고 문헌 [5]를 주로 참조하여 작성하였다. 따라서 자세한 사항을 원한다면 참고 문헌 [5]를 읽어 보기 바라며, 기초적인 내용 보다는 kernel programming에 필요한 관점에서 핵심적인 부분만을 추려 설명해 보고자 하였다. device tree 관련 기본 문법을 이해하고자 한다면, 아래 내용을 읽기 전에 필자의 아래 문서를 먼저 읽어 보기를 권한다.

http://www.kandroid.org/board/board.php?board=HTCDream&command=body&no=159
<Android_KernelHacks_Chapter4.pdf 참조>


[여기서 잠깐] Device Tree란 ?
단적으로 표현하면, 일정한 형식(문법)을 갖춘 텍스트를 이용하여, hardware(SoC, Board)를 기술하는 것을 말함.
이와 대비되는 기존의 방식으로 platform device 기반의 board 기술 방식(C coding)이 있었음.

<등장 배경 및 기존 방식의 문제점>
  1) SoC 혹은 board 별로 독자적인 code 구현
  2) 같은 SoC에서 파생된 보드 간에 상호 연관성이 있음에도 불구하고, 이를 전혀 고려하지 않고, 별도로 구현함.
  3) 따라서, 코드의 복잡도 및 코드량이 늘어는 문제 발생함.
     : arch/arm/mach-{YOURBOARD}/board-*.c 파일이 매우 복잡하고 난해함.
     : ARM linux 진영의 골칫거리. Linus Torvalds의 지적 !
  4) 보드 구성이 바뀌더라도 kernel code를 수정하지 않고, 동작할 수 있는 방식의 필요성 인식
  5) Device Tree는 기존에 다른 쪽(CPU)에서 사용하던 방식으로 ARM에도 채용하게 됨.
     : 새로 나오는 보드로 개발을 진행하여, linux kernel에 자신의 코드를 반영하고자 한다면, 반드시 device tree 기반으로 작업이 이루어져야 함.

<dts/dtsi 파일 compile 방법>

Source to blob:


$ scripts/dtc/dtc -I dts -O dtb -o /path/to/my-tree.dtb /path/to/my-tree.dts
Blob to source:
$ scripts/dtc/dtc -I dtb -O dts -o /path/to/fromdtb.dts /path/to/found_this.dtb



(*) Device Tree를 제대로 이해하기 위해서는(또는 이해하게 되면), 보드에 대한 전반적인 구조를 제대로 파악하고 있어야 한다(파악하게 된다).

우선 그림 3.1은  Device Tree를 이용하여, bootloader(예: u-boot)에서 DTB(Device Tree Blob - DT binary file)와 kernel image(uImage)를 RAM에 loading한 후, kernel booting을 진행하는 모습을 보여준다(기존 방식과의 차이는 별도 정리 안함).


그림 3.1 Device Tree를 이용한 커널 부팅 시, 메모리 맵


Device Tree에서 장치를 그림 3.2와 같이 노드로 표현하는데, 몇가지 주목해야할 부분만을 정리해 보면 다음과 같다.

<Device Tree에서의 장치 표현 방식 소개>
1) device는 노드로 표현하며(예: node@0), 각각의 노드는 다양한 속성 정보를 갖는다.
  : 각각의 device는 서로 다른 속성 정보(예: address, interrupt 정보 등)를 가짐.
  : 특히, compatible 속성은 device driver와 연결되는 부분으로, device driver code에서 관련 compatible string을 검색해 보면, 연결된 platform driver를 찾을 수 있음.

2) node@ 뒤에 붙는 숫자는 unit address로, 장치에 접근하기 위해 사용되는 1 차 주소이고, 노드
내의 reg 속성에 나열되어 있는 정보에 해당한다.
  : unit address가 필요한 이유는 동일한 장치(예: uart)가 여럿 존재할 경우, 이를 구분해가 위해서임.

3) 노드내에는 또다른 노드(자식 노드)가 올 수 있다.
  : device controller와 연결된 consumer(혹은 slave) device의 관계로 이해하면 될 듯.

4) 노드는 앞 부분에 별명(alias)을 붙일 수 있으며(예: node1), 다른 노드에서는 주로 이 별명을  활용하여 해당 노드를 참조하게 되는데, 이 때는 & 기호를 사용한다(예: &node1).
  : 별명을 사용하는 이유는, 주솟값 등이 붙어 있는 복잡한 노드에 대한 표현이 훨씬 간단해지기 때문임.
  : consumer device -> controller로의 접근을 표현(예: interrupt, clock, pinctrl 등)

5) 실세계에는 매우 다양한 장치(device)가 존재하므로, 자신만의 독자적인 device를 표현하기 위해서는 binding 문서(Documentation/devicetree/bindings)를 잘 정리해 두어야 한다(검증도 필요함).
  : 실제로 여러 dts[i] 파일 내용을 살펴 보면, 생각 보다 매우 복잡한 표현이 많이 있음.



그림 3.2 Device Tree 문법 - 기초


다음으로 device tree의 전체 구조를 살펴보기로 하자.

<Device Tree의 전체 구조 소개>
1) 확장자가 dtsi인 파일은 SoC를 표현하며, dts인 경우는 하위 보드를 표현한다.

2) 계층 구조를 유지하기 위해 하위 보드는 상위 보드 혹은 SoC의 dts를 상속 받을 수 있으며, include 문(혹은 C style의 #include 도 가능)을 사용하여 상위 보드 혹은 SoC를 위한 dts 파일을 포함시킬 수 있다(dts 파일 중간에서 포함시키는 것도 가능함).

3) 하위 보드에서 정의한 내용 중, 상위 보드의 내용과 중복되는 내용은 하위 보드에서 정의한 내용이 최종적으로 반영되며, 중복되지 않는 내용은 새로 추가(역시 반영됨)된다.
  : dts 내용을 보다 보면, 처음 부터 &node { } 로 표현된 부분이 있는데, 이는 앞서 dts[i] 파일에서 정의한 node를 overriding하는 것으로 이해하면 된다.

4) SoC의 구조를 보면, 대개 cpu, memory, system bus, system bus에 연결된 각종 device controller, device controller에 연결된 consumer device 들로 구성되어 있으므로, 이를 모두 node로 표현하면 된다.
  : ocp는 On Chip Peripheral을 의미하며, 각종 device controller를 이 부분에 나열해 주면 된다.

5) 각각의 node는 앞서 설명한 바와 같이, 다양한 속성(예: register, interrupt 등)이 있으므로 이를 적절히 표현해 주어야 하며, node 간에는 상호 연관성(예: interrupt, clock 등)이 있을 수 있으니, 역시 이를 잘 표현(&node 활용)해 주어야 한다.

그림 3.3은 dtsi 파일(SoC 용)과 이를 상속하는 dts 파일(board 용)이 하나의 dtb(실제 binary 파일이며, 편의상 text 형태로 보여주고 있음) 파일로 통합되는 과정을 보여주고 있다.

그림 3.3 Device Tree 상세 구조(1)


(다른 예이긴 하지만) 아래 그림 3.4는 imx28 SoC를 위한 device tree를 간략히 정리한 것이며, 그림 3.5는 imx28-evk 보드를 위한 device tree 파일을 정리한 것이다. arch/arm/boot/dts 디렉토리 아래에는 다양한 dts[i] 파일이 존재하므로 다양한 파일을 참조해 보는 것이 바람직할 듯 하다.

그림 3.4 Device Tree 상세세 구조(2) - SoC Device Tree



그림 3.5 Device Tree 상세 구조(3) -  Board Device Tree


Device Tree로 표현된 보드 description 내용은 커널 코드에서 참조할 수 있어야 하는데, 이와 관련 내용을 정리해 보면 아래 그림 3.6과 같다. 편의 상, 위의 그림 3.4 ~ 3.5의 내용이 아닌 일반적인 내용을 표현해 보았으나, 내용을 보면 바로 이해가 가능할 것으로 보인다. 그림 3.6에서 중요한 부분은 "vendor,soc-model1" 형태로 표현된 compatible 속성 부분으로, 위의 그림 3.5에 맞게 수정하려면, "fsl,imx28-evk" (보드 파일내의 top compatible 속성 정보)를 적어주면 된다. 그리고, 당연한 얘기지만 ,아래 코드는 arch/arm/mach-{YOURBOARD}/board_XXXX.c 파일을 구성하는 내용으로, 기존의 보드 파일에 비해 매우 간소화된 것을 알 수 있다.



그림 3.6 Device Tree 상세 구조(4) -  보드 파일 초기화 코드


다음으로 아래 그림 3.7은 apbh bus controller를 노드로 표현한 것으로, 여러가지 속성 정보와 노드들로 이루어져 있음을 알 수 있다. 이중, compatible = "simple-bus"이 좀 특별해 보이는데, "simple-bus"는 그 하위 노드(대개 device controller)가 동적으로 인식할 수 없는 장치(platform device)임을 뜻한다. 따라서, 그 아래에 기술된 여러 child node는 예전 기준으로 보면 platform device라 할 수 있으며, 이와 연결된 platform driver를 찾기 위해서는 그 node 내에 정의되어 있는 compatible 속성 정보를 활용하면 된다(불행히도 아래 예에서는 그 부분이 빠져 있음). 참고로, 아래 hsadc node의 compatible 속성을 child dts 파일에서 다른 내용으로 교체(override)한다면, 다른 platform driver가 연결되게 되므로, 이 방법을 이용한다면 자신이 만든 새로운 platform driver를 활용할 수 있게 될 것이다.

그림 3.7 Device Tree 상세 구조(5) - simple-bus


그림 3.8은 i2c0 controller 노드(아마도 위의 apbh node 내에 존재)를 표현한 것으로, compatible = " fsl,imx28-i2c" 속성을 갖고 있으며, 하위에 2개의 i2c slave 장치(sgtl5000, at24@51)가 연결되어 있음을 알 수 있다. i2c slave 장치는 반드시 address(i2c slave address) 속성을 지정해 주어야 하는데, 이는 reg = <주소> 속성을 통해서 가능하다. platform device의 경우와 비교해서 생각해 보기 바란다.


그림 3.8 Device Tree 상세 구조(6)  - i2c bus


그림 3.9는 위의 i2c0 controller에 해당하는 platform driver(drivers/i2c/busses/i2c-mxs.c)의 대략적인 모습을 보여준다. 앞서도 언급했다시피, 위의 device tree 내용과 platform driver를 연결해 주는 것은 "fsl,imx28-i2c" compatible 속성임을 알 수 있다.



그림 3.9 Device Tree 상세 구조(7)  - i2c bus controller driver 예


그림 3.10은 apbh bus에 연결된 interrupt controller(icoll)와 다른 장치(ssp0)에서 interrupt를 사용하는 방식을 표현하고 있다.
<Device Tree에서의 interrupt 표현 방식>
1) interrupt controller는 자신이 interrupt controller임을 알리기 위해 interrupt-controller; 문을 선언해 주어야 하며,
2) interrupt를 사용하는 장치에서는 자신이 사용하는 interrupt controller가 어떤 장치인지를 기술해 주어야 한다.
  : interrupt-parent = <&icoll>;
3) 또한, interrupt에 사용할 pin 번호를 지정해 주어야 한다.
  : interrupts = <96>
4) (드라이버 코드) 마지막으로 드라이버에서는 인터럽트를 등록(요청)해 주어야 한다.


그림 3.10 Device Tree 상세 구조(8) - interrupt

Device Tree에서 선언한 interrupt 부분을 driver 내에서 어떻게 사용하는지를 간략히 살펴 보면 다음과 같다. 편의상, 아래 내용은 위의 그림 3.10과는 무관한 다른 내용을 토대로 정리하였다.

<device tree 예>
  interrupts = < 0 59 1 >;
  interrupt-parent = <&gic>;

<driver code 예>
  irq = irq_of_parse_and_map(op->dev.of_node, 0);    //irq = 59가 들어옴.
  rc = request_irq(irq, xillybus_isr, 0, "xillybus", op->dev);

그림 3.11은 시스템 내에서 사용 중인 clock을 device tree로 표현한 것인데, 이를 보다 쉽게 이해하기 위해서는 SoC Technical Reference 문서를 참조하여, 전체적인 clock의 상관 관계를 먼저 파악하는 것이 필요하다.
<Device Tree에서의 clock 표현 방식>
1) 먼저 SoC 내의 clock을 모두 열거한다.
  : clock의 여러 속성, 특히 frequency 부분을 빼먹지 않는다.
2) 다음으로 각각의 장치(아래 그림 3.11에서는 cpu, timer, usb controller)에서 필요한 clock을 참조한다.
  : cpu@0의 경우, cpuclk 참조(=&cpuclk)
  : timer@20300의 경우, coreclk과 refclk 참조(= &coreclk, &refclk)
  : usb@52000의 경우, gateclk 참조(&gateclk)
3) (드라이버 코드) 마지막으로 드라이버에서는 clock을 사용하도록 요청해 주어야 한다.


그림 3.11 Device Tree 상세 구조(9) - clock(1)


그림 3.12 Device Tree 상세 구조(10) - clock(2)


Device tree 관련하여 마지막으로 살펴볼 내용은 pin control(혹은 pin muxing)에 관한 것이다. pin control(혹은 pin muxing)이 필요한 이유는 실제 패키지 밖으로 나와 있는 pin의 갯수 보다, 의도된 장치(i2c, uart, spi, gpio 등)의 수가 많아서, 이를 선택적으로 사용하기 위함이다. Device tree를 사용하기 이전에는 각각의 vendor 마다, 독자적인 방식을 사용하여 pin muxing 기능을 구현하였으나, Linus Walleij의 노력으로 linux 3.2 부터 pin control subsystem으로 통합되었다.



그림 3.13 Device Tree 상세 구조(11) - pin control subsystem(framework)


Device tree에서 pin control 관련 설정을 위해서는 크게 두단계의 과정이 필요하다.
<Device Tree에서의 pin control 표현 방식>
1) 사용하려는 pin의 목록을 정의한다(예: uart, i2c, spi 핀 등).
  : 위의 그림 3.13 처럼 mux를 기준으로 pin을 정의하는 것이 아니라, device(gpio, uart, spi, i2c 등)를 기준으로 pin을 정의하는 것임.
2) 위에서 정의한 pin을 실제 장치(예: gpio, uart)와 결합시킨다.
  : pinctrl-<number>에는 pin control 목록을 적어 주고(예: &uart0_pins_a),
  : pinctrl-names에는 pin control이름을 부여한다. 참고로, 이름이 "defaults"일 경우는 probe시 자동으로 pin control 요청을 해주게 된다.
3) (드라이버 코드) 마지막으로 드라이버 코드에서는 pin control을 요청해 주어야만 실제로 해당 pin을 원하는 용도로 사용할 수 있게 된다.

아래 그림 3.14는 leds(gpio)와 uart0 관련 pin control 설정 내용을 보여준다. 앞서 언급한 바와 같이 사용할 pin의 내역을 pinctrl@1c20800 node내에 선언한 후, 각각의 장치(uart0, leds)에서 이를 사용하기 위해 결합(pinctrl-0, pinctrl-names)하는 과정을 거치게 된다.



그림 3.14 Device Tree 상세 구조(12) - pin control(1)


그림 3.15는 BBB 보드에서 i2c0 controller를 위한 pin control 설정 과정에 대한 예를 보여주고 있다. pinctrl-single,pins 속성 부분을 제외하면, 앞서 설명한 내용과 대부분 동일한 방식으로 표현되어 있음을 알 수 있다. 이는 TI chip의 특성(drivers/pinctrl/pinctrl-single.c)과 관련된 부분으로, 이곳에는 각 핀의 <register, value> 쌍을 적어주게 된다. 참고로 아래 예에서는 register의 값으로 "(PIN_INPUT_PULLUP | MUX_MODE0)"와 같이 적어 주었으나, 실제로는 숫자 값(예: 0x70)을 적어주어야 한다. 또한 <register, value> 정보를 알기 위해서는 SoC TRM 문서를 참조해야 한다(이 부분이 조금 어렵게 느껴질 수 있다^^).

그림 3.15 Device Tree 상세 구조(13) - pin control(2)


Device Tree에서 기술한 pin control 내용은 실제로 platform driver의 probe 함수 내에서 요청(request)하는 과정을 통해서 실제로 사용할 수 있는 상태가 된다. 이는 다른 device 가령, clock, regulator 등을 사용하기 위해 get 함수를 호출해 주는 것과 같은 맥락이다. 아래 그림 3.16에서 이와 관련된 코드(pin control request)를 발췌해 보면 다음과 같다.

dev->pins = devm_pinctrl_get_select_default(&pdev->dev);

Pin control 관련 보다 구체적인 내용은 참고 문헌 [13-14]를 참조하기 바란다. 참고로, pin control 관련해서는 별도의 blog로 정리를 해 볼 예정에 있다^^.


그림 3.16 Device Tree 상세 구조(14) - pin control driver 사용 예


이상으로 Device Tree 관련하여 중요하다고 생각되는 부분만을 정리하여 보았다(빙산의 일각이기는 하지만^^). 여기까지 언급한 내용은 쉽다면 쉽고, 어렵다면 어려울 수 있겠는데, 실제로 device tree를 제대로 이해하기 위해서는 보단 많은 관련 문서 및 실제 dts[i] 파일을 검토해 보아야 할 것으로 보인다. 처음에는 한눈에 device tree의 concept가 들어오지 않을 것이나, 반복적으로 관련 글을 읽어 보고, 아래 소개하는 실제 코딩 과정을 거치다 보면, 그 의미와 사용법이 보다 명확해 질 것으로 믿는다.

끝으로, device tree 관련하여 좋은 글을 많이 정리해 주신 Free Electrons의 Thomas Petazzoni 님께 감사의 마음을 전한다 - Thomas and free electrons guys ! Thanks you so much for your great documents[5-10].


4. GPIO driver example - LED driver

이번 절에서는 device tree 기반으로 LED(GPIO) 드라이버를 어떻게 작성하는지에 관하여 설명하고자 한다. 먼저 기존의 gpio led platform device 코드를 살펴봄으로써, device tree로 전환 시, 차이점을 파악해 볼 것이며, 후반부에서는 device tree를 사용하는 gpio led platform driver 코드를 분석하므로써, 드라이버 내에서 device tree를 어떤식으로 접근하는지를 이해하도록 해 볼 것이다.

[여기서 잠깐 !] BBB의 GPIO와 Pinmux에 대해 
AM335x에는 4개의 GPIO bank(chip)가 존재하며, 각각의 GPIO bank는 32개의 GPIO 패드로 구성되어 있다. 하나의 GPIO 패드는 GPIO[chip]_[pin] 와 같이 표현되는데, 실제 사용되는 pin 번호는 아래의 산술식을 통해 계산해내야 한다.
GPIO numuber = chip * 32 + pin
예를 들어, GPIO1_21은 1*32 + 21 = 53 이므로, 이는 GPIO53을 뜻하는 것으로 이해하면 된다. 아래 예는 익히 알고 있는 gpio API를 활용하여, GPIO 53을 통해 LED를 켜는 간단한 예를 보여준다.

////////////////////////////////////////////////////////////

#define GPIO1_21    53

static int __init gpio_led_init(void)
{
    ...
    err = gpio_request(GPIO1_21, "USR 1 LED");
    gpio_direction_output(GPIO1_21, 0);
    msleep(1000);
    gpio_direction_output(GPIO1_21, 1);
    ...
}
////////////////////////////////////////////////////////////


다음으로, AM335x에서도 pin 부족 문제를 해소하기 위해 pinmux 기능을 사용한다. AM335x TRM 문서를 참조하면 알 수 있듯이, GPIO를 포함한 몇몇 device(주로, uart, i2c, mmc, spi, lcd 등)는 pinmux 기능을 사용하게 되며, 8가지 모드(mode0 ~ mode7)에 따라 서로 다른 장치의 pin이 선택되게 됨을 알 수 있다.
////////////////////////////////////////////////////////////////////////////////////////////////////////////


(다시 원래의 문제로 돌아와서)BBB에는 사용자를 위한 4개의 파란색 led(usr0 ~ usr3)가 존재하며, 각각은 GPIO1_21(=GPIO 53), GPIO1_22(GPIO 54), GPIO1_23(GPIO 55), GPIO1_24(GPIO 56)를 통해 제어되는데, 이들 핀은 모두 pinmux 설정을 통해 선택된 후에야 사용이 가능하다.




그림 4.1 Platform device 기반의 GPIO LED 코드 예


따라서, 먼저 4개의  LED(gpio)에 대한 pin mux 설정을 위한 배열을 선언하고, 이후 boneleds_init( ) 함수에서 setup_pin_mux() 함수를 호출해 줌으로써 pin muxing을 위한 초기화 작업이 진행되게 되는 것을 볼 수 있다. 참고로, pin mux 배열내에 선언된 내용은 각 pin에 대한 정보(실제로는 register 주소임 - offset 번지)와 register 값(mode7 | pin output)임을 유추할 수 있다.


static struct pinmux_config bone_pin_mux[] = {
    {"gpmc_a5.rgmii2_td0", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT}, // USR0
    {"gpmc_a6.rgmii2_tclk", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT}, // USR1
    {"gpmc_a7.rgmii2_rclk", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT}, // USR2
    {"gpmc_a8.rgmii2_rd3", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT}, // USR3
    ...
    {NULL, 0},
};

static void boneleds_init(int evm_id, int profile )
{
    ...
    setup_pin_mux(bone_pin_mux);
    err = platform_device_register(&bone_leds_gpio);
    ...
}


이미 독자 여러분이 알고 있는 바와 같이, 최종적으로 platform_device_register() 함수를 호출해 줌으로써 platform device가 등록되게 된다.


다음으로 그림 4.2는 앞서 설명한 내용을 device tree 형태로 전환한 것으로, device tree 내에서 pinmux를 표현하기 위해서는 앞서 3절의 후반부에서 설명한 바와 같이, 크게 2단계의 과정을 거치게 된다.

1) pin mux 설정을 위한 pin 목록 선언(userled_pins 노드)
  : 어떤 pin을 pin mux의 용도로 사용할지를 언급하는 것임.
2) 실제 led 장치(gpio-leds 노드)에서 pinmux에 사용할 pin을 자신과 결합시킴
   : pinctrl-names, pinctrl-0 부분 선언해 주어야 함.
   : led 노드에서 해당 gpio bank를 참조하도록 설정해 줌.
      -> gpios = <&gpio2 21 0>;
      -> gpios = <&gpio2 22 0>;
      -> gpios = <&gpio2 23 0>;
      -> gpios = <&gpio2 24 0>;
  => 참고로, 여기서 gpio1이 아니라, gpio2 노드를 참조하는 이유는, am33xx.dtsi 파일에서 gpio bank 명을 gpio1 ~ gpio4로 표현했기 때문임.


그림 4.2 Device Tree 기반의 GPIO LED 


마지막으로 device tree 기반의 LED platform driver의 구조를 분석해 봄으로써 이 절을 마무리하도록 하자. 먼저 그림 4.3 코드에서 가장 먼저 주목해야 하는 내용은 compatible = "gpio-leds" 부분이라고 말할 수 있다. 그 이유는 이 값을 통해 앞서 설명한 device tree의 gpio-leds 노드와 led platform driver가 연결되게 되기 때문이다.

<TODO - 추가 설명 필요함>



그림 4.3 GPIO LED platform driver 구조



[여기서 잠깐 !] of_XXXX( ) 함수에 대해 
우선 of는 Open Firmware를 뜻하며, driver 내에서 device tree 관련 정보를 얻어오기 위해서는 of_XXXX( ) 형태의 함수를 사용해야 한다. 이 함수들은 drivers/of 디렉토리에 구현되어 있는데, 예제를 통해서 of 함수의 사용법을 간략히 정리해 보면 다음과 같다. platform device 시절의 함수와 비교해 보면서 아래 내용을 살펴보기 바란다.

<device tree 예>
xillybus_0: xillybus@50000000 {
     compatible = "xlnx,xillybus-1.00.a";
     reg = < 0x50000000 0x1000 >;
     interrupts = < 0 59 1 >;
     interrupt-parent = <&gic>;
     xlnx,max-burst-len = <0x10>;
     xlnx,native-data-width = <0x20>;
     xlnx,slv-awidth = <0x20>;
     xlnx,slv-dwidth = <0x20>;
     xlnx,use-wstrb = <0x1>;
} ;

<platform driver 초기화 코드 예>
static struct of_device_id xillybus_of_match[] __devinitdata = {
    { .compatible = "xlnx,xillybus-1.00.a", },
    {}
};
MODULE_DEVICE_TABLE(of, xillybus_of_match);
[ ... ]
static struct platform_driver xillybus_platform_driver = {
     .probe = xilly_drv_probe,
     .remove = xilly_drv_remove,
     .driver = {
          .name = "xillybus",
          .owner = THIS_MODULE,
          .of_match_table = xillybus_of_match,
     },
};

<probe() 함수 내 - sanity checking 함수>
static int __devinit xilly_drv_probe(struct platform_device *op)
{
     const struct of_device_id *match;
     match = of_match_device(xillybus_of_match, &op->dev);    //sanity check !
     if (!match)
          return -EINVAL;
     ...

<probe() 함수 내 - resource 정보 추출 함수>
int rc = 0;
struct resource res;
void *registers;
rc = of_address_to_resource(&op->dev.of_node, 0, &res);  //resource 정보 획득
if (rc) {
     /* Fail */
}
if (!request_mem_region(res.start, resource_size(&res), "xillybus")) {
     /* Fail */
}
registers = of_iomap(op->dev.of_node, 0);    //register 정보 추출
if (!registers) {
     /* Fail */
}

<probe() 함수 내 - 인터럽트 번호  추출 함수>
irq = irq_of_parse_and_map(op->dev.of_node, 0);
rc = request_irq(irq, xillybus_isr, 0, "xillybus", op->dev);

<probe() 함수 내 - 속성 값 추출 함수>
void *ptr;
int value;
ptr = of_get_property(op->dev.of_node, "xlnx,slv-awidth", NULL);
if (!ptr) {
    /* Couldn’t find the entry */
}
value = be32_to_cpup(ptr);


//////////////////////////////////////////////////////////////////////////////////////

이렇게 해서 간략하게 나마 device tree 방식을 사용하는 GPIO LED 드라이버에 관하여 살펴 보았다.


5. i2c driver 구현 - Wii nunchuk
내용이 너무 길어져 아래에 별도로 정리하였다.

http://slowbootkernelhacks.blogspot.kr/2014/03/device-tree-i2c-programming.html

6.  UART adapter driver 구현
내용이 너무 길어져 아래에 별도로 정리하였다.

http://slowbootkernelhacks.blogspot.kr/2014/03/device-tree-uart-programming.html

7. SPI driver 구현
내용이 너무 길어져 아래에 별도로 정리하였다.

http://slowbootkernelhacks.blogspot.kr/2014/04/device-tree-spi-programming.html

8. USB driver 구현
<TODO>


9. SD/MMC driver 구현
<TODO>


최근(2017.05.26)에 작성한 아래 내용도 함께 참조해 주기 바란다.
<Yocto 환경에서의 linux 4.x device driver programming>
http://slowbootkernelhacks.blogspot.kr/2017/05/yocto-project-linux-device-driver.html

References
<BBB>
1) BeagleBone Black System Reference Manual Revision B, January 20, 2014, Gerald Coley

2) AM335x ARM® CortexTM-A8 Microprocessors(MPUs) Technical Reference Manual, Texas Instruments

3) Getting Started With BeagleBone, by Matt Richardson, Maker Media

4) http://eewiki.net/display/linuxonarm/BeagleBone+Black - Robert Nelson

<Device Tree & Overlay>
5) Device Tree for dummies.pdf - Thomas Petazzoni, Free Electrons

6) Linux kernel and driver development training Lab Book, Free Electrons

7) Linux Kernel and Driver Development Training, Free Electrons

8) Linux kernel: consolidation in the ARM architecture support, Thomas Petazzoni, Free Electrons,

9) ARM support in the Linux kernel, Thomas Petazzoni, Free Electrons

10) Your new ARM SoC Linux support check-list! Thomas Petazzoni, CLEMENT, Free Electrons,

11) Supporting 200 different expansionboards - The broken promise of device tree, CircuitCo.

12) The Device Tree: Plug and play for Embedded Linux, Eli Billauer

13) Introduction to the BeagleBone Black Device Tree, Created by Justin Cooper

<Pin Control>
14) Pin Control Sybsystem – Building Pins and GPIO from the ground up, Linus Walleij

15) PIN CONTROL OVERVIEW, Linaro Kernel Workgroup, ST-Ericsson, Linus Walleij

<Wii Nunchuk>
16) ZX-NUNCHUK(#8000339) Wii-Nunchuk interface board




댓글 21개:

  1. 정말 대단하십니다!!

    그런데 한가지 여쭙고 싶은게 있습니다.
    device driver를 작성하는 입장에서 dt 와 board 서술 방식을 두개다 지원해야 하게 할까요?

    if (pdev->dev.of_node)
    /* dt 관련 처리 */
    else
    /* 기존 방식 대로 값 가져오기.... */

    답글삭제
  2. 먼저, 제 글에 관심을 가져주셔서 감사합니다(저도 열심히 공부 중입니다^^).

    잘 아시겠지만, 현재는 device tree가 과도기 단계에 있기 때문에, 관련 device driver(platform driver가 되겠죠..)가 올라가는 보드(혹은 SoC)가 device tree 방식으로 완벽히 전환되었다면, 굳이 이전 방식의 코드를 고려할 필요는 없을 듯 보입니다.

    다만, 현재 여러 device driver의 probe 함수을 살펴 보면, 말씀하신 바와 같이 양쪽 모두를 고려한 코드가 많이 보이는데요...
    이는, 하나의 SoC에서 파생된 보드 중, 일부는 아직까지 platform device 형태로 구현되어 있고, 일부는 device tree 형태로 변경를 한 상태이기 때문에 이 둘을 모두 cover하는 코드를 만들 수 밖에 없게 된 것이지요 ...

    /* 예1: drivers/busses/i2c-omap.c 에서 발췌 */
    const struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data;
    const struct of_device_id *match;

    match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev);
    if (match) {
    /* dt 관련 처리 */
    u32 freq = 100000; /* default to 100000 Hz */

    pdata = match->data;
    dev->flags = pdata->flags;

    of_property_read_u32(node, "clock-frequency", &freq);
    /* convert DT freq value in Hz into kHz for speed */
    dev->speed = freq / 1000;
    } else if (pdata != NULL) {
    /* 기존 방식대로 처리 */
    dev->speed = pdata->clkrate;
    dev->flags = pdata->flags;
    dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
    }

    /* 예2: drivers/gpio/gpio-mxs.c 에서 발췌 */
    struct device_node *np = pdev->dev.of_node;
    ...
    if (np) {
    /* dt 관련 처리 */
    port->id = of_alias_get_id(np, "gpio");
    if (port->id < 0)
    return port->id;
    port->devid = (enum mxs_gpio_id) of_id->data;
    } else {
    /* 기존 방식대로 처리 */
    port->id = pdev->id;
    port->devid = pdev->id_entry->driver_data;
    }

    도움이 되셨나요 ?

    답글삭제
    답글
    1. 와우 이렇게나 빨리 답변을 주시고...
      넵 알겠습니다.
      지금 현 시기에 작성한다면 두개 고려한 것을 짜야 겠군요...

      삭제
  3. 작성자가 댓글을 삭제했습니다.

    답글삭제
  4. 정말 팬입니다. 안드로이드 /임베디드 리눅스 관련 업계에 일하면서 올려주신 문서와 글로 많은 도움을 얻었습니다. 개인적으로는 회사가 힘들다고 나가라는 힘든 시기를 겪고 있는데, uart 관련된 지식이 필요하여 구글링 중 또 올려주신 글에 도움을 받아 이쪽 업계를 떠나게 된다면 인사라도 드려야 할 듯 하여 글을 올립니다. 세상에는 다양한 종류의 사람이 있는데 자신이 어렵게 안 지식을 아무런 조건없이 공유해 주시는 분, 또 자신의 하고 있는 지식에 열정을 갖고 계신 분 정말 존경하고 그런 분들과 일하고 싶은 생각이 간절합니다. 좋은 학벌과 학위를 갖고 계신지는 모르지만, 웹에 올려 주신 글 만으로도 여러 개발자들에게 존경 받으셔야한다는 개인 적인 생각입니다. 만약 이 업계를 떠나지 않는다면 항상 올려주신 글 열심히 읽고 노력하는 개발자가 되겠습니다. 감사합니다.

    답글삭제
    답글
    1. 정말 감사합니다. 뭐 대단한 일을 한것도 아닌데, 이런 글까지 올려 주시니, 몸둘 바를 모르겠군요^^.
      (이런 글을 읽고 있노라면, 솔직히 제가 더 힘이 납니다).
      다시한번 감사의 말씀을 드리며, 하시는 일 모두 잘 풀리시리라 믿습니다. 저도 시간나는 대로 보다 좋은 글로 보답(?)하도록 하겠습니다. 그럼 화이팅 하세요^^.

      삭제
  5. 감사합니다. 열공하겠습니다.

    답글삭제
  6. 안녕하세요flattened device tree검색하다가
    여기까지왔어요
    제노트3하드웨어가삼성이아니고다른영어고그뒤에저게있더라구요ㅜ
    유저네임은 root라고되어있고ㅠ

    몇일삼성서비스센타에선이상업ㅇ다고하는데
    어제어플로시스템상세정보보니
    저런게나와서요

    무선업데이트한거말고는
    누구빌려준적없는데


    몇달전에도
    네이버실시간감시에서도루팅됬다고떠서
    센타가니정식폰맞대서
    돌아왔는데

    또 이러니

    삼성서비스센타직원들이눈치못채게
    하드웨어 바꿀수있는게
    가능한가요?ㅜ
    도와주세요ㅠ

    답글삭제
  7. 죄송합니다. 제가 도와드릴 수 있는 내용이 아닌것 같군요.

    답글삭제
  8. 정말 대단하시네요. 글 잘 읽겠습니다.

    답글삭제
  9. 작성자가 댓글을 삭제했습니다.

    답글삭제
  10. 안녕하세요. 질문이 있어서 댓글 남깁니다.

    제가 메모리를 64M를 사용하고 있습니다.

    Filesystem Size Used Available Use% Mounted on
    rootfs 384.0K 2 12.0K 172.0K 55% /
    /dev/root 6.3M 6.3M 0 100% /rom
    tmpfs 29.9M 644.0K 29.3M 2% /tmp
    /dev/mtdblock5 384.0K 212.0K 172.0K 55% /overlay
    overlayfs:/overlay 384.0K 212.0K 172.0K 55% /
    tmpfs 512.0K 0 512.0K 0% /dev

    보시면 알겠지만 tmpfs가용공간에 너무 많은 공간이 할당 되어 있습니다.
    그래서 추가로 어플리케이션을 올리는데 어려움이 있는데요 ..
    이것을 DTS파일에서 조정가능한지.. 가능하다면 어떤식으로 풀어나가야 될지 모르겠습니다.

    spi@b00 {
    status = "okay";

    m25p80@0 {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "en25q64";
    reg = <0 0>;
    linux,modalias = "m25p80", "en25q64";
    spi-max-frequency = <10000000>;

    partition@0 {
    label = "u-boot";
    reg = <0x0 0x30000>;
    read-only;
    };

    partition@30000 {
    label = "u-boot-env";
    reg = <0x30000 0x10000>;
    read-only;
    };

    factory: partition@40000 {
    label = "factory";
    reg = <0x40000 0x10000>;
    read-only;
    };

    partition@50000 {
    label = "firmware";
    reg = <0x50000 0x7b0000>;
    };
    };
    };

    현재 제가 사용하고있는 dts파일입니다.

    답글삭제
  11. 제가 질문을 잘못 들렸습니다..

    partition@50000 {
    label = "firmware";
    reg = <0x50000 0x7b0000>; 를 0xfb0000 로 바꿔주니
    Filesystem Size Used Available Use% Mounted on
    rootfs 384.0K 212.0K 172.0K 55% /
    /dev/root 6.3M 6.3M 0 100% /rom

    에서

    Filesystem Size Used Available Use% Mounted on
    rootfs 1.7M 220.0K 1.5M 13% /
    /dev/root 5.0M 5.0M 0 100% /rom

    로 여유공간이 생겼고 정상작동 합니다.
    그렇다면 rom의 공간을 최적화 할 수 도 있다는 것 같은데요.
    이건 어떻게 해야할지 다시 질문드립니다..

    답글삭제
  12. 좋은 글 입니다. 많은 도움이 되었습니다. 고맙습니다. =)

    답글삭제
  13. 안녕하세요
    디바이스 트리 공부하는데 한글로 이렇게 도움이 되는 자료가 없었던 것 같습니다.
    혹시 공부하셨던 자료중에 책으로 된건 없었나요?
    거의다 PDF 파일로 참조가 있던데, 책으로 공부를 하고 싶은데.. 추천해주실 만한것이 있으면 감사하겠습니다.

    답글삭제
  14. 글쎄요... device tree만 전문적으로 설명한 책(쓸만한 책)은 아직 보질 못했군요.
    최근에 작성한 내용인데, 아래 글도 함께 참고해 주시기 바랍니다(github에서 예제를 download할 수 있음).
    http://slowbootkernelhacks.blogspot.kr/2017/05/yocto-project-linux-device-driver.html

    답글삭제
  15. 안녕하세요. 한가지 더 궁금한 사항이 있습니다. 디바이스 트리에서 부팅에 대해서 헷갈리는 부분이 있어서 정확하게 이해하고 싶어 질문 드립니다.
    디바이스 트리에서의 부팅은 커널이미지(uImage)와 DTB 파일이 각각 메모리에 적재가 됩니다. 다음으로 DTB파일을 적재한 메모리 주소를 r2에 담아 커널로 전달한다.
    저는 이렇게 정리를 하였는데, dtb파일을 적재한 메모리 주소를 커널로 전달한다는 것이 어떤 의미를 가지는 것인지 잘 모르겠습니다.커널로 전달한다는 것이 위의 uImage를 말하는건가요??

    제가 초보라 아직 커널에 대한 이해도도 많이 부족한 상태에서, DT를 공부해야하는 상황이라서 이부분에대한 개념 잡기가 어렵네요.

    답글삭제
  16. 아래 내용을 참고해 보시기 바랍니다.


    ==================
    1) bootloader(예: u-boot)는 kernel image(예: uImage)와 DTB(device tree blob - binary file)를 memory(DRAM)에 올린다.
    2) (여러 과정을 거친 후) bootloader가 kernel 시작 번지로 jump하여 kernel을 실행시킨다.
    => kernel로 분기하기 전에, R2 register에 DTB의 주솟값을 넣어 준다.
    3) kernel은 부팅 과정에서 R2 register의 주솟값을 이용하여 DTB의 위치를 알게 된다.
    4) 별도의 메모리 영역에 배치된 DTB의 내용을 kernel에서 사용하기 위해서는, kernel 내의 적당한 data structure(예: list 형태)로 담아내는 과정이 필요하다.
    => device tree 이전 방식에서는 관련 내용이 kernel 내에 포함(복잡한 C code 형태)되어 있었으므로, 이 과정이 불필요했지만, DTB의 경우는 kernel과 별도로 분리되어 있는 내용(파일)이므로, 이를 kernel에서 이용하려면, kernel 내의 적당한 data structure에 담아 내는 과정이 필요할 것임.
    => 이때, memory 상의 어느 위치에 device tree blob이 위치하고 있는지를 알아내기 위해 R2 register가 쓰이게 되는 것임.
    5) 이제 구동 중인 kernel에서 device tree 내용을 실제로 사용할 수 있는 준비가 되었으니, 이를 이용하여 각각의 device driver가 구동되기 시작한다.
    ...

    답글삭제
    답글
    1. 더 세분화해서 보니 이해를 할 수 있을 것 같습니다. 답글 감사드립니다.
      좋은 하루 되십시요~!

      삭제
  17. 디바이스 드라이버를 만들어 insmod를 통해 등록까지는 진행했는데요. 디바이스 드라이버를 시험하기 위해서, Application을 만들어, 디바이스 드라이버의 함수에 접근하려면 어떻게 해야 하는지 궁금합니다.

    답글삭제