2016년 11월 30일 수요일

Atmel SAMA5D3 Xplained board i2c device driver 분석

이번 시간에는, Atmel SAMA5D3 Xplained board의 device tree & kernel code를 분석해 보도록 하겠다(순서가 좀 바뀐 감이 있는데, Atmel SAMA5D3 source code를 build하고 target board에 image를 올리는 절차는 추후 정리해서 올릴 예정이니, 조금만 기다려 주기 바란다. 😋 ).

먼저 가장 만만한(?) i2c controller(master)와 i2c device(slave)를 하나 선택하여 관련 코드(device tree 및 device driver)를 분석하는 것으로 이번 blog를 시작해 볼까 한다.

<일반적인 board bring-up 절차>
a) target board를 받은 후, 회로도를 분석하고, 관련 datasheet를 살펴 본다.
b) u-boot 등 bootloader 관련 기초 작업을 진행하여, kernel이 loading될 수 있는 준비를 한다.
c) 그 다음으로 할 일이, (ARM board라면) device tree를 살펴 보고, 관련 device driver code를 찾아 보는 것이다.
d) 마지막 단계로, target board에 맞는 device tree를 하나 생성(혹은 기존 내용  수정)하고, 그에 맞는 device driver를 작성(혹은 수정)하는 순이 될 것이다.

Device Tree의 기초지식과 관련해서는 이전 blog의 내용을 먼저 참조해 볼 것을 권한다.


1. Atmel SAMA5D3 Device Tree 소개
독자 여러분이 익히 알고 있는 바와 같이, kernel source 중 arch/arm/boot/dts 디렉토리를 보면 다양한 device tree 파일이 존재하는데, 이중 SAMA5D3 Xplained board용 device tree source를 열거해 보면 다음과 같다.


sama5d36.dtsi

sama5d3.dtsi (실제 이파일 SoC dtsi file 임)
sama5d3_can.dtsi
sama5d3_gmac.dtsi
sama5d3_emac.dtsi
sama5d3_lcd.dtsi
sama5d3_mci2.dtsi
sama5d3_tcb1.dtsi
sama5d3_uart.dtsi

 ^
|
at91-sama5d3_xplained.dts (target board dts file 임)

이 중, sama5d36.dtsi는 "Atmel SAMA5D3 family SoC" 용 dtsi 파일이며, at91-sama5d3_xplained.dts는 여기서 파생(상속)된 target board 용 dts 파일임을 한눈에 알 수 있다.

참고) 위의 내용 중 검정색 글씨로 쓰여진 7개의 dtsi 파일은 주변 장치(can,m gmac, emac, lcd, mci, tcb, uart) 별 pin mux 설정 및 관련 controller 내용을 별도로 분리해 놓은 것으로, 최종 target board에서는 이를 역시 상속 받아 선별적으로 사용하면 된다.

먼저 sama5d36.dtsi 파일의 전체 구조를 살펴 보면 아래와 같다.

==============================================================
/ {
    model = "Atmel SAMA5D3 family SoC";
    compatible = "atmel,sama5d3", "atmel,sama5";
    interrupt-parent = <&aic>;

    aliases {
        serial0 = &dbgu;
        serial1 = &usart0;
        <생략>
     };

    cpus {
        #address-cells = <1>;    /* 32bit CPU */
        #size-cells = <0>;
        cpu@0 {                         /* single core */
            device_type = "cpu";
            compatible = "arm,cortex-a5";
            reg = <0x0>;
        };
    };

    pmu {
        compatible = "arm,cortex-a5-pmu";
        interrupts = <46 IRQ_TYPE_LEVEL_HIGH 0>;        /* power management unit
                                                                                             interrupt request number 46 */
    };

    memory {
        reg = <0x20000000 0x8000000>;       /* memory mapping table 참조해야 함 */
                                                                       /* 0x20000000: DDRCS 시작 번지 */
                                                                       /* 0x8000000: 128MB */
    };

    clocks {
       <생략>
    };

    sram: sram@00300000 {
        compatible = "mmio-sram";
        reg = <0x00300000 0x20000>;          /* 0x00300000: sram0 시작 번지 */
                                                                      /* 0x20000: 128KB */
    };

    ahb {                          /* advanced high performance bus - CPU, RAM, DMA 등 기술 */
        compatible = "simple-bus";        /* platform device */
        #address-cells = <1>;                  /* 32bit bus */
        #size-cells = <1>;                        /* CPU에서 직접 접근 */
        ranges;

        apb {                      /* advanced peripheral bus - 각종 주변 장치 기술 */
            compatible = "simple-bus";
            #address-cells = <1>;             /* 32bit bus */
            #size-cells = <1>;                    /* CPU에서 직접 접근 - 중간에 address  전환 불필요 */
            ranges;
            <생략 - 각종 device controller 기술>       /* 대부분 status = "disabled"로 되어 있음 */

            aic: interrupt-controller@fffff000 {
                #interrupt-cells = <3>;
                compatible = "atmel,sama5d3-aic";
                interrupt-controller;        /* interrupt controller 임을 명시 */
                reg = <0xfffff000 0x200>;        /* 0xfffff000: AIC register 번지, 0x200: 512 bytes */
                atmel,external-irqs = <47>;
            };

             <생략 - 각종 device controller 기술>
        };
    };
};
==============================================================

다음으로 at91-sama5d3_xplained.dts의 전체 구조를 살펴 보기로 하자. 대충 눈치 챗겠지만, 아래 내용은 sama5d36.dtsi의 내용을 그대로 수용하되, 차이가 나는 점만을 추가(확장)로 기술한 것으로 이해하면 될 것이다.

==============================================================
/ {
    model = "SAMA5D3 Xplained";
    compatible = "atmel,sama5d3-xplained", "atmel,sama5d3", "atmel,sama5";

    chosen {
        stdout-path = "serial0:115200n8";
    };

    memory {
        reg = <0x20000000 0x10000000>;    /* 0x20000000: DDRCS 시작 번지 */
                                                                      /* 0x10000000: 256MB */
    };

    clocks {
        slow_xtal {
            clock-frequency = <32768>;    /* 32.768 khz crystal oscillator */
        };

        main_xtal {
            clock-frequency = <12000000>;       /* 12Mhz crystal oscillator */
        };
    };

    ahb {
        apb {
            <생략 - 실제로 사용하는 device controller & device 기술>
            /* 대부분(실제 사용하는)의 device controller 상태를 okay로 표시 */

        };
        vcc_mmc0_reg: fixedregulator@0 {
            <생략>
        };
        gpio_keys {
             <생략>
        };
        leds {
               compatible = "gpio-leds";

              d2 {       /* 파란색 led */
                  label = "d2";
                  gpios = <&pioE 23 GPIO_ACTIVE_LOW>; /* PE23, conflicts with A23, CTS2 */
                  linux,default-trigger = "heartbeat";    /* led 출력 방식 : heartbeat */
              };

             d3 {       /* 빨간색 led */
                  label = "d3";
                  gpios = <&pioE 24 GPIO_ACTIVE_HIGH>;
                                                                               /* led 출력(trigger) 방식: none */
             };
        };
    };
};
==============================================================

위의 device tree 내용을 살펴 보다 보면, 군데 군데에 많은 주솟값이 사용되고 있는 것을 알 수 있다. 그렇다면 과연 이 값들의 정확한 의미는 무엇일까 ? 해답은 아래 그림 속에 있다.



<그림 1-1 SAMA5D3 memory mapping 도>

위의 그림을 보면, 일반적인 메모리 장치(SDRAM, NAND, ROM, SRAM 등)는 물론이고 각종 주변 장치 controller에도 일정한 주소가 할당되어 있음을 알 수 있는데, 이는 CPU에서 각종 device controller를 제어함에 있어 하나의 주소 체계 안에서 모두 접근이 가능함을 뜻한다(이를 memory mapped I/O 방식이라고 함).

앞선 device tree 내용 중 특별히 memory(SDRAM) 설정 내용(address, length)이 의미하는 바를 아래에 다시 정리해 보았다.

==============================================================
    memory {
        reg = <0x20000000 0x10000000>;  
    };

*-1) address 0x20000000  : DDRCS 시작 번지
*-2) length 0x10000000  : 256MB
==============================================================

참고로, 아래 그림에 의하면 SDRAM은 최대 512MB(0x20000000)까지 장착할 수 있음을 알 수 있다.



<그림 1-2 메모리(SDRAM - taget board의 경우 DDR2) 시작 번지 및 크기>



2. I2C 관련 Device Tree 분석
자, 여기까지 SAMA5D3 target board의 device tree 내용을 대충 훑어  보았으니, 이제부터는 i2c controller에 집중해 볼 차례이다.


<그림 2-1 일반적인 i2c controller & slave devices 관계도>


아래에  sama5d3.dtsi 파일(이하: SoC dts 파일)과 at91-sama5d3_xplained.dts 파일(이하: target board dts 파일)에 기술되어 있는 i2c 관련 내용을 발췌해 보았다.

먼저 아래 3개의 코드(코드 2-1 ~ 2-3)를 보면 알 수 있듯이, SAMA5D36 SoC 내에는 3개의 i2c controller가 존재한다. 주요 특징으로는 우선 memory 번지는 각각  0xf0014000, 0xf0018000, 0xf801c000 으로 지정되어 있으며, interrupt 번호는 각각 18, 19, 20(i2c controller -> AIC로 interrupt 발생시 사용하는 번호)을 사용하고 있다. 그 밖에도 dma, pinctrl(pin mux) 및 clocks을 사용하고 있음을 확인할 수 있다. 그리고, 맨 마지막에는 status 값이 모두 "disabled"로 되어 있어, 현재 사용하지 않고 있음을  알 수 있다.

<코드 2-1 sama5d3.dtsi i2c0 controller>


<코드 2-2 sama5d3.dtsi i2c1 controller>


<코드 2-3 sama5d3.dtsi i2c2 controller>


<여기서 잠깐 !>
앞서 언급한 내용 중, dma, pinctrl, clocks 관련 reference  부분을 별도로 발췌해 보면 다음과 같다. 각각의 내용이 무엇을 의미하는지에 관해서는 독자 여러분이 직접 따져 보기 바란다(여기서는 내용 전개상 생략하기로 함).
==============================================================
<dma controller 0, 1>
            dma0: dma-controller@ffffe600 {
                compatible = "atmel,at91sam9g45-dma";
                reg = <0xffffe600 0x200>;
                interrupts = <30 IRQ_TYPE_LEVEL_HIGH 0>;
                #dma-cells = <2>;
                clocks = <&dma0_clk>;
                clock-names = "dma_clk";
            };

            dma1: dma-controller@ffffe800 {
                compatible = "atmel,at91sam9g45-dma";
                reg = <0xffffe800 0x200>;
                interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>;
                #dma-cells = <2>;
                clocks = <&dma1_clk>;
                clock-names = "dma_clk";
            };
==============================================================
<i2c 관련 pin control 0, 1, 2>
                i2c0 {
                    pinctrl_i2c0: i2c0-0 {
                        atmel,pins =
                            <AT91_PIOA 30 AT91_PERIPH_A AT91_PINCTRL_NONE   /* PA30 periph A TWD0 pin, conflicts with URXD1, ISI_VSYNC */
                             AT91_PIOA 31 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* PA31 periph A TWCK0 pin, conflicts with UTXD1, ISI_HSYNC */
                    };
                };

                i2c1 {
                    pinctrl_i2c1: i2c1-0 {
                        atmel,pins =
                            <AT91_PIOC 26 AT91_PERIPH_B AT91_PINCTRL_NONE   /* PC26 periph B TWD1 pin, conflicts with SPI1_NPCS1, ISI_D11 */
                             AT91_PIOC 27 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* PC27 periph B TWCK1 pin, conflicts with SPI1_NPCS2, ISI_D10 */
                    };
                };

                i2c2 {
                    pinctrl_i2c2: i2c2-0 {
                        atmel,pins =
                            <AT91_PIOA 18 AT91_PERIPH_B AT91_PINCTRL_NONE   /* TWD2 pin, conflicts with LCDDAT18, ISI_D2 */
                             AT91_PIOA 19 AT91_PERIPH_B AT91_PINCTRL_NONE>; /* TWCK2 pin, conflicts with LCDDAT19, ISI_D3 */
                    };
                };
==============================================================
<i2c(= twi) clock 0, 1, 2>
                    twi0_clk: twi0_clk {
                        reg = <18>;
                        #clock-cells = <0>;
                        atmel,clk-output-range = <0 16625000>;
                    };

                    twi1_clk: twi1_clk {
                        #clock-cells = <0>;
                        reg = <19>;
                        atmel,clk-output-range = <0 16625000>;
                    };

                    twi2_clk: twi2_clk {
                        #clock-cells = <0>;
                        reg = <20>;
                        atmel,clk-output-range = <0 16625000>;
                    };
==============================================================

반면에 아래 3개의 코드(코드 2-4 ~ 2-6)는 target board의 i2c controller 관련 설정(override) 내용을 보여준다. 앞서 언급된 SoC dts 내용과 어떤 부분에 차이가 있는지 살펴보기로 하자.

먼저, i2c0 controller의 경우는 SoC의 그것과는 달리 pinctrl 부분만이 아래와 같이 변경되었는데, pinctrl 내용을 가만히 살펴 보니, PIOA(Parallel IO A) 30, 31번 pin은 여전히 그대로 사용하고 있으며, 마지막 option이 아래와 같이 수정된 점이 차이가 난다(아 ! 물론 status 값도 disabled에서 okay로 바뀌긴 했다).

<코드 2-4 at91-sama5d3_xplained.dts i2c0 controller>

                   <변경 내용 비교>
                   ===============================================
                    <SoC dts 내용>
                    pinctrl_i2c0: i2c0-0 {
                        atmel,pins =
                            <AT91_PIOA 30 AT91_PERIPH_A AT91_PINCTRL_NONE
                             AT91_PIOA 31 AT91_PERIPH_A AT91_PINCTRL_NONE>;
                    };

                    <target board dts 내용>
                    pinctrl_i2c0_pu: i2c0_pu {
                        atmel,pins =
                            <AT91_PIOA 30 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>,
                            <AT91_PIOA 31 AT91_PERIPH_A AT91_PINCTRL_PULL_UP>;
                    };
                    ===============================================

<참고: arch/arm/boot/dts/include/dt-bindings/pinctrl/at91.h 파일 내용 발췌>
===========================================================
#define AT91_PINCTRL_NONE       (0 << 0)
#define AT91_PINCTRL_PULL_UP        (1 << 0)
#define AT91_PINCTRL_MULTI_DRIVE    (1 << 1)
#define AT91_PINCTRL_DEGLITCH       (1 << 2)
#define AT91_PINCTRL_PULL_DOWN      (1 << 3)
#define AT91_PINCTRL_DIS_SCHMIT     (1 << 4)
#define AT91_PINCTRL_OUTPUT     (1 << 7)
#define AT91_PINCTRL_OUTPUT_VAL(x)  ((x & 0x1) << 8)
#define AT91_PINCTRL_DEBOUNCE       (1 << 16)
#define AT91_PINCTRL_DEBOUNCE_VAL(x)    (x << 17)
===========================================================

다음으로, i2c1 controller는 좀 더 복잡해 보이니, i2c2 controller를  먼저 살펴 보면, 이 역시 i2c0 대비 dmas가 추가된 것을 제외하고는 i2c0와 차이가 없어 보인다. 따라서 다음 코드로 바로 넘어가도록 하겠다.

<코드 2-5 at91-sama5d3_xplained.dts i2c2 controller>

마지막으로, i2c1 controller의 경우는 뭔가 좀더 복잡해 보인다. 가만히 살펴 보니, act8865@5b라는 pmic device가 추가되어 있음을 알 수 있다. 

"여기서 주목해 볼 부분은 i2c controller는 여러 개의 i2c device와 연결됨에 있어, USB나 PCI 처럼 dynamic(run-time)하게 연결되는 구조가 아니라, static하게 설정되는 형태(platform bus)라는 점이다. 따라서  pmic(act8865@5b)는 i2c1 controller에 static하게 연결된 i2c device로 보면 될 것이다. 그림 2-1에도 표현되어 있다시피, i2c device를 언급할 때 반드시 포함되어야 하는 부분이 i2c device의 address 값인데, 아래 코드 2-6을 보면 이 값이 0x5b임을 확인할 수 있다. 그리고 이 주솟값은 ac58865@ 뒤에 붙는 값으로 사용되기도 한다."

당연한 얘기지만, 만일 다른 pmic chip을 사용하는 상황이 발생한다면, i2c device의 address를 다른 값으로 변경(pmic datasheet 참조)해 주어야 하며, (별도로 설명은 안했지만)그 아래의 regulator 관련 부분도 적절히 변경해 주어야 할 것이다.

질문 사항) i2c1 controller의 status는 "okay"인데, pmic의 status는 "disabled"로 되어 있다. 왜 일까 ?


<코드 2-6 at91-sama5d3_xplained.dts i2c1 controller>


새로 추가된 pmic 관련 내용을 보다 제대로 이해하기 위해서는 ACT8865 datasheet 및  target board의 회로도 중 PMIC 관련 부분을 먼저 확인해 볼 필요가 있어 보인다.

참고문헌 [3]에 의하면, ACT8865는 Atmel SAMA5Dx & SAM9 시리즈에 사용되는 PMU(power management unit) 장치로, 아래와 같은 특징을 갖는다.



<그림 2-2 ACT8865 PMIC block diagram>


PMU(or PMIC) 장치의 역할은 글자 그대로 CPU(정확히는 SoC)의 power를 관리(안정적으로 유지)해 주는 것이다. 따라서 bootloader나 kernel 작업 시, PMIC와 관련해서는 i2c를 통해 DC/DC converter(REG1 ~ REG3), LDO regulator(REG4 ~ REG7)의 설정 값을 변경시켜 주는 작업을 해 주어야 할 수도 있다(실제 target board에 상황에 맞게 수정이 필요하다는 뜻).

PMU의 또 다른 역할로는, 아래 그림에서 알 수 있는 것처럼 사용자가 Push button(예: reset 버튼)을 누르면, nRSTO(ACT8865) -> NRST(SAMA5D3)를 통해 CPU 및 주변 장치를 reset 시켜주는 것이다.

그 밖의 PMU의 역할과 관련해서는 관련 datasheet(참고 문헌 [3])를 꼼꼼히 살펴 볼 필요가 있다.


<그림 2-3 SAMA5D3  회로도 중, ACT8865 관련 부분>


3. I2C 관련 Device Driver 분석
이번 절에서는 앞서 2절에서 소개한 device tree에 상응하는 platform driver 코드 즉, i2c1 controller code와  i2c1 device code(act8865 pmic)를 각각 분석해 보고자 한다.

a) i2c controller code 분석
   - drivers/i2c/busses/i2c-at91.c [TBD - 아래 코드 분석 내용 추가해야 함]

=============================================================
static const struct of_device_id atmel_twi_dt_ids[] = {
    {
        .compatible = "atmel,at91rm9200-i2c",
        .data = &at91rm9200_config,
    } , {
        .compatible = "atmel,at91sam9260-i2c",
        .data = &at91sam9260_config,
    } , {
        .compatible = "atmel,at91sam9261-i2c",
        .data = &at91sam9261_config,
    } , {
        .compatible = "atmel,at91sam9g20-i2c",
        .data = &at91sam9g20_config,
    } , {
        .compatible = "atmel,at91sam9g10-i2c",
        .data = &at91sam9g10_config,
    }, {
        .compatible = "atmel,at91sam9x5-i2c",
        .data = &at91sam9x5_config,
    }, {
        .compatible = "atmel,sama5d4-i2c",
        .data = &sama5d4_config,
    }, {
        .compatible = "atmel,sama5d2-i2c",
        .data = &sama5d2_config,
    }, {
        /* sentinel */
    }
};
MODULE_DEVICE_TABLE(of, atmel_twi_dt_ids);

static struct platform_driver at91_twi_driver = {
    .probe      = at91_twi_probe,
    .remove     = at91_twi_remove,
    .id_table   = at91_twi_devtypes,
    .driver     = {
        .name   = "at91_i2c",
        .of_match_table = of_match_ptr(atmel_twi_dt_ids),
        .pm = at91_twi_pm_ops,
    },
};

static int at91_twi_probe(struct platform_device *pdev)
{
    struct at91_twi_dev *dev;
    struct resource *mem;
    int rc;
    u32 phy_addr;
    u32 bus_clk_rate;

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

    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!mem)
        return -ENODEV;
    phy_addr = mem->start;

    dev->pdata = at91_twi_get_driver_data(pdev);
    if (!dev->pdata)
        return -ENODEV;

    dev->base = devm_ioremap_resource(&pdev->dev, mem);
    if (IS_ERR(dev->base))
        return PTR_ERR(dev->base);

    dev->irq = platform_get_irq(pdev, 0);
    if (dev->irq < 0)
        return dev->irq;

    rc = devm_request_irq(&pdev->dev, dev->irq, atmel_twi_interrupt, 0,
             dev_name(dev->dev), dev);
    if (rc) {
        dev_err(dev->dev, "Cannot get irq %d: %d\n", dev->irq, rc);
        return rc;
    }

    platform_set_drvdata(pdev, dev);

    dev->clk = devm_clk_get(dev->dev, NULL);
    if (IS_ERR(dev->clk)) {
        dev_err(dev->dev, "no clock defined\n");
        return -ENODEV;
    }
    clk_prepare_enable(dev->clk);

    if (dev->dev->of_node) {
        rc = at91_twi_configure_dma(dev, phy_addr);
        if (rc == -EPROBE_DEFER)
            return rc;
    }

    if (!of_property_read_u32(pdev->dev.of_node, "atmel,fifo-size",
                  &dev->fifo_size)) {
        dev_info(dev->dev, "Using FIFO (%u data)\n", dev->fifo_size);
    }

    rc = of_property_read_u32(dev->dev->of_node, "clock-frequency",
            &bus_clk_rate);
    if (rc)
        bus_clk_rate = DEFAULT_TWI_CLK_HZ;

    at91_calc_twi_clock(dev, bus_clk_rate);
    at91_init_twi_bus(dev);

    snprintf(dev->adapter.name, sizeof(dev->adapter.name), "AT91");
    i2c_set_adapdata(&dev->adapter, dev);
    dev->adapter.owner = THIS_MODULE;
    dev->adapter.class = I2C_CLASS_DEPRECATED;
    dev->adapter.algo = &at91_twi_algorithm;
    dev->adapter.quirks = &at91_twi_quirks;
    dev->adapter.dev.parent = dev->dev;
    dev->adapter.nr = pdev->id;
    dev->adapter.timeout = AT91_I2C_TIMEOUT;
    dev->adapter.dev.of_node = pdev->dev.of_node;

    pm_runtime_set_autosuspend_delay(dev->dev, AUTOSUSPEND_TIMEOUT);
    pm_runtime_use_autosuspend(dev->dev);
    pm_runtime_set_active(dev->dev);
    pm_runtime_enable(dev->dev);

    rc = i2c_add_numbered_adapter(&dev->adapter);
    if (rc) {
        dev_err(dev->dev, "Adapter %s registration failed\n",
            dev->adapter.name);
        clk_disable_unprepare(dev->clk);

        pm_runtime_disable(dev->dev);
        pm_runtime_set_suspended(dev->dev);

        return rc;
    }

    dev_info(dev->dev, "AT91 i2c bus driver (hw version: %#x).\n",
         at91_twi_read(dev, AT91_TWI_VER));
    return 0;
}
=============================================================

b) i2c device code 분석
  - drivers/regulator/act8865-regulator.c [TBD - 아래 코드 분석 내용 추가해야 함]

=============================================================
static const struct of_device_id act8865_dt_ids[] = {
    { .compatible = "active-semi,act8600", .data = (void *)ACT8600 },
    { .compatible = "active-semi,act8846", .data = (void *)ACT8846 },
    { .compatible = "active-semi,act8865", .data = (void *)ACT8865 },
    { }
};
MODULE_DEVICE_TABLE(of, act8865_dt_ids);

static const struct i2c_device_id act8865_ids[] = {
    { .name = "act8600", .driver_data = ACT8600 },
    { .name = "act8846", .driver_data = ACT8846 },
    { .name = "act8865", .driver_data = ACT8865 },
    { },
};

MODULE_DEVICE_TABLE(i2c, act8865_ids);
static struct i2c_driver act8865_pmic_driver = {
    .driver = {
        .name   = "act8865",
    },
    .probe      = act8865_pmic_probe,
    .id_table   = act8865_ids,
};

static int act8865_pmic_probe(struct i2c_client *client,
                  const struct i2c_device_id *i2c_id)
{
    static const struct regulator_desc *regulators;
    struct act8865_platform_data pdata_of, *pdata;
    struct device *dev = &client->dev;
    struct device_node **of_node;
    int i, ret, num_regulators;
    struct act8865 *act8865;
    unsigned long type;
    int off_reg, off_mask;
    int voltage_select = 0;

    pdata = dev_get_platdata(dev);

    if (dev->of_node && !pdata) {
        const struct of_device_id *id;

        id = of_match_device(of_match_ptr(act8865_dt_ids), dev);
        if (!id)
            return -ENODEV;

        type = (unsigned long) id->data;

        voltage_select = !!of_get_property(dev->of_node,
                           "active-semi,vsel-high",
                           NULL);
    } else {
        type = i2c_id->driver_data;
    }

    switch (type) {
    case ACT8600:
        regulators = act8600_regulators;
        num_regulators = ARRAY_SIZE(act8600_regulators);
        off_reg = -1;
        off_mask = -1;
        break;
    case ACT8846:
        regulators = act8846_regulators;
        num_regulators = ARRAY_SIZE(act8846_regulators);
        off_reg = ACT8846_GLB_OFF_CTRL;
        off_mask = ACT8846_OFF_SYSMASK;
        break;
    case ACT8865:
        if (voltage_select) {
            regulators = act8865_alt_regulators;
            num_regulators = ARRAY_SIZE(act8865_alt_regulators);
        } else {
            regulators = act8865_regulators;
            num_regulators = ARRAY_SIZE(act8865_regulators);
        }
        off_reg = ACT8865_SYS_CTRL;
        off_mask = ACT8865_MSTROFF;
        break;
    default:
        dev_err(dev, "invalid device id %lu\n", type);
        return -EINVAL;
    }

    of_node = devm_kzalloc(dev, sizeof(struct device_node *) *
                   num_regulators, GFP_KERNEL);
    if (!of_node)
        return -ENOMEM;

    if (dev->of_node && !pdata) {
        ret = act8865_pdata_from_dt(dev, of_node, &pdata_of, type);
        if (ret < 0)
            return ret;

        pdata = &pdata_of;
    }

    if (pdata->num_regulators > num_regulators) {
        dev_err(dev, "too many regulators: %d\n",
            pdata->num_regulators);
        return -EINVAL;
    }

    act8865 = devm_kzalloc(dev, sizeof(struct act8865), GFP_KERNEL);
    if (!act8865)
        return -ENOMEM;

    act8865->regmap = devm_regmap_init_i2c(client, &act8865_regmap_config);
    if (IS_ERR(act8865->regmap)) {
        ret = PTR_ERR(act8865->regmap);
        dev_err(&client->dev, "Failed to allocate register map: %d\n",
            ret);
        return ret;
    }

    if (of_device_is_system_power_controller(dev->of_node)) {
        if (!pm_power_off && (off_reg > 0)) {
            act8865_i2c_client = client;
            act8865->off_reg = off_reg;
            act8865->off_mask = off_mask;
            pm_power_off = act8865_power_off;
        } else {
            dev_err(dev, "Failed to set poweroff capability, already defined\n");
        }
    }

    /* Finally register devices */
    for (i = 0; i < num_regulators; i++) {
        const struct regulator_desc *desc = &regulators[i];
        struct regulator_config config = { };
        struct regulator_dev *rdev;

        config.dev = dev;
        config.init_data = act8865_get_init_data(desc->id, pdata);
        config.of_node = of_node[i];
        config.driver_data = act8865;
        config.regmap = act8865->regmap;

        rdev = devm_regulator_register(&client->dev, desc, &config);
        if (IS_ERR(rdev)) {
            dev_err(dev, "failed to register %s\n", desc->name);
            return PTR_ERR(rdev);
        }
    }

    i2c_set_clientdata(client, act8865);
    devm_kfree(dev, of_node);

    return 0;
}
=============================================================


4. 실제 I2C Device 추가해 보기
마지막으로 SAMA5D3 Xplained board에서 실제로 돌아가는 i2c device를 하나 선택하여, driver 작업을 진행해 봄으로써  i2c 관련 device tree 및 device driver와 관련해 보다 상세히 이해해 보도록 하자.

<TBD - 실제 i2c device를 구입한 후 관련 내용을 작성할 예정임>



Kernel이 날이 갈수록 복잡해지고 있다. 분석만이 살길임을 다시한번 느끼면서, 부족하지만 이번 글을 마무리하고자 한다.

References

1. Atmel_11121_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Datasheet.pdf
2. Atmel_11269_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Xplained_User-Guide.pdf
3. ACT8865_Datasheet.pdf(SAMAD5D3를 위한 power management unit(PMIC))


Slowboot

Atmel SAMA5D3 Xplained board GPIO

GPIO는 embedded system design 시 가장 기본이 되는 component라고 해도 무방할 정도로 널리 그리고 자주 사용된다. 이번 시간에는 Atmel SAMA5D3 Xplained board의 GPIO가 어떤 식으로 구현되어 있는지 자세히 분석해 보고자 한다.

<이번 blog에서 소개할 내용>
a) gpio device tree
b) gpio driver
   - kernel gpio framework 포함
   - gpio interrupt
   - kernel config
c) userspace(sysfs) gpio 사용법
d) led driver, gpio-keys driver 소개


1. Atmel SAMA5D3 GPIO Device Tree
그럼 먼저 sama5d3.dtsi 파일의 gpio 관련 부분을 살펴 보는 것으로 설명을 시작해 보도록 하자. 아래 내용을 보면 알 수 있듯이 SAMA5D3에는 5개의 gpio controller 즉, pioA ~ pioE가 존재한다. SAMA5D3 datasheet에 따르면, 근데 실제로는 gpio controller라기 보다는 PIO(Parallel I/O) controller가 맞는 표현이다.

============================================================
 pioA: gpio@fffff200 {
       compatible = "atmel,at91sam9x5-gpio", "atmel,at91rm9200-gpio";
       reg = <0xfffff200 0x100>;
       interrupts = <6 IRQ_TYPE_LEVEL_HIGH 1>;   /* interrupt source 임을 표시 */
       #gpio-cells = <2>;
       gpio-controller;            /* gpio controller 임을 표시 */
       interrupt-controller;    /* interrupt controller 임을 표시 - 위의 내용에 의하면
                                                interrupt source이면서, 동시에 controller이기도 함 */
       #interrupt-cells = <2>;
       clocks = <&pioA_clk>;
 };

 pioB: gpio@fffff400 {
       <생략>
 };

 pioC: gpio@fffff600 {
        <생략>
 };

 pioD: gpio@fffff800 {
        <생략>
 };석

 pioE: gpio@fffffa00 {
         <생략>
 }
============================================================

Atmel에서 말하는 PIO controller는 pin muxing과 GPIO 처리를 모두 관장하는 장치로 이해할 수 있을 듯하다. 그림 1-1에 PIO controller의 block diagram을 옮겨 보았다.

<1-1 PIO controller block diagram>

S/W 관점에서 보면 SAMA5D3 Xplained board에는 5개의 GPIO bank가 있고, 각각의 bank에는 32개의 gpio가 포함되어 있는 것으로 설명이 가능하다. 따라서 앞으로 3장에서 다시 언급하겠지만, GPIO pin 번호를 계산하고자 때에는 아래의 산술식을 사용할 수 있다.

GPIO pin number = PIO bank number * 32 + pin offset

예를 들어, PIOE 23번 pin은 위의 산술식에 의하면, 4(PIOE) * 32 + 23 = 151이 된다(참고: PIOA bank number는 1이 아니고 0임).



<1-2 PIO 5 banks>

끝으로 위의 device tree 내용 중 몇가지 사항을 추가로 정리해 보면 다음과 같다.
a) gpio controller는 main interrupt controller 입장에서 볼때, interrupt source에 해당한다.
b) 또한, gpio controller에 연결되는 주변 장치 관점에서 볼 때는 interrupt controller로 볼 수도 있다.
c) gpio controller는 아래와 같은 peripheral clock을 사용한다.
                    ============================
                    pioA_clk: pioA_clk {
                        #clock-cells = <0>;
                        reg = <6>;
                    };
                    ============================


2. GPIO 관련 Device Driver 분석
1절의 device tree에 있는 compatible string  "atmel,at91sam9x5-gpio" 으로 device drivers 디렉토리를 검색해 보면, 아래 파일을 쉽게 찾을 수 있다.
drivers/pinctrl/pinctrl-at91.c

이제 부터는 이 코드의 probe 함수를 분석해 보도록 하자.

==============================================================
/* This structure is replicated for each GPIO block allocated at probe time */
static const struct gpio_chip at91_gpio_template = {
    .request        = gpiochip_generic_request,
    .free           = gpiochip_generic_free,
    .get_direction      = at91_gpio_get_direction,
    .direction_input    = at91_gpio_direction_input,
    .get            = at91_gpio_get,
    .direction_output   = at91_gpio_direction_output,
    .set            = at91_gpio_set,
    .set_multiple       = at91_gpio_set_multiple,
    .dbg_show       = at91_gpio_dbg_show,
    .can_sleep      = false,
    .ngpio          = MAX_NB_GPIO_PER_BANK,
};

static const struct of_device_id at91_gpio_of_match[] = {
    { .compatible = "atmel,at91sam9x5-gpio", .data = &at91sam9x5_ops, },
    { .compatible = "atmel,at91rm9200-gpio", .data = &at91rm9200_ops },
    { /* sentinel */ }
};

static int at91_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct resource *res;
    struct at91_gpio_chip *at91_chip = NULL;
    struct gpio_chip *chip;
    struct pinctrl_gpio_range *range;
    int ret = 0;
    int irq, i;
    int alias_idx = of_alias_get_id(np, "gpio");   /* gpio bank 번호 알아내기 */
    uint32_t ngpio;
    char **names;

    BUG_ON(alias_idx >= ARRAY_SIZE(gpio_chips));
    if (gpio_chips[alias_idx]) {
        ret = -EBUSY;
        goto err;
    }

    irq = platform_get_irq(pdev, 0);   /* interrupt request number 구하기 */
    if (irq < 0) {
        ret = irq;
        goto err;
    }

    /* struct at91_gpio_chip 영역 할당 */
    at91_chip = devm_kzalloc(&pdev->dev, sizeof(*at91_chip), GFP_KERNEL);
    if (!at91_chip) {
        ret = -ENOMEM;
        goto err;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   /* gpio bank address 구하기 */
    at91_chip->regbase = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(at91_chip->regbase)) {
        ret = PTR_ERR(at91_chip->regbase);
        goto err;
    }

    /* at91_chip 주요 field 내용 채우기 */
    at91_chip->ops = (struct at91_pinctrl_mux_ops *)
        of_match_device(at91_gpio_of_match, &pdev->dev)->data;
    at91_chip->pioc_virq = irq;
    at91_chip->pioc_idx = alias_idx;

    at91_chip->clock = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(at91_chip->clock)) {
        dev_err(&pdev->dev, "failed to get clock, ignoring.\n");
        ret = PTR_ERR(at91_chip->clock);
        goto err;
    }

    ret = clk_prepare_enable(at91_chip->clock);   /* gpio controller 관련 clock 준비 */
    if (ret) {
        dev_err(&pdev->dev, "failed to prepare and enable clock, ignoring.\n");
        goto clk_enable_err;
    }

    at91_chip->chip = at91_gpio_template;   /* atmel 용 struct gpio_chip API 대입 */

    /* struct gpio_chip의 각 field 채우기 */
    chip = &at91_chip->chip;
    chip->of_node = np;
    chip->label = dev_name(&pdev->dev);
    chip->parent = &pdev->dev;
    chip->owner = THIS_MODULE;
    chip->base = alias_idx * MAX_NB_GPIO_PER_BANK;

    if (!of_property_read_u32(np, "#gpio-lines", &ngpio)) {
        if (ngpio >= MAX_NB_GPIO_PER_BANK)
            pr_err("at91_gpio.%d, gpio-nb >= %d failback to %d\n",
                   alias_idx, MAX_NB_GPIO_PER_BANK, MAX_NB_GPIO_PER_BANK);
        else
            chip->ngpio = ngpio;
    }

    names = devm_kzalloc(&pdev->dev, sizeof(char *) * chip->ngpio,
                 GFP_KERNEL);

    if (!names) {
        ret = -ENOMEM;
        goto clk_enable_err;
    }

    for (i = 0; i < chip->ngpio; i++)
        names[i] = kasprintf(GFP_KERNEL, "pio%c%d", alias_idx + 'A', i);

    chip->names = (const char *const *)names;

    range = &at91_chip->range;
    range->name = chip->label;
    range->id = alias_idx;
    range->pin_base = range->base = range->id * MAX_NB_GPIO_PER_BANK;

    range->npins = chip->ngpio;
    range->gc = chip;

    ret = gpiochip_add(chip);   /* gpio chip 등록 */
    if (ret)
        goto gpiochip_add_err;

    gpio_chips[alias_idx] = at91_chip;
    gpio_banks = max(gpio_banks, alias_idx + 1);

    ret = at91_gpio_of_irq_setup(pdev, at91_chip);    /* gpio interrupt controller setup */
    if (ret)
        goto irq_setup_err;

    dev_info(&pdev->dev, "at address %p\n", at91_chip->regbase);

    return 0;

irq_setup_err:
    gpiochip_remove(chip);
gpiochip_add_err:
clk_enable_err:
    clk_disable_unprepare(at91_chip->clock);
err:
    dev_err(&pdev->dev, "Failure %i for GPIO %i\n", ret, alias_idx);

    return ret;
}

static struct platform_driver at91_gpio_driver = {
    .driver = {
        .name = "gpio-at91",
        .of_match_table = at91_gpio_of_match,
    },
    .probe = at91_gpio_probe,
};

static struct platform_driver * const drivers[] = {
    &at91_gpio_driver,
    &at91_pinctrl_driver,
};

static int __init at91_pinctrl_init(void)
{
    return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
}
arch_initcall(at91_pinctrl_init);
==============================================================

다음 내용은 kernel gpio framework에서 제공하는 API 함수들을 열거한 것이다. device driver에서 GPIO를 제어하고자 할 경우에는 아래 함수들을 적절히 조합하여 사용하면 된다.

#include <linux/gpio.h>
int gpio_is_valid(int number);
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
/* GPIO INPUT:  return zero or nonzero */
int gpio_get_value(unsigned gpio);

/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
int gpio_cansleep(unsigned gpio);
/* GPIO INPUT:  return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);

/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
/* request GPIO, returning 0 or negative errno.
 * non-null labels may be useful for diagnostics.
 */
int gpio_request(unsigned gpio, const char *label);

/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
<example>
err = gpio_request(30, "sample_name");
gpio_direction_input(30);
gpio_export(30, true);
gpio_get_value(30);
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);

/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
<example>
irq_num = gpio_to_irq(30)
request_irq(irq_num, handler, 0, "gpio_test", NULL);
set_irq_type(irq_num, IRQ_TYPE_EDGE_RISING);
free_irq(irq_num, NULL);
gpio_free(30);
<TBD> 위의 API를 적절히 이용한 제대로 된 sample code 추가 !

3. Userspace(sysfs) GPIO 사용법
<TBD>


4. led driver and gpio-keys driver 소개
<TBD>


(2주 전에 정리를 했던 부분인데...) 추가 정리가 좀 더 필요하지만, 일단 blog에 게시하고자 한다.


References

1. http://www.wiki.xilinx.com/Linux+GPIO+Driver

2. http://michaelhleonard.com/gpio-interface-access-conventions-on-linux/



Slowboot

2016년 11월 19일 토요일

Atmel SAMA5D3 Xplained board를 기반으로 OpenVPN 구축하기

Atmel(www.at91.com, www.atmel.com)에서 SAMA5D3 Xplained board를 하나 구입했다.




이번 blog에서는 SAMA5D3 Xplained board를 기반으로 OpenVPN 서버를 구축(on OpenWrt)하는 전 과정을 상세히 다루어 보고자 한다. 그 과정에서 u-boot, linux kernel, device tree 관련 주요 코드도 함께 소개해 보도록 하겠다.

<앞으로 소개할 내용>
1. Atmel SAMA5D3 board 소개
2. SAMA5D3 source download & build 하기
   2.1 AT91 Bootstrap
   2.2 U-boot bootloader
   2.3 Linux kernel (with device tree)
   2.4 Yocto build system
   2.5 Buildroot build system
   2.6 SAM-BA tool(firmware writer) 사용법 소개
3. 주요 소스 코드 분석
   3.1 u-boot bootloader
   3.2 device tree
   3.3 linux kernel(device drivers)
4. OpenWrt source download & build 하기
   4.1 OpenWrt build 방법 소개
   4.2 SAMA5D3 NFS booting
   4.3 SAMA5D3 NAND booting
5. OpenVPN Server porting
   5.1 OpenVPN 소개
   5.2 Server porting
   5.3 동작 시험
6. IoT Gateway or Sensor로 활용하기[시간이 된다면]
   6.1 다양한 sensor를 붙여 보기(Python or C)
   6.2 LTE를 이용한 IoT Gateway 만들기


오늘은 그 첫번째 시간으로 SAMA5D3 Xplained board를 가볍게 훑어 보기로 하자.

제 1 장. Atmel SAMA5D3 Xplained board 개요

아래 그림이 Atmel SAMA5D3 Xplained board가 되겠다.



<Atmel SAMA5D3 Xplained board>


<SAMA5D3 Xplained board의 장점>
a) 최신 kernel을 사용하고 있으며, device tree를 지원한다.
b) 문서가 매우 잘 정리되어 있어 혼자서 따라하는데 전혀 무리가 없다.
c) build system으로 Yocto, BuildRoot가 모두 고려되어 있다.
d) SAM-BA라는 훌륭한 firmware image writing tool을 제공하고 있다.
(*) embedded linux를 master하고 싶은 독자라면 강추하고 싶다. 👍









<SAMA5D3 Architecture>


SAMA5D3 Xplained board를 활용할 수 있는 분야로는 아래와 같은 영역을 생각해 볼 수 있단다.
high performance portable handheld applications such as human-machine interfaces, control panels, smart grid infrastructures, network gateways, M2M systems, 2D barcode scanners, barcode printers, machine vision equipment, as well as home and commercial building automations, POS terminals, medical devices and white goods.
위에는 언급되어 있지 않지만, 요즘 유행하는 IoT gateway로도 충분히 활용 가능하다.

하지만, 본 blog에서는 이런 영역보다는 SAMA5D3 board를 OpenVPN gateway(security gateway)로 활용해 보고자 한다. 그 이유는 현재 OpenVPN에 꽂혀(?) 있기 때문이다. 😎 

또한, build system으로는 Atmel에서 기본적으로 소개하고 있는 Yocto나 BuildRoot 보다는 gateway 계의 android격이라고 할 수 있는 OpenWrt를 활용해 봄으로써, VPN, Firewall, Wi-Fi AP, LTE Router, Open vSwitch(OpenFlow) 등으로도 쉽게 전환가능하도록 해 볼 예정이다.


아래 그림은 SAMA5D3 Xplained board의 functional block도이다. 자세한 사항은 아래 원문을 참고하기 바란다.

The SAMA5D3 Xplained board is built around the SAMA5D36, a Cortex-A5 application processor which combines high-performance computing device with low-power consumption and a wide range of communication peripherals.
It features a combination of user interface functionalities and high data rate IOs, including LCD controller, touchscreen, camera interface, Gigabit and 10/100 Ethernet ports, high-speed USB and SDIO.
The ARM Cortex-A5 supports the latest generation of DDR2 and NAND Flash memory interfaces for program and data storage. An internal 166-MHz multi-layer bus architecture associated with 24 DMA channels and two 64-Kbyte SRAM blocks, sustains the high bandwidth required by the processor and the high-speed peripherals.



<Atmel SAMA53 Xplained board block diagram>


부팅 순서는 아래와 같이 요약해 볼 수 있다. 즉, Boot ROM -> AT91 Bootstrap loader -> U-Boot bootloader -> Linux kernel 순으로 부팅이 진행된다.


<Boot Sequence>

아래 그림은 NAND flash를 사용할 경우의 파티션 map과 NAND flash(nand flash로 국한하지는 않음)에 firmware image를 write할 때 사용하는 tool(SAM-BA)을 보여준다.


<NAND flash memory map>


<SAM-BA - firmware image writing tool>



부팅 sequence 및 NAND 파티션과 관련해서는 앞으로 소개할 3장에서 자세히 살펴 보기로 한다.

다음 그림은 SAMA5D3 Xplained board에 console 케이블을 연결한 모습 및 TeraTerm을 사용하여 실제로 target board에 login한 모습을 각각 보여준다.


<FTDI console cable 연결>

<TeraTerm 화면 - 115200, 8N1 설정>


SAMA5D3 Xplained board를 보다 자세히 이해하기 위해서는 회로도를 분석해 볼 필요가 있는데, 이와 관련해서는 아래 파일을 참조해 보시기 바란다(이 내용도 별도로 정리해 게재하고자 했으나, 저작권 문제가 있을 듯 보여 추가로 정리하지 않기로 함).

"Atmel_11269_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Xplained_User-Guide.pdf"


마지막으로 SAMA5D3 Xplained board를 위한 Linux, Android, Windows 개발 page를 소개하고 첫번째 글을 마치고자 한다.

http://www.at91.com/android4sam/bin/view/Android4SAM/

http://www.at91.com/windows4sam/bin/view/Windows4SAM/


<References>
2. Atmel documents
   - Atmel_11270_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Xplained_Getting-Started.pdf
   - Atmel_11269_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Xplained_User-Guide.pdf
   -  Atmel_11121_32-bit-Cortex-A5-Microcontroller_SAMA5D3-Datasheet.pdf

Slowboot

2016년 11월 17일 목요일

Embedded Linux 교육 자료

일전에 작성해 두었던 embedded linux 교육 자료입니다.
(Raspberry Pi2와 Embedded Linux Primer Vol2 by Hallinan 책을 근간으로 하여 작성함)









자세한 사항은 아래 파일을 참조하시길 ...




Slowboot

2014년 4월 4일 금요일

Linux Kernel Hacks History by Slowboot


Until now(~ 2014.3) ...

I've tried to analyze Linux kernel & device drivers only for Android until now. As the result, the book(not commercial version) which is called to "Android Kernel Hacks" has been created for you and me.
1) Android Overview
2) Kernel Programming Techniques
3) ARM Board Initialization based on ARM Device Tree
4) Power Management Techniques
5) Some Device Drivers Programming for Smart Devices
6) Kernel Debugging Techniques

<chapter 1 ~ chapter 7>
or

After that, I started to summarize some topics for ARM Device Tree using BeagleBone Black which is opensource hardware based on TI AM335x SoC.

1) Device Tree Details

2) How to write GPIO & pin control driver on BBB

3) How to write I2C driver on BBB

4) How to write UART driver on BBB

5) How to write SPI driver on BBB


So, from now on ...

I'll analyze and summarize for some topics below.

1. IIO(Industrial I/O) Subsystem - Framework for ADC, Analog sensors
  - See the "IIO, a new kernel subsystem Maxime Ripard, Free Electrons" file.
  - drivers/iio
2. USB framework - About OTG, Gadget etc..
  - See the "Bootstrap Yourself with Linux-USB Stack: Design, Develop, Debug, and Validate Embedded USB, Rajaram Regupathy" book.
  - drivers/usb
3. MMC framework
  - See the "Block device drivers, Thomas Petazzoni, Free Electrons" file.
  - drivers/mmc
4. GPIO/Regulator Framework, Common Clock Framework - 2nd summarization
  - userspace GPIO control
  - "device enable(power control)" related !
  - pin control & GPIO
  - See the "Linux PSP GPIO Driver Guide.pdf" file.
5. V4L2 Framework
  - See the "Media controller Goals, architecture and roadmap, V4L2 Helsinki Summit 2010-06-14, Laurent Pinchart" file.
  - driver/media
6. PWM Framework
  - drivers/pwm
7. Buildroot
  - This is not related to Linux kernel/device drivers but, I'll study it as a recent build system for embedded linux.
  - See the "buildroot.pdf, Thomas Petazzoni, Free Electrons" file.

Slowboot