2014년 3월 28일 금요일

Device Tree 기반의 I2C programming


본 고에서는 open source hardware로 유명한 beaglebone black(BBB)을 이용하여 최신 linux kernel(3.10.x)에서 device driver를 어떻게 작성하는지에 관하여 정리해 보고자 한다.
(앞서 게재한 내용이 너무 길어져 5절부터 별도로 정리하였음^^)

목차는 다음과 같다.

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


5. i2c driver 구현 - Wii nunchuk
이번 절에서는 device tree에서 i2c1 controller를 위한 pinmux 설정 방법을 소개하고, 이어서 slave device용 wii nunchuk 디바이스 드라이버를 작성하는 방법을 소개해 보고자 한다. 시작에 앞서 linux i2c subsystem 관련 보다 자세한 사항은 참고 문서 [6-7]을 참조하기 바란다.



그림 5.1 I2C bus 예

전체적인 설명은 아래와 같이 크게 3단계로 나누어 진행할 것이다.
1) i2c1 controller pin control 선언 및 장치와의 결합
  : am335x-bone-common.dtsi, am335x-boneblack.dts 파일 수정
2) i2c1 controller driver(platform driver) 분석
  : drivers/i2c/busses/i2c-omap.c
3) i2c1 slave driver 구현 - wii nunchuck driver
  : i2c slave driver, input driver 형식


[여기서 잠깐 !] BBB의 P8, P9 확장 핀 헤더에 관하여
이번 절에서 소개하는 will nunchuk용  i2c driver를 구현하기 위해서는 BBB의 확장 핀을 활용하여야만 한다.  BBB에는 아래 그림 5.2 처럼 P8 및 P9 두개의 확장 헤더가 있으며, 각각은 46개의 핀을 꽂을 수 있도록 되어 있다. 이 두개의 확장 핀 헤더를 사용하여 다양한 주변 장치(GPIO, I2C, SPI, UART, LCD, Camera, 각종 analog 센서 등)를 연결하여 자유롭게 테스트해 볼 수가 있다.


그림 5.2 BBB P8, P9 확장 핀 헤더


P8, P9 확장 핀 중, 일부 핀의 경우는 사용자의 선택에 의해 다양한 용도로 사용이 가능한데, 이는 앞서 3절에서 설명한 pin mux 기능과 관련된 부분으로, 해당 핀을 사용자가 원하는 용도로 사용하기 위해서는 반드시 적절한 모드 값(Mode0 ~ Mode7)을 코드 내에서 지정해 주어야 한다. 그림 5.3에서는 P9의 pin 배치 정보 중 일부를 표시한 것으로, P8의 경우도 유사한 형태로 pin 배치를 가지게 된다. 보다 자세한 내용은 참고 문헌 [1]을 참고하기 바란다.

  그림 5.3 P9 확장 헤더 핀 배치도



[여기서 잠깐 !] Wii Nunchuk에 관하여

Wii Nunchuk는 익히 알고 있는 바와 같이, 게임용 조이스틱(혹은 리모콘)으로 3축(x,y,z)  가속도(accelerometer) 센서와 아날로그 조이스틱, 2개의 버튼(C, Z)이 있으며, 가격도 저렴(2만원 내외)하여, BBB에 붙여 테스트하기에 매우 용이한 제품이다. i2c를 통해 주고 받는 데이타는 그림 5.5에 있는 것 처럼, 모두 6 바이트로 각각의 의미는 조이스틱 X, Y 축 정보, 가속도 센서용 X, Y, Z(각각 10 bit - 6번째 바이트에 2bit 정보가 추가로 담겨 있음) 정보 및 C, Z 버튼의 선택 여부(0 or 1)이다. 마지막으로, nunchuk는 i2c slave 장치로 동작하기 위해 0x52의 slave 주솟값을 갖는다. Nunchuk 관련 보다 자세한 사항은 해당 datasheet를 참조하기 바란다.



그림 5.4 Wii Nunchuk[참고 문서 16 참조]


그림 5.5 Nunchuk 데이타 통신 규약[참고 문서 16 참조]


Nunchuk는 i2c 통신을 하는 만큼, BBB P9 헤더의 아래 핀에 연결하여 테스트해 보기로 한다(그림 5.6 참조).
1) P9 GND 핀: 1 혹은 2번 핀 모두 가능(1번에 연결)
2) P9 파워(DC 3.3V) 핀 : 3 혹은 4번 핀 모두 가능(4번에 연결)
3) P9 CLK 핀: 17번 핀(I2C1_SCL)
4) P9 DATA 핀:  18번 핀 (I2C1_SDA)

(*) 참고로 위의 그림 5.3을을 보면, P9 17, 18번 핀이 각각 I2C1_SCL 및 I2C1_SDA로 사용되기 위해서는 Mode2로 설정(pinmux)되어야 함을 알 수 있다.



그림 5.6 BBB P9 확장 핀에 Nunchuk 연결


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

여기까지 기본 작업에 필요한 배경 설명을 하였으니, 이제부터는 본격적으로 코딩 과정에 들어가 보도록 하자.먼저, 그림 5.7은 arch/arm/boot/dts/am335x-bone-common.dtsi 파일을 수정하여, i2c1용 pin control 설정을 추가한 내용(i2c1_pins  노드 부분)을 보여준다.


그림 5.7 i2c1_pins node 추가(pin control 선언) - am335x-bone-common.dtsi 파일 수정

다음으로 그림 5.8에서는 am33xx.dtsi에 이미 선언되어 있는 i2c1 node를 override하여 pinctrl 관련 설정 내용 및  wiichuk i2c slave device node를 추가한 모습을 보여주고 있다.

       <am33xx.dtsi 파일 내에 선언되어 있는 i2c1 node>
        i2c1: i2c@4802a000 {
            compatible = "ti,omap4-i2c";                   <표시 1>
            #address-cells = <1>;
            #size-cells = <0>;
            ti,hwmods = "i2c2"; /* TODO: Fix hwmod */
            reg = <0x4802a000 0x1000>;
            interrupts = <71>;
            status = "disabled";
        };

<참고 사항>
1) 위의 내용에는 pinctrl-<number>, pinctrl-names 부분이 없으며, 하위 노드도 존재하지 않는다. 또한 status는 "disabled"로 되어 있음을 알 수 있다.
2) 아래 그림 5.8에서는 이러한 내용이 어떻게 수정되고 있는지 주목해 보기 바란다.
  a) pinctrl-names, pinctrl-0 속성 추가함.
  b) wiichuk 하위 노드 추가함.
  c) status 속성을 "okay"로 변경(활성화시킴)
  d) 기타, 100Khz clock frequency 값 설정 부분 추가


그림 5.8 i2c1 controller에서 pinmux 결합 - am335x-boneblack.dts 파일 수정


이렇게 수정한 dts[i] 파일을 다시 compile 한 후, 생성된  dtb 파일(그림 5.9)을 적용(dtb 파일을 target 보드로 복사해 주면 됨)하여 시스템을 재 부팅하게 되면, 그림 5.10에서 처럼  nunchuk@52 노드가 정상적으로 추가되어 있음을 확인할 수 있다.

그림 5.9 kernel 및 dts[i] 파일 build 예


그림 5.10 device tree 수정 사항 확인 방법

참고로, target 보드에서 "$ dtc -I fs /proc/device-tree" 명령을 실행하면, 현재 동작 중인 device tree을 역으로 추출해 낼 수가 있다. 한번 시도해 보기 바란다. arch/arm/boot/dts 디렉토리에서 보았던 내용과는 다소 차이가 있지만, device tree 파일이 console로 출력되는 것을 알 수 있을 것이다.


이제까지는 device tree를 수정하여, i2c1 controller 관련 노드를 수정하고, i2c1 slave device노드를 하나 추가하는 부분을 살펴 보았다. 그렇다면 i2c1 controller driver의 실제 모습은 어떨까 ? 이는 drivers/i2c/busses/i2c-omap.c 파일에 담겨져 있는데, 전체 내용을 설명하는 것은 매우 방대하고 복잡할 수 있으니, probe() 함수를 위주로 간략히 분석해 보기로 하겠다. 참고로, i2c adapter(controller) 드라이버를 제대로 이해하기 위해서는 별도의 관련 서적이나 아래 문서를 참조하기 바란다.


아래 문서가 좋은 출발점이 될 수 있을 것이다(chapter 6 참조).



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

/*
 * TI OMAP I2C master mode driver
 */
[...]

/* omap i2c controller 장치 data structure 정의 */
struct omap_i2c_dev {
spinlock_t lock; /* IRQ synchronization */
struct device *dev;
void __iomem *base; /* virtual */
int irq;
int reg_shift;      /* bit shift for I2C register addresses */
struct completion cmd_complete;
struct resource *ioarea;
u32 latency; /* maximum mpu wkup latency */
void (*set_mpu_wkup_lat)(struct device *dev,
   long latency);
u32 speed; /* Speed of bus in kHz */
u32 flags;
u16 cmd_err;
u8 *buf;
u8 *regs;
size_t buf_len;
struct i2c_adapter adapter;
u8 threshold;
u8 fifo_size; /* use as flag and value
* fifo_size==0 implies no fifo
* if set, should be trsh+1
*/
u32 rev;
unsigned b_hw:1; /* bad h/w fixes */
unsigned receiver:1; /* true when we're in receiver mode */
u16 iestate; /* Saved interrupt register */
u16 pscstate;
u16 scllstate;
u16 sclhstate;
u16 syscstate;
u16 westate;
u16 errata;

struct pinctrl *pins;
};

[...]

/* omap i2c controller 초기화 함수 - 주로  clock 등 초기화 */
static int omap_i2c_init(struct omap_i2c_dev *dev)
{
   [...]
}

/* i2c adapter 데이타 송수신 관련 함수 */
static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
   [...]
}

/* data 수신 관련 함수 - interrupt handler 내에서 사용 */
static void omap_i2c_receive_data(struct omap_i2c_dev *dev, u8 num_bytes,
        bool is_rdr)
{
   [...]
}

/* data 전송 관련 함수 - interrupt handler 내에서 사용 */
static int omap_i2c_transmit_data(struct omap_i2c_dev *dev, u8 num_bytes,
        bool is_xdr)
{
   [...]
}

/* interrupt handler thread - data 송수신 interrupt 처리 */
static irqreturn_t
omap_i2c_isr_thread(int this_irq, void *dev_id)
{
   [...]
}


/* i2c adapter algorithm structure  정의 - data 전송 관련 함수 정의 */
static const struct i2c_algorithm omap_i2c_algo = {
.master_xfer = omap_i2c_xfer,
.functionality = omap_i2c_func,
};

#ifdef CONFIG_OF
static struct omap_i2c_bus_platform_data omap3_pdata = {
.rev = OMAP_I2C_IP_VERSION_1,
.flags = OMAP_I2C_FLAG_BUS_SHIFT_2,
};

static struct omap_i2c_bus_platform_data omap4_pdata = {
.rev = OMAP_I2C_IP_VERSION_2,
};

static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",    //BBB(AM335x)의 경우 이 부분에 해당함.  <표시 1> 참조
.data = &omap4_pdata,
},
{
.compatible = "ti,omap3-i2c",
.data = &omap3_pdata,
},
{ },
};
MODULE_DEVICE_TABLE(of, omap_i2c_of_match);
#endif

[...]
/* i2c controller driver probe 함수 */
static int
omap_i2c_probe(struct platform_device *pdev)
{
struct omap_i2c_dev *dev;
struct i2c_adapter *adap;
struct resource *mem;
const struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data;
struct device_node *node = pdev->dev.of_node;
const struct of_device_id *match;
int irq;
int r;
u32 rev;
u16 minor, major, scheme;
struct pinctrl *pinctrl;

/* NOTE: driver uses the static register mapping */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}

irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return irq;
}

dev = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
if (!dev) {
dev_err(&pdev->dev, "Menory allocation failed\n");
return -ENOMEM;
}

dev->base = devm_request_and_ioremap(&pdev->dev, mem);
if (!dev->base) {
dev_err(&pdev->dev, "I2C region already claimed\n");
return -ENOMEM;
}

    /* device tree를 사용하는지 확인 */
match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev);
if (match) {
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;
}

dev->pins = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(dev->pins)) {
if (PTR_ERR(dev->pins) == -EPROBE_DEFER)
return -EPROBE_DEFER;

dev_warn(&pdev->dev, "did not get pins for i2c error: %li\n",  PTR_ERR(dev->pins));
dev->pins = NULL;
}

dev->dev = &pdev->dev;
dev->irq = irq;

spin_lock_init(&dev->lock);

platform_set_drvdata(pdev, dev);
init_completion(&dev->cmd_complete);

dev->reg_shift = (dev->flags >> OMAP_I2C_FLAG_BUS_SHIFT__SHIFT) & 3;

pm_runtime_enable(dev->dev);
pm_runtime_set_autosuspend_delay(dev->dev, OMAP_I2C_PM_TIMEOUT);
pm_runtime_use_autosuspend(dev->dev);

r = pm_runtime_get_sync(dev->dev);
if (IS_ERR_VALUE(r))
goto err_free_mem;

/*
    * Read the Rev hi bit-[15:14] ie scheme this is 1 indicates ver2.
    * On omap1/3/2 Offset 4 is IE Reg the bit [15:14] is 0 at reset.
    * Also since the omap_i2c_read_reg uses reg_map_ip_* a
    * raw_readw is done.
    */
rev = __raw_readw(dev->base + 0x04);

scheme = OMAP_I2C_SCHEME(rev);
switch (scheme) {
case OMAP_I2C_SCHEME_0:
dev->regs = (u8 *)reg_map_ip_v1;
dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG);
minor = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
major = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
break;
case OMAP_I2C_SCHEME_1:
/* FALLTHROUGH */
default:
dev->regs = (u8 *)reg_map_ip_v2;
rev = (rev << 16) |
omap_i2c_read_reg(dev, OMAP_I2C_IP_V2_REVNB_LO);
minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
dev->rev = rev;
}

dev->errata = 0;

if (dev->rev >= OMAP_I2C_REV_ON_2430 &&
dev->rev < OMAP_I2C_REV_ON_4430_PLUS)
dev->errata |= I2C_OMAP_ERRATA_I207;

if (dev->rev <= OMAP_I2C_REV_ON_3430_3530)
dev->errata |= I2C_OMAP_ERRATA_I462;

if (!(dev->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s;

/* Set up the fifo size - Get total size */
s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
dev->fifo_size = 0x8 << s;

/*
       * Set up notification threshold as half the total available
       * size. This is to ensure that we can handle the status on int
 * call back latencies.
       */

dev->fifo_size = (dev->fifo_size / 2);

if (dev->rev < OMAP_I2C_REV_ON_3630)
dev->b_hw = 1; /* Enable hardware fixes */

/* calculate wakeup latency constraint for MPU */
if (dev->set_mpu_wkup_lat != NULL)
dev->latency = (1000000 * dev->fifo_size) / (1000 * dev->speed / 8);
}

/* reset ASAP, clearing any IRQs */
omap_i2c_init(dev);

    /* interrupt 요청 */
if (dev->rev < OMAP_I2C_OMAP1_REV_2)
r = devm_request_irq(&pdev->dev, dev->irq, omap_i2c_omap1_isr,
IRQF_NO_SUSPEND, pdev->name, dev);
else
r = devm_request_threaded_irq(&pdev->dev, dev->irq,
omap_i2c_isr, omap_i2c_isr_thread,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
pdev->name, dev);

if (r) {
dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
}

adap = &dev->adapter;
i2c_set_adapdata(adap, dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo;
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;

/* i2c device drivers may be active on return from add_adapter() */
adap->nr = pdev->id;
r = i2c_add_numbered_adapter(adap);   //i2c adapter로 등록
if (r) {
dev_err(dev->dev, "failure adding adapter\n");
goto err_unuse_clocks;
}

dev_info(dev->dev, "bus %d rev%d.%d at %d kHz\n", adap->nr,
                      major, minor, dev->speed);

    /* i2c adapter에 붙어 있는 child node(slave device)를 참조하여 device driver로 등록해 줌 */
of_i2c_register_devices(adap);

    /* pinmux 설정 요청 */
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(dev->dev, "unable to select pin group\n");

pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);

return 0;

err_unuse_clocks:
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
pm_runtime_put(dev->dev);
pm_runtime_disable(&pdev->dev);
err_free_mem:
platform_set_drvdata(pdev, NULL);

return r;
}

/* driver 제거시 호출되는 함수 - kernel module로 구현할 경우에만 의미 있음 */
static int omap_i2c_remove(struct platform_device *pdev)
{
struct omap_i2c_dev *dev = platform_get_drvdata(pdev);
  int ret;

platform_set_drvdata(pdev, NULL);

i2c_del_adapter(&dev->adapter);
ret = pm_runtime_get_sync(&pdev->dev);
if (IS_ERR_VALUE(ret))
return ret;

omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}

static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.owner = THIS_MODULE,
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};

/* I2C may be needed to bring up other drivers */
static int __init
omap_i2c_init_driver(void)
{
return platform_driver_register(&omap_i2c_driver);
}
subsys_initcall(omap_i2c_init_driver);

static void __exit omap_i2c_exit_driver(void)
{
platform_driver_unregister(&omap_i2c_driver);
}
module_exit(omap_i2c_exit_driver);

MODULE_AUTHOR("MontaVista Software, Inc. (and others)");
MODULE_DESCRIPTION("TI OMAP I2C bus adapter");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:omap_i2c");

코드 5.1 omap i2c bus adapter driver

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

마지막으로, Grant Likely <grant.likely@secretlab.ca>님이 작성한, nunchuk  드라이버(wiichuk.c)를 분석해 보기로 하겠다. 아래 소개하는 wiichuk.c 파일의 원문(Makefile, Kconfig 포함)은 아래 사이트에서 확인이 가능한데,

http://lwn.net/Articles/443043/

편의상 앞서 설명한 device tree의 compatible 속성과 내용을 일치시키기 위해, 원문을 아래와 같이 일부 수정하였음을 밝힌다(그 밖에도 현재 사용중인 kernel version과 맞지 않는 부분이 있어 몇가지를 추가로 수정하였다).

static const struct of_device_id wiichuck_match_table[] = {
//{ .compatible = "nintendo,nunchuck", },
        { .compatible = "nintendo,nunchuk", },
{ }
};

참고로, 아래 코드를 원할히 이해하기 위해서는 i2c client driver 작성 방법과 input subsystem의 개념을 미리 파악하고 있어야 한다. 이와 관련하여 보다 자세한 사항 역시 아래 site의 내용(pdf 파일 - chapter 6)을 참고하기 바란다.


http://www.kandroid.org/board/board.php?board=HTCDream&command=body&no=159


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

/*
 * Nintendo Nunchuck driver for i2c connection.
 *
 * Copyright (c) 2011 Secret Lab Technologies Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>

MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
MODULE_DESCRIPTION("Nintendo Nunchuck driver");
MODULE_LICENSE("GPL");

#define WIICHUCK_JOY_MAX_AXIS 220
#define WIICHUCK_JOY_MIN_AXIS 30
#define WIICHUCK_JOY_FUZZ 4
#define WIICHUCK_JOY_FLAT 8

/* wiichuck device 자료구조 선언  - input device이면서, i2c client device 임.*/
struct wiichuck_device {
struct input_polled_dev *poll_dev;
struct i2c_client *i2c_client;
int state;
};


/* input_polled_dev는 user space에서 주기적으로 값을 읽어가는 것과는 달리, work queue를 이용하여 지정된 시각(polled_inter val) 마다 값을 읽어 user space로 던져주는 방식으로,
아래 함수가 주기적으로 호출됨 */
static void wiichuck_poll(struct input_polled_dev *poll_dev)
{
struct wiichuck_device *wiichuck = poll_dev->private;
struct i2c_client *i2c = wiichuck->i2c_client;
static uint8_t cmd_byte = 0;
struct i2c_msg cmd_msg =
{ .addr = i2c->addr, .len = 1, .buf = &cmd_byte };
uint8_t b[6];
  struct i2c_msg data_msg =
{ .addr = i2c->addr, .flags = I2C_M_RD, .len = 6, .buf = b };
  int jx, jy, ax, ay, az;
bool c, z;

     /* state는 0 -> 1 -> 0 형태로 계속 toggle */
switch (wiichuck->state) {
case 0:
         /* 1 byte command 송신(to nunchuk) */
i2c_transfer(i2c->adapter, &cmd_msg, 1);
wiichuck->state = 1;
break;

case 1:
         /* 6 bytes의  data 수신(from nunchuk) */
i2c_transfer(i2c->adapter, &data_msg, 1);

jx = b[0];
jy = b[1];
ax = (b[2] << 2) & ((b[5] >> 2) & 0x3);
ay = (b[3] << 2) & ((b[5] >> 4) & 0x3);
az = (b[4] << 2) & ((b[5] >> 6) & 0x3);
z = !(b[5] & 1);
c = !(b[5] & 2);

         /* 주기적으로 joystick X/Y, 가속도 정보 X/Y/Z, C/Z 버튼 선택 정보를 userspace로 전달하며, userspace에서는 /dev/input/eventX에서 값을 읽어들이면 됨 */
input_report_abs(poll_dev->input, ABS_X, jx);
input_report_abs(poll_dev->input, ABS_Y, jy);
input_report_abs(poll_dev->input, ABS_RX, ax);
input_report_abs(poll_dev->input, ABS_RY, ax);
input_report_abs(poll_dev->input, ABS_RZ, ay);
input_report_key(poll_dev->input, BTN_C, c);
input_report_key(poll_dev->input, BTN_Z, z);
input_sync(poll_dev->input);

wiichuck->state = 0;
dev_dbg(&i2c->dev, "wiichuck: j=%.3i,%.3i a=%.3x,%.3x,%.3x %c%c\n",
jx,jy, ax,ay,az, c ? 'C' : 'c', z ? 'Z' : 'z');
break;

default:
wiichuck->state = 0;
}
}

/* input device 초기화시 호출됨 - nunchuk와 통신을 위한 준비 단계로 보면 됨. */
static void wiichuck_open(struct input_polled_dev *poll_dev)
{
struct wiichuck_device *wiichuck = poll_dev->private;
struct i2c_client *i2c = wiichuck->i2c_client;
static uint8_t data1[2] = { 0xf0, 0x55 };   //비 암호화 통신을 의미
static uint8_t data2[2] = { 0xfb, 0x00 };   //nunchuk 초기화 완료 의미
struct i2c_msg msg1 = { .addr = i2c->addr, .len = 2, .buf = data1 };
struct i2c_msg msg2 = { .addr = i2c->addr, .len = 2, .buf = data2 };

     /* nunchuk를 초기화하기 위한 정보를 전달 */
i2c_transfer(i2c->adapter, &msg1, 1);
i2c_transfer(i2c->adapter, &msg2, 1);
wiichuck->state = 0;

dev_dbg(&i2c->dev, "wiichuck open()\n");
}

/* 이 함수에서 nunchuk device를 초기화하는 작업 진행하게 됨 */
static int wiichuck_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct wiichuck_device *wiichuck;
struct input_polled_dev *poll_dev;
struct input_dev *input_dev;
int rc;

wiichuck = kzalloc(sizeof(*wiichuck), GFP_KERNEL);
if (!wiichuck)
return -ENOMEM;

     /* input polled device 용 메모리 할당 */
poll_dev = input_allocate_polled_device();
if (!poll_dev) {
rc = -ENOMEM;
goto err_alloc;
}

wiichuck->i2c_client = client;
wiichuck->poll_dev = poll_dev;

poll_dev->private = wiichuck;
poll_dev->poll = wiichuck_poll;
poll_dev->poll_interval = 50; /* Poll every 50ms */
poll_dev->open = wiichuck_open;

input_dev = poll_dev->input;
input_dev->name = "Nintendo Nunchuck";
input_dev->id.bustype = BUS_I2C;
input_dev->dev.parent = &client->dev;

/* nunchuk 드라이버에서 생성할 수 있는 이벤트 선언 */
set_bit(EV_ABS, input_dev->evbit);
set_bit(ABS_X, input_dev->absbit); /* joystick - X, Y 축*/
set_bit(ABS_Y, input_dev->absbit);
set_bit(ABS_RX, input_dev->absbit); /* accelerometer(가속도 센서) - X, Y, Z 축 */
set_bit(ABS_RY, input_dev->absbit);
set_bit(ABS_RZ, input_dev->absbit);

set_bit(EV_KEY, input_dev->evbit);
set_bit(BTN_C, input_dev->keybit); /* buttons - C/Z 버튼*/
set_bit(BTN_Z, input_dev->keybit);

input_set_abs_params(input_dev, ABS_X, 30, 220, 4, 8);
input_set_abs_params(input_dev, ABS_Y, 40, 200, 4, 8);
input_set_abs_params(input_dev, ABS_RX, 0, 0x3ff, 4, 8);
input_set_abs_params(input_dev, ABS_RY, 0, 0x3ff, 4, 8);
input_set_abs_params(input_dev, ABS_RZ, 0, 0x3ff, 4, 8);

rc = input_register_polled_device(wiichuck->poll_dev);   //input polled 장치로 등록
if (rc) {
dev_err(&client->dev, "Failed to register input device\n");
goto err_register;
}

i2c_set_clientdata(client, wiichuck);   //i2c client data 지정

return 0;

     err_register:
input_free_polled_device(poll_dev);
     err_alloc:
kfree(wiichuck);

return rc;
}

static int wiichuck_remove(struct i2c_client *client)
{
struct wiichuck_device *wiichuck = i2c_get_clientdata(client);

i2c_set_clientdata(client, NULL);
input_unregister_polled_device(wiichuck->poll_dev);
input_free_polled_device(wiichuck->poll_dev);
kfree(wiichuck);

return 0;
}

static const struct i2c_device_id wiichuck_id[] = {
{ "nunchuk", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, wiichuck_id);

#ifdef CONFIG_OF
static const struct of_device_id wiichuck_match_table[] = {
{ .compatible = "nintendo,nunchuk", },   //그림 5.7의 compatible 속성과 연결되는 부분
{ }
};
#else
#define wiichuck_match_table NULL
#endif

static struct i2c_driver wiichuck_driver = {
.driver = {
.name = "nunchuk",
.owner = THIS_MODULE,
.of_match_table = wiichuck_match_table,
},
.probe = wiichuck_probe,
.remove = wiichuck_remove,
.id_table = wiichuck_id,
};

static int __init wiichuck_init(void)
{
return i2c_add_driver(&wiichuck_driver);
}
module_init(wiichuck_init);

static void __exit wiichuck_exit(void)
{
i2c_del_driver(&wiichuck_driver);
}
module_exit(wiichuck_exit);

코드 5.2 i2c1 slave device 예 - Wii Nuchuk 드라이버

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


이상으로 간단하게 나마, BBB에서 i2c1 controller를 사용할 수 있도록 활성화시키고, 여기에 wii nunchuk slave 드라이버를 생성하여 연결시키는 방법에 관하여 소개하였다. 중간 중간에 배경이 될만한 부분이 누락되어 있어, 과연 독자 여러분에게 얼마나 도움이 되었는지 잘 모르겠다 ....


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




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이 선택되게 됨을 알 수 있다.
////////////////////////////////////////////////////////////////////////////////////////////////////////////