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




댓글 2개:

  1. 0x188
    0x18c
    0x178
    0x17c


    위 내용 중 아래 부분이 의미하는 것은 무엇이고
    어느 부부을 참조해야 하는지 부탁할까요

    reg=<0x52>
    이런 값들은 어떤것을 통해 알 수 있나요?

    답글삭제
  2. 답변이 좀 늦었습니다.

    (잘 아시겠지만) I2C에 관해 잠시 되짚어 본다면 ...
    1) i2c는 master 장치(i2c controller)와 slave들로 이루어져 있으며, 이 둘 간의 통신을 위해서는 두 가닥의 선(clock, data line)이 사용됩니다.
    2) 두가닥의 선만을 사용하여, 여러 slave 장치를 연결하다 보니, 각각의 slave 장치를 구분하기 위해서는 주소(address) 값이 사용되며, 이 주솟값은 data line을 통해 전달되고, 자신의 주소에 해당하는 slave만이 통신에 응하도록 되어 있습니다.

    질문하신 내용 중, reg=<0x52> 부분은 2)에서 언급한 slave 장치의 주소를 의미하며, 관련 내용은 관련 장치의 datasheet(위의 경우는 wii nuchuck datasheet)를 참조하셔야 합니다.

    또한, 앞서 나열하신 4개의 hexa 값 들은 1)에서 언급한 clock(SDC)과 data line(SDA)과 관련한 register 주소 값으로 이 부분은 SoC user manual(이 경우는 AM335x_TRM.pdf)을 참조하셔야만 합니다. 보통 SoC 내에는 여러개의 I2C controller가 존재하게 되고, 이 중 어는 것에 실제 slave 장치를 연결해야 하는지가 결정되고 나면, 이를 찾아 적절히 기술해 주어야 합니다. 본 고의 경우는 i2c1 controller에 wii nuchuck slave device를 연결해 주고 있습니다.

    답이 되었는지요 ?

    답글삭제