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

댓글 1개: