2016년 11월 30일 수요일

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

댓글 없음:

댓글 쓰기