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