GPIO Subsystem -2-

 

GPIO Controller 디바이스 드라이버

최근에는 GPIO 디바이스 드라이버가 독립적으로 작성되지 않고, Pin Controller 디바이스 드라이버 내부에 Pin Control 디바이스 드라이버와 GPIO 디바이스 드라이버 코드가 동시에 포함되어 구현되는 추세이다.

 

드라이버 시작

GPIO 컨트롤러가 어떤 버스뒤에 붙어 있는지에 따라서 다음 어느 형태의 드라이버 뒤에 붙일지 고려해야 한다.

  • 플랫폼 드라이버
    • module_platform_driver() 또는 platform_driver_register()
  • pci 드라이버
    • module_pci_driver()
  • i2 드라이버
    • module_i2c_driver() 또는 i2c_add_driver()

 

broadcom ns2 소스 예

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

static struct platform_driver iproc_gpio_driver = {
        .driver = {
                .name = "iproc-gpio",
                .of_match_table = iproc_gpio_of_match,
        },
        .probe = iproc_gpio_probe,
};

static int __init iproc_gpio_init(void)
{
        return platform_driver_register(&iproc_gpio_driver);
}
arch_initcall_sync(iproc_gpio_init);

 

GPIO 디바이스 등록

broadcom ns, ns2, stingray 칩 계열의 gpio controller를 등록한다. gpio 내부에서 인터럽트 처리도 하므로 관련된 irq_chip도 준비하여 등록한다.

iproc_gpio_probe()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c -1/2-

static int iproc_gpio_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct resource *res;
        struct iproc_gpio *chip;
        struct gpio_chip *gc;
        u32 ngpios, pinconf_disable_mask = 0;
        int irq, ret;
        bool no_pinconf = false;

        /* NSP does not support drive strength config */
        if (of_device_is_compatible(dev->of_node, "brcm,iproc-nsp-gpio"))
                pinconf_disable_mask = BIT(IPROC_PINCONF_DRIVE_STRENGTH);
        /* Stingray does not support pinconf in this controller */
        else if (of_device_is_compatible(dev->of_node,
                                         "brcm,iproc-stingray-gpio"))
                no_pinconf = true;

        chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
        if (!chip)
                return -ENOMEM;

        chip->dev = dev;
        platform_set_drvdata(pdev, chip);

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        chip->base = devm_ioremap_resource(dev, res);
        if (IS_ERR(chip->base)) {
                dev_err(dev, "unable to map I/O memory\n");
                return PTR_ERR(chip->base);
        }

        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
        if (res) {
                chip->io_ctrl = devm_ioremap_resource(dev, res);
                if (IS_ERR(chip->io_ctrl)) {
                        dev_err(dev, "unable to map I/O memory\n");
                        return PTR_ERR(chip->io_ctrl);
                }
        }

        if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) {
                dev_err(&pdev->dev, "missing ngpios DT property\n");
                return -ENODEV;
        }
  • 코드 라인 12~13에서 “brcm,iproc-nsp-gpio” gpio 드라이버를 사용하는 칩(ns, ns2)은 drive-strength를 설정할 수 없다.
  • 코드 라인 15~17에서 “brcm,iproc-stingray-gpio” 드라이버를  사용하는 칩(stingray)은 pinconf를 사용할 수 없다.
  • 코드 라인 19~24에서 벤더의 gpio chip 정보를 구성하기 위한 구조체를 할당하고 이 구조체를 플랫폼 디바이스와 연결한다.
  • 코드 라인 26~40에서 첫 번째와 두 번째 플랫폼 리소스를 알아와서 io 매핑한다.
  • 코드 라인 42~45에서 디바이스 트리 노드의 “ngpios” 속성을 읽어 전체 gpio 핀 수를 알아온다.

 

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c -2/2-

        raw_spin_lock_init(&chip->lock);

        gc = &chip->gc;
        gc->base = -1;
        gc->ngpio = ngpios;
        chip->num_banks = (ngpios + NGPIOS_PER_BANK - 1) / NGPIOS_PER_BANK;
        gc->label = dev_name(dev);
        gc->parent = dev;
        gc->of_node = dev->of_node;
        gc->request = iproc_gpio_request;
        gc->free = iproc_gpio_free;
        gc->direction_input = iproc_gpio_direction_input;
        gc->direction_output = iproc_gpio_direction_output;
        gc->set = iproc_gpio_set;
        gc->get = iproc_gpio_get;

        chip->pinmux_is_supported = of_property_read_bool(dev->of_node,
                                                        "gpio-ranges");

        ret = gpiochip_add_data(gc, chip);
        if (ret < 0) {
                dev_err(dev, "unable to add GPIO chip\n");
                return ret;
        }

        if (!no_pinconf) {
                ret = iproc_gpio_register_pinconf(chip);
                if (ret) {
                        dev_err(dev, "unable to register pinconf\n");
                        goto err_rm_gpiochip;
                }

                if (pinconf_disable_mask) {
                        ret = iproc_pinconf_disable_map_create(chip,
                                                         pinconf_disable_mask);
                        if (ret) {
                                dev_err(dev,
                                        "unable to create pinconf disable map\n");
                                goto err_rm_gpiochip;
                        }
                }
        }

        /* optional GPIO interrupt support */
        irq = platform_get_irq(pdev, 0);
        if (irq) {
                ret = gpiochip_irqchip_add(gc, &iproc_gpio_irq_chip, 0,
                                           handle_simple_irq, IRQ_TYPE_NONE);
                if (ret) {
                        dev_err(dev, "no GPIO irqchip\n");
                        goto err_rm_gpiochip;
                }

                gpiochip_set_chained_irqchip(gc, &iproc_gpio_irq_chip, irq,
                                             iproc_gpio_irq_handler);
        }

        return 0;

err_rm_gpiochip:
        gpiochip_remove(gc);

        return ret;
}
  • 코드 라인 1~15에서 gpiochip에 대한 스핀락을 획득한 채로 gpiochip에 대한 정보 및 오퍼레이션 후크 함수들을 지정한다.
    • num_banks는 총 지원되는 gpio 핀 수를 32개 단위로 round-up한 수이다.
      • 예) ngpios=66 -> num_banks=3
    • 후크 함수 6개가 지정되었고, 그 중 (*request) 및 (*free) 후크 함수는 pin controller의 pinmux와 연계시켰다.
  • 코드 라인 17~18에서 gpio 디바이스 노드에서 “gpio-ranges” 속성이 발견되면 pinmux와의 연동이 있도록 gpiochip 멤버 pinmux_is_supported를 설정한다.
  • 코드 라인 20~24에서 gpiochip을 등록한다.
  • 코드 라인 26~42에서 pinconf가 지원되는 칩(ns, ns2)인 경우 pin controller(&iproc_pctrl_ops, &iproc_pconf_ops 오퍼레이션) 구현들을 등록시킨다.
    • pinconf 설정 항목 중 지원되지 않는 것들을 위해 disable 항목들을 매핑한다. pinconf가 지원하는 항목은 다음과 같다.
      • drive-strength (ns, ns2는 지원하지 않으므로 disable 매핑시킨다.)
      • bias-disable
      • bias-pull-up
      • bias-pull-down
  • 코드 라인 45~56에서 플랫폼 데이터를 통해 parent irq를 알아온다. gpio용 interrupt controller 기능에 대응하려고 전역에 준비해둔 iproc_gpio_irq_chip을 등록하고 parent irq에 체인으로 연결한다.

 

GPIO Controller 등록(gpio_chip)

아래 그림은 두 개의 gpio 핀을 갖는 gpio controller를 등록할 때의 모습을 보여준다.

 

gpiochip_add_data()

drivers/gpio/gpiolib.c -1/4-

/**
 * gpiochip_add_data() - register a gpio_chip
 * @chip: the chip to register, with chip->base initialized
 * @data: driver-private data associated with this chip
 *
 * Context: potentially before irqs will work
 *
 * When gpiochip_add_data() is called very early during boot, so that GPIOs
 * can be freely used, the chip->parent device must be registered before
 * the gpio framework's arch_initcall().  Otherwise sysfs initialization
 * for GPIOs will fail rudely.
 *
 * gpiochip_add_data() must only be called after gpiolib initialization,
 * ie after core_initcall().
 *
 * If chip->base is negative, this requests dynamic assignment of
 * a range of valid GPIOs.
 *
 * Returns:
 * A negative errno if the chip can't be registered, such as because the
 * chip->base is invalid or already associated with a different chip.
 * Otherwise it returns zero as a success code.
 */
int gpiochip_add_data(struct gpio_chip *chip, void *data)
{
        unsigned long   flags;
        int             status = 0;
        unsigned        i;
        int             base = chip->base;
        struct gpio_device *gdev;

        /*
         * First: allocate and populate the internal stat container, and
         * set up the struct device.
         */
        gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
        if (!gdev)
                return -ENOMEM;
        gdev->dev.bus = &gpio_bus_type;
        gdev->chip = chip;
        chip->gpiodev = gdev;
        if (chip->parent) {
                gdev->dev.parent = chip->parent;
                gdev->dev.of_node = chip->parent->of_node;
        }

#ifdef CONFIG_OF_GPIO
        /* If the gpiochip has an assigned OF node this takes precedence */
        if (chip->of_node)
                gdev->dev.of_node = chip->of_node;
#endif

        gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL);
        if (gdev->id < 0) {
                status = gdev->id;
                goto err_free_gdev;
        }
        dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
        device_initialize(&gdev->dev);
        dev_set_drvdata(&gdev->dev, gdev);
        if (chip->parent && chip->parent->driver)
                gdev->owner = chip->parent->driver->owner;
        else if (chip->owner)
                /* TODO: remove chip->owner */
                gdev->owner = chip->owner;
        else
                gdev->owner = THIS_MODULE;
  • 코드 라인 37~46에서 gpio_device 구조체를 할당한 후 인자로 전달받은 gpiochip을 연결시킨다.
  • 코드 라인 48~52에서 chip에 있는 디바이스 노드 정보도 gpio_device에 연결한다.
  • 코드 라인 53~57에서 gpio_device에 사용할 id는 Radix tree로 관리하는 IDA를 통해 할당받는다.
    • 처음 시작 시 0번 부터 할당받는다.
  • 코드 라인 58에서 gpio 디바이스의 이름으로 “gpiochip<id>”를 사용한다.
    • 처음 시작 시 gpio controller 디바이스는 “gpiochip0” 이라는 이름을 갖는다.
  • 코드 라인 59~67에서 gpio 디바이스의 초기 설정을 준비한다.

 

drivers/gpio/gpiolib.c -2/4-

        gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);
        if (!gdev->descs) {
                status = -ENOMEM;
                goto err_free_gdev;
        }

        if (chip->ngpio == 0) {
                chip_err(chip, "tried to insert a GPIO chip with zero lines\n");
                status = -EINVAL;
                goto err_free_descs;
        }

        if (chip->label)
                gdev->label = kstrdup(chip->label, GFP_KERNEL);
        else
                gdev->label = kstrdup("unknown", GFP_KERNEL);
        if (!gdev->label) {
                status = -ENOMEM;
                goto err_free_descs;
        }

        gdev->ngpio = chip->ngpio;
        gdev->data = data;

        spin_lock_irqsave(&gpio_lock, flags);

        /*
         * TODO: this allocates a Linux GPIO number base in the global
         * GPIO numberspace for this chip. In the long run we want to
         * get *rid* of this numberspace and use only descriptors, but
         * it may be a pipe dream. It will not happen before we get rid
         * of the sysfs interface anyways.
         */
        if (base < 0) {
                base = gpiochip_find_base(chip->ngpio);
                if (base < 0) {
                        status = base;
                        spin_unlock_irqrestore(&gpio_lock, flags);
                        goto err_free_label;
                }
                /*
                 * TODO: it should not be necessary to reflect the assigned
                 * base outside of the GPIO subsystem. Go over drivers and
                 * see if anyone makes use of this, else drop this and assign
                 * a poison instead.
                 */
                chip->base = base;
        }
        gdev->base = base;

        status = gpiodev_add_to_list(gdev);
        if (status) {
                spin_unlock_irqrestore(&gpio_lock, flags);
                goto err_free_label;
        }

        spin_unlock_irqrestore(&gpio_lock, flags);
  • 코드 라인 1~11에서 gpio controller가 다룰 gpio 개수 만큼 gpio 디스크립터를 생성한다.
  • 코드 라인 13~20에서 gpio 디바이스 이름으로 gpiochip에 부여된 label 명을 복제하여 사용한다. 만일 지정되지 않은 경우 “unknown”이라는 이름을 사용한다.
  • 코드 라인 22~23에서 gpio 디바이스에 gpio 개수와 인자로 전달받은 custom gpio data 정보를 연결한다.
  • 코드 라인 25~57에서 gpio_lock 스핀 락을 획득한 채로 이 gpiochip이 다루는 gpio 개수만큼 들어갈 수 있는 공간의 시작 번호인 base 번호를 알아온다. 그런 후 글로벌 gpiochip 리스트에 추가한다.
    • 공간을 찾을 때 gpio 번호들이 gpio controller 간에 중첩되지 않도록 한다.

 

drivers/gpio/gpiolib.c -3/4-

        for (i = 0; i < chip->ngpio; i++) {
                struct gpio_desc *desc = &gdev->descs[i];

                desc->gdev = gdev;
                /*
                 * REVISIT: most hardware initializes GPIOs as inputs
                 * (often with pullups enabled) so power usage is
                 * minimized. Linux code should set the gpio direction
                 * first thing; but until it does, and in case
                 * chip->get_direction is not set, we may expose the
                 * wrong direction in sysfs.
                 */

                if (chip->get_direction) {
                        /*
                         * If we have .get_direction, set up the initial
                         * direction flag from the hardware.
                         */
                        int dir = chip->get_direction(chip, i);

                        if (!dir)
                                set_bit(FLAG_IS_OUT, &desc->flags);
                } else if (!chip->direction_input) {
                        /*
                         * If the chip lacks the .direction_input callback
                         * we logically assume all lines are outputs.
                         */
                        set_bit(FLAG_IS_OUT, &desc->flags);
                }
        }

#ifdef CONFIG_PINCTRL
        INIT_LIST_HEAD(&gdev->pin_ranges);
#endif

        status = gpiochip_set_desc_names(chip);
        if (status)
                goto err_remove_from_list;

        status = gpiochip_irqchip_init_valid_mask(chip);
        if (status)
                goto err_remove_from_list;

        status = of_gpiochip_add(chip);
        if (status)
                goto err_remove_chip;

        acpi_gpiochip_add(chip);

        /*
         * By first adding the chardev, and then adding the device,
         * we get a device node entry in sysfs under
         * /sys/bus/gpio/devices/gpiochipN/dev that can be used for
         * coldplug of device nodes and other udev business.
         * We can do this only if gpiolib has been initialized.
         * Otherwise, defer until later.
         */
        if (gpiolib_initialized) {
                status = gpiochip_setup_dev(gdev);
                if (status)
                        goto err_remove_chip;
        }
        return 0;
  • 코드 라인 1~30에서 gpiochip이 다루는 gpio 개수 만큼 gpio 디스크립터를 초기화한다.
    • gpio controller가 HW 레지스터를 읽어 현재 해당 gpio 핀의 입/출력 모드를 읽어와서 FLAG_IS_OUT 플래그 비트에 설정한다.
      • direction 방향은 0=출력, 1=입력이다.
    • 만일 controller가 direction을 알아오는 기능이 없는 경우 출력 모드로 가정하고 FLAG_IS_OUT 플래그 비트를 설정한다.
  • 코드 라인 32~34에서 만일 커널에서 pin controller 기능이 설정된 경우 pin controller에서 요청한 gpio_range가 담길 리스트인 pin_ranges를 초기화한다.
    • gpiochip_add_pin_range() 함수 및 gpiochip_add_pingroup_range() 함수에서 추가된다.
  • 코드 라인 36~38에서 gpiochip에 등록되어 있는 gpio 이름들(gc->names)을 gpio 디스크립터 이름에 차례로 부여한다.
    • 중복되는 이름은 경고 메시지가 출력된다.
  • 코드 라인 40~42에서 gpiochip이 다루는 gpio의 모든 핀이 irq가 동작 가능한 상태로 irq valid mask를 할당하고 이들 비트들을 모두 valid 상태로 설정한다.
  • 코드 라인 44~46에서 gpio controller 오퍼레이션을 포함한 gpio_chip 구조체를 등록하고 디바이스 트리의 gpio controller 노드에서 각종 속성들을 파싱하여 처리한다. 디바이스 트리 노드가 없으면 성공(0) 결과로 다음 처리를 계속 한다.
  • 코드 라인 48에서 acpi_gpio를 할당 후 gpio controller 오퍼레이션을 포함한 gpio_chip 구조체를 등록하고 ACPI 테이블을 스캔 후 각종 속성들을 파싱하여 처리한다. ACPI가 지원되지 않는 경우 아무것도 처리하지 않는다.
  • 코드 라인 58~62에서 gpiolib가 초기화된 경우 gpiochip을 캐릭터 디바이스로 등록하고 sysfs bus에 노출시킨다.
    • core_initcall(gpiolib_dev_init) 함수를 통해 GPIO sysfs bus에 gpio 캐릭터 디바이스가 등록된 후에 전역 변수 gpiolib_initialized가 true로 설정된다.

 

drivers/gpio/gpiolib.c -4/4-

err_remove_chip:
        acpi_gpiochip_remove(chip);
        gpiochip_free_hogs(chip);
        of_gpiochip_remove(chip);
        gpiochip_irqchip_free_valid_mask(chip);
err_remove_from_list:
        spin_lock_irqsave(&gpio_lock, flags);
        list_del(&gdev->list);
        spin_unlock_irqrestore(&gpio_lock, flags);
err_free_label:
        kfree(gdev->label);
err_free_descs:
        kfree(gdev->descs);
err_free_gdev:
        ida_simple_remove(&gpio_ida, gdev->id);
        /* failures here can mean systems won't boot... */
        pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__,
               gdev->base, gdev->base + gdev->ngpio - 1,
               chip->label ? : "generic");
        kfree(gdev);
        return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add_data);

 

gpiochip_get_desc()

drivers/gpio/gpiolib.c

/**
 * gpiochip_get_desc - get the GPIO descriptor corresponding to the given
 *                     hardware number for this chip
 * @chip: GPIO chip
 * @hwnum: hardware number of the GPIO for this chip
 *
 * Returns:
 * A pointer to the GPIO descriptor or %ERR_PTR(-EINVAL) if no GPIO exists
 * in the given chip for the specified hardware number.
 */
struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip,
                                    u16 hwnum)
{
        struct gpio_device *gdev = chip->gpiodev;

        if (hwnum >= gdev->ngpio)
                return ERR_PTR(-EINVAL);

        return &gdev->descs[hwnum];
}

인자로 전달받은 gpiochip에 있는 gpio 디스크립터 배열에서 hwnum 인덱스 위치의 gpio 디스크립터를 반환한다.

 

gpiochip_setup_dev()

drivers/gpio/gpiolib.c

static int gpiochip_setup_dev(struct gpio_device *gdev)
{
        int status;

        cdev_init(&gdev->chrdev, &gpio_fileops);
        gdev->chrdev.owner = THIS_MODULE;
        gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);

        status = cdev_device_add(&gdev->chrdev, &gdev->dev);
        if (status)
                return status;

        chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
                 MAJOR(gpio_devt), gdev->id);

        status = gpiochip_sysfs_register(gdev);
        if (status)
                goto err_remove_device;

        /* From this point, the .release() function cleans up gpio_device */
        gdev->dev.release = gpiodevice_release;
        pr_debug("%s: registered GPIOs %d to %d on device: %s (%s)\n",
                 __func__, gdev->base, gdev->base + gdev->ngpio - 1,
                 dev_name(&gdev->dev), gdev->chip->label ? : "generic");

        return 0;

err_remove_device:
        cdev_device_del(&gdev->chrdev, &gdev->dev);
        return status;
}

gpiochip을 캐릭터 디바이스로 등록하고 sysfs bus에 노출시킨다.

 

gpio 디바이스 관리

gpio 디바이스의 gpio 번호 대역을 관리하기 위한 방법이 있다.

  • gpio 디바이스마다 gpio 번호 대역이 있다. 시작 번호에 해당하는 base와 개수에 해당하는 ngpio이다.
  • gpio 디바이스에서 사용하는 gpio 번호 대역은 가능하면 가장 우측부터 사용된다.

 

gpiochip_find_base()

drivers/gpio/gpiolib.c

/* dynamic allocation of GPIOs, e.g. on a hotplugged device */
static int gpiochip_find_base(int ngpio)
{
        struct gpio_device *gdev;
        int base = ARCH_NR_GPIOS - ngpio;

        list_for_each_entry_reverse(gdev, &gpio_devices, list) {
                /* found a free space? */
                if (gdev->base + gdev->ngpio <= base)
                        break;
                else
                        /* nope, check the space right before the chip */
                        base = gdev->base - ngpio;
        }

        if (gpio_is_valid(base)) {
                pr_debug("%s: found new base at %d\n", __func__, base);
                return base;
        } else {
                pr_err("%s: cannot find free range\n", __func__);
                return -ENOSPC;
        }
}

아키텍처 지원 gpio 최대 개수가 사용할 수 있는 최대 범위(512 또는 벤더가 지정한 값)내에서 gpio 디바이스들이 사용하지 않는 gpio 번호 대역을 피해 가장 우측 번호에 들어갈 수 있는 시작 번호를 반환한다.

  • 코드 라인 5에서 사용할 수 있는 gpio 시작 번호를 찾기 위해 base 값에 아키텍처 지원 gpio 최대 개수에서 인자로 전달받은 ngpio 수 만큼 뺀다.
    • 아키텍처 지원 gpio 최대 개수는 특별히 지정되지 않은 경우 512개를 사용한다.
  • 코드 라인 7~14에서 전역 gpio 디바이스 리스트에서 gpio 디바이스를 순회하며 순회 중인 gpio 디바이스가 사용하는 gpio 범위가  base보다 작은 경우 즉, 우측에 공간이 있는 경우 루프를 벗어나서 그 base 값으로 함수를 빠져나간다. 그렇지 않은 경우 순회 중인 디바이스가 사용하는 base 번호에서 ngpio 수만큼 빼고 계속 순회한다.
    • 예) gpio 디바이스가 440~472까지 32개의 gpio를 사용 중에 추가로 ngpio=32개 요청한 경우 -> base 값은 480이다.
    • 예) gpio 디바이스가 450~482까지 32개의 gpio를 사용 중에 추가로 ngpio=32개 요청한 경우 -> base 값은 418이다.
    • 예) 등록된 gpio 디바이스가 없는 상태에서 처음 32개를 요구하면 반환되는 base 값은 512 – 32 = 480이다.

 

gpiodev_add_to_list()

gpio 디바이스를 전역 gpio 디바이스 리스트에 추가할 때 base 번호 순으로 정렬되도록 끼워 넣는다.

drivers/gpio/gpiolib.c

/*
 * Add a new chip to the global chips list, keeping the list of chips sorted
 * by range(means [base, base + ngpio - 1]) order.
 *
 * Return -EBUSY if the new chip overlaps with some other chip's integer
 * space.
 */
static int gpiodev_add_to_list(struct gpio_device *gdev)
{
        struct gpio_device *prev, *next;

        if (list_empty(&gpio_devices)) {
                /* initial entry in list */
                list_add_tail(&gdev->list, &gpio_devices);
                return 0;
        }

        next = list_entry(gpio_devices.next, struct gpio_device, list);
        if (gdev->base + gdev->ngpio <= next->base) {
                /* add before first entry */
                list_add(&gdev->list, &gpio_devices);
                return 0;
        }

        prev = list_entry(gpio_devices.prev, struct gpio_device, list);
        if (prev->base + prev->ngpio <= gdev->base) {
                /* add behind last entry */
                list_add_tail(&gdev->list, &gpio_devices);
                return 0;
        }

        list_for_each_entry_safe(prev, next, &gpio_devices, list) {
                /* at the end of the list */
                if (&next->list == &gpio_devices)
                        break;

                /* add between prev and next */
                if (prev->base + prev->ngpio <= gdev->base
                                && gdev->base + gdev->ngpio <= next->base) {
                        list_add(&gdev->list, &prev->list);
                        return 0;
                }
        }

        dev_err(&gdev->dev, "GPIO integer space overlap, cannot add chip\n");
        return -EBUSY;
}

 

GPIO 오퍼레이션

각 벤더들은 GPIO controller 디바이스 드라이버를 작성하기 위해 gpio controller에 대한 operation 함수를 작성하여야 한다.

  • gpio operation에 대한 항목들은 아래 그림에서 좌측 후크 함수들이 있는 곳에 해당한다.
  • 추가로 IRQ 처리가 필요한 경우 irq chain 연결이 필요하다 이에 대한 구현은 다음과 같다.
    • 첫 번째, gpio controller 디바이스 드라이버의 시작 지점인 probe 함수에서 gpio용 irq controller의 operation 을 구성해야 한다. 따라서 irq_chip을 구성하고 gpio_chip_irqchip_add() 함수를 사용하여 등록한다.
    • 두 번째 gpiochip_set_chained_irqchip() 함수를 사용하여 상위 IRQ controller와 연동해야 한다. 연동 시 상위에 연결한 parent irq 번호를 알고 있어야 한다.

include/linux/gpio/driver.h

struct gpio_chip {
        ...
        int     (*request)(struct gpio_chip *chip, unsigned offset);
        void    (*free)(struct gpio_chip *chip, unsigned offset);
        int     (*get_direction)(struct gpio_chip *chip, unsigned offset);
        int     (*direction_input)(struct gpio_chip *chip, unsigned offset);
        int     (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
        int     (*get)(struct gpio_chip *chip, unsigned offset);
        void    (*set)(struct gpio_chip *chip, unsigned offset, int value);
        void    (*set_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
        int     (*set_config)(struct gpio_chip *chip, unsigned offset, unsigned long config); 
        int     (*to_irq)(struct gpio_chip *chip, unsigned offset);
        void    (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
        ...
}
  • (*request)
    • pin과 관련하여 HW에 먼저 activation 설정이 필요한 경우에 구현한다.
      • gpio 핀의 사용을 위해 먼저 파워 설정이나 sleep 관련 등의 HW 설정이 필요한 경우이다.
    • pin controller와 연동된 경우 pinmux를 통해 gpio를 먼저 선택하게 요청한다.
      • 해당 gpio 핀이 항상 gpio function으로 고정된 경우 이 후크는 구현할 필요 없다.
  • (*free)
    • 위 (*request) 후크에서 설정한 것을 deactivation이 필요한 경우에 구현한다.
  • (*get_direction)
    • offset에 해당하는 gpio 핀이 입력(0) 모드인지 출력(0) 모드인지 HW로부터 알아온다.
  • (*direction_input)
    • offset에 해당하는 gpio 핀을 input 모드로 HW 설정한다.
  • (*direction_output)
    • gpio 핀을 output 모드로 HW 설정하고 출력 값을 기록한다.
  • (*get)
    • gpio 핀에 대한 시그널(high=1, low=0, 에러=-1) 값을 읽어온다. (보통 input 모드에서 사용)
  • (set)
    • gpio 핀에 대한 시그널 값을 기록한다. (보통 output 모드에서 사용)
  • (*set_multiple)
    • 여러 개의 gpio 핀들에 대한 값을 기록한다. 단 mask에 해당하는 핀들만 기록한다. (보통 output 모드에서 사용)
  • (*set_config)
    • offset에 해당하는 gpio 핀에 대한 각종 HW 설정이 필요한 경우 구현한다.
    • pin controller의 pinconf와 연동되어 사용하는 경우가 많다.
  • (*to_irq)
    • offset에 해당하는 gpio 핀이 어떠한 irq로 매핑되어 있는지 알아와야 할 필요가 있을 때 구현한다.
    • 정적(static) 매핑을 사용하지 않고 동적(dynamic) 매핑을 사용하는 경우 구현한다.
    • 예) 절전 모드에서 매핑이 변경되는 구성
  • (*dbg_show)
    • debugfs를 통해 출력하고 싶은 내용이 있는 경우 구현한다.
    • 예) pullup/pulldown 설정 상태

 

broadcom ns2 소스 예

iproc_gpio_request()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

/*
 * Request the Iproc IOMUX pinmux controller to mux individual pins to GPIO
 */
static int iproc_gpio_request(struct gpio_chip *gc, unsigned offset)
{
        struct iproc_gpio *chip = gpiochip_get_data(gc);
        unsigned gpio = gc->base + offset;

        /* not all Iproc GPIO pins can be muxed individually */
        if (!chip->pinmux_is_supported)
                return 0;

        return pinctrl_request_gpio(gpio);
}

offset에 해당하는 gpio 핀을 pin controller의 pinmux를 통해 선택한다

  • pinmux_is_supported
    • gpio controller 노드에서 “gpio-ranges” 속성을 사용하여 pin controller와 연동된 경우 설정된다.

 

iproc_gpio_direction_input()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

static int iproc_gpio_direction_input(struct gpio_chip *gc, unsigned gpio)
{
        struct iproc_gpio *chip = gpiochip_get_data(gc);
        unsigned long flags;

        raw_spin_lock_irqsave(&chip->lock, flags);
        iproc_set_bit(chip, IPROC_GPIO_OUT_EN_OFFSET, gpio, false);
        raw_spin_unlock_irqrestore(&chip->lock, flags);

        dev_dbg(chip->dev, "gpio:%u set input\n", gpio);

        return 0;
}

gpio 레지스터를 통해 gpio 번호에 해당하는 gpio 핀을 input 모드로 설정한다.

 

iproc_gpio_set()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

static void iproc_gpio_set(struct gpio_chip *gc, unsigned gpio, int val)
{
        struct iproc_gpio *chip = gpiochip_get_data(gc);
        unsigned long flags;

        raw_spin_lock_irqsave(&chip->lock, flags);
        iproc_set_bit(chip, IPROC_GPIO_DATA_OUT_OFFSET, gpio, !!(val));
        raw_spin_unlock_irqrestore(&chip->lock, flags);

        dev_dbg(chip->dev, "gpio:%u set, value:%d\n", gpio, val);
}

gpio 레지스터를 읽어와서 gpio 번호에 해당하는 비트만을 val 값으로 변경하여 gpio 레지스터에 기록한다.

  • gpio 번호는 0~31번까지 사용 가능

 

iproc_set_bit()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

/**
 *  iproc_set_bit - set or clear one bit (corresponding to the GPIO pin) in a
 *  Iproc GPIO register
 *
 *  @iproc_gpio: Iproc GPIO device
 *  @reg: register offset
 *  @gpio: GPIO pin
 *  @set: set or clear
 */
static inline void iproc_set_bit(struct iproc_gpio *chip, unsigned int reg,
                                  unsigned gpio, bool set)
{
        unsigned int offset = IPROC_GPIO_REG(gpio, reg);
        unsigned int shift = IPROC_GPIO_SHIFT(gpio);
        u32 val;

        val = readl(chip->base + offset);
        if (set)
                val |= BIT(shift);
        else
                val &= ~BIT(shift);
        writel(val, chip->base + offset);
}

gpio 레지스터를 읽어와서 gpio 번호에 해당하는 비트만을 변경하여 다시 gpio 레지스터에 기록한다.

  • gpio 번호는 0~31번까지 사용 가능

 

iproc_gpio_get()

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

static int iproc_gpio_get(struct gpio_chip *gc, unsigned gpio)
{
        struct iproc_gpio *chip = gpiochip_get_data(gc);
        unsigned int offset = IPROC_GPIO_REG(gpio,
                                              IPROC_GPIO_DATA_IN_OFFSET);
        unsigned int shift = IPROC_GPIO_SHIFT(gpio);

        return !!(readl(chip->base + offset) & BIT(shift));
}

gpio 레지스터를 읽어와서 gpio 번호에 해당하는 비트 값을 반환한다. (low=0, high=1)

  • gpio 번호는 0~31번까지 사용 가능

 

gpio 디스크립터에 연결된 irq 번호 얻기

gpiod_to_irq()

drivers/gpio/gpiolib.c

/**
 * gpiod_to_irq() - return the IRQ corresponding to a GPIO
 * @desc: gpio whose IRQ will be returned (already requested)
 *
 * Return the IRQ corresponding to the passed GPIO, or an error code in case of
 * error.
 */
int gpiod_to_irq(const struct gpio_desc *desc)
{
        struct gpio_chip *chip;
        int offset;

        /*
         * Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics
         * requires this function to not return zero on an invalid descriptor
         * but rather a negative error number.
         */
        if (!desc || IS_ERR(desc) || !desc->gdev || !desc->gdev->chip)
                return -EINVAL;

        chip = desc->gdev->chip;
        offset = gpio_chip_hwgpio(desc);
        if (chip->to_irq) {
                int retirq = chip->to_irq(chip, offset);

                /* Zero means NO_IRQ */
                if (!retirq)
                        return -ENXIO;

                return retirq;
        }
        return -ENXIO;
}
EXPORT_SYMBOL_GPL(gpiod_to_irq);

인자로 주어진 gpio 디스크립터에 해당하는 gpio 핀에 연결된 irq 번호를 알아온다.

  • gpio 디스크립터에 해당하는 gpiochip이 없으면 -EINVAL 에러를 반환한다.
  • 연결된 irq가 없는 경우 -ENXIO 에러를 반환한다.

 

상위 Interrupt 컨트롤러와의 Cascade 연동

인터럽트 컨트롤러간 Cascade 연동에는 다음 두 가지 방법 중 하나를 사용해야 한다.

  • chained irq
    • 인터럽트 핸들러가 irq context에서 동작한다.
    • 인터럽트 핸들러에서 하위 장치(gpio) 레지스터를 호출할 때 느린 api를 사용하면 안된다. (blocking api 금지)
    • SoC 내부에 연결된 gpio를 연결할 때 사용된다.
    • PCI  버스뒤에 연결된 gpio도 사용할 수 있다. (pci는 빠르므로)
  • nested irq
    • 인터럽트 핸들러가 process context에서 동작한다. 즉 RT 스레드가 wakeup 된다.
    • 인터럽트 핸들러에서 하위 장치(gpio) 레지스터를 호출할 때 느린 api를 사용할 수 있다.
    • 주로 i2c 나 spi 버스 뒤에 gpio 같은 장치가 연결되어 사용할 때 사용한다.

 

다음 그림은 gpio controller 디바이스 드라이버의 초입인 probe 함수에서 chained irq 연계를 위한 함수 호출 과정을 보여준다.

 

gpio contoller에서 등록할 irq_chip에 연계되는 구조체들의 연결을 보여준다.

  • irq 연결에 대한 로지컬 다이어그램은 아래 그림의 좌측 하단을 참고한다.

 

gpiochip용 irq 도메인 오퍼레이션

gpiochip용 irq 도메인 오퍼레이션은 전역에 선언된 &gpiochip_domain_ops를 사용하며 다음 후크 함수들이 고정되어 사용된다.

static const struct irq_domain_ops gpiochip_domain_ops = {
        .map    = gpiochip_irq_map,
        .unmap  = gpiochip_irq_unmap,
        /* Virtually all GPIO irqchips are twocell:ed */
        .xlate  = irq_domain_xlate_twocell,
};
  • gpiochip_irq_map()
    • gpiochip에 주어진 irqchip과 irq 핸들러 함수를 해당 irq 디스크립터에 설정한다.
  • gpiochip_irq_unmap()
    • irq 디스크립터에 설정해둔 irqchip과 irq 핸들러 함수를 null을 대입하여 클리어한다.
  • irq_domain_xlate_twocell()
    • 첫 번째 argument를 변환 없이 그대로 hwirq로 사용
    • 두 번째 argument를 변환 없이 그대로 타입으로 사용

 

gpio_chip과 irq_chip을 한꺼번에 처리할 수 있는 gpio_irq_chip 구조체와  gpiochip_add_irqchip() 함수도 커널 v4.15-rc1에 소개되었다.

 

gpiochip_irqchip_add()

include/linux/gpio/driver.h

static inline int gpiochip_irqchip_add(struct gpio_chip *gpiochip,
                       struct irq_chip *irqchip,
                       unsigned int first_irq,
                       irq_flow_handler_t handler,
                       unsigned int type)
{
    return gpiochip_irqchip_add_key(gpiochip, irqchip, first_irq,
                    handler, type, false, NULL);
}

gpio에서 사용하는 irq에 대한 컨트롤을 위해 gpiochip에 irqchip을 추가한다.

 

gpiochip_irqchip_add_key()

gpio에서 사용하는 irq에 대한 컨트롤을 위해 gpiochip에 irqchip을 추가한다.

  • 네 번째 인자로 전달받은 handler 함수는 종종 미리 정의된 irq core 함수를 사용한다. 대부분 아래 네 가지 중 하나를 사용한다.
    • handle_edge_irq()
    • handle_level_irq()
    • handle_simple_irq() – common case
    • handle_bad_irq()
  • 다섯 번째 인자로 전달받은 타입으로 irqchip의 타입이 설정된다.
    • 대부분 IRQ_TYPE_NONE 값을 사용하고, 이러한 경우 어떠한 값도 HW에 설정하지 않는다.
  • 여섯 번째 인자는 handle_nested_irq() 함수에서 호출된 경우 true를 받는다.
  • 일곱 번째 인자는 lockdep을 사용하는 경우에만 전달되며, 사용하지 않는 경우 null을 받는다.

drivers/gpio/gpiolib.c

/**
 * gpiochip_irqchip_add_key() - adds an irqchip to a gpiochip
 * @gpiochip: the gpiochip to add the irqchip to
 * @irqchip: the irqchip to add to the gpiochip
 * @first_irq: if not dynamically assigned, the base (first) IRQ to
 * allocate gpiochip irqs from
 * @handler: the irq handler to use (often a predefined irq core function)
 * @type: the default type for IRQs on this irqchip, pass IRQ_TYPE_NONE
 * to have the core avoid setting up any default type in the hardware.
 * @nested: whether this is a nested irqchip calling handle_nested_irq()
 * in its IRQ handler
 * @lock_key: lockdep class
 *
 * This function closely associates a certain irqchip with a certain
 * gpiochip, providing an irq domain to translate the local IRQs to
 * global irqs in the gpiolib core, and making sure that the gpiochip
 * is passed as chip data to all related functions. Driver callbacks
 * need to use gpiochip_get_data() to get their local state containers back
 * from the gpiochip passed as chip data. An irqdomain will be stored
 * in the gpiochip that shall be used by the driver to handle IRQ number
 * translation. The gpiochip will need to be initialized and registered
 * before calling this function.
 *
 * This function will handle two cell:ed simple IRQs and assumes all
 * the pins on the gpiochip can generate a unique IRQ. Everything else
 * need to be open coded.
 */
int gpiochip_irqchip_add_key(struct gpio_chip *gpiochip,
                 struct irq_chip *irqchip,
                 unsigned int first_irq,
                 irq_flow_handler_t handler,
                 unsigned int type,
                 bool nested,
                 struct lock_class_key *lock_key)
{
    struct device_node *of_node;

    if (!gpiochip || !irqchip)
        return -EINVAL;

    if (!gpiochip->parent) {
        pr_err("missing gpiochip .dev parent pointer\n");
        return -EINVAL;
    }
    gpiochip->irq_nested = nested;
    of_node = gpiochip->parent->of_node;
#ifdef CONFIG_OF_GPIO
    /*
     * If the gpiochip has an assigned OF node this takes precedence
     * FIXME: get rid of this and use gpiochip->parent->of_node
     * everywhere
     */
    if (gpiochip->of_node)
        of_node = gpiochip->of_node;
#endif
  • 코드 라인 38~39에서 gpiochip 및 irqchip을 전달 받지 못한 경우 에러로 함수를 빠져나간다.
  • 코드 라인 41~44에서 gpiochip이 연결되어 있는 상위 디바이스가 지정되지 않은 경우 에러 메시지를 출력하고 함수를 빠져나간다.
    • 상위 디바이스는 i2c,  usb, pci 버스 또는 플랫폼 디바이스이다.
  • 코드 라인 46~55에서 gpiochip에 해당하는 디바이스 트리 노드를 읽어온 경우 of_node에 대입한다. 그렇지 않은 경우는 부모 버스 디바이스노드를 of_node에 대입한다.

 

    /*
     * Specifying a default trigger is a terrible idea if DT or ACPI is
     * used to configure the interrupts, as you may end-up with
     * conflicting triggers. Tell the user, and reset to NONE.
     */
    if (WARN(of_node && type != IRQ_TYPE_NONE,
         "%pOF: Ignoring %d default trigger\n", of_node, type))
        type = IRQ_TYPE_NONE;
    if (has_acpi_companion(gpiochip->parent) && type != IRQ_TYPE_NONE) {
        acpi_handle_warn(ACPI_HANDLE(gpiochip->parent),
                 "Ignoring %d default trigger\n", type);
        type = IRQ_TYPE_NONE;
    }

    gpiochip->irqchip = irqchip;
    gpiochip->irq_handler = handler;
    gpiochip->irq_default_type = type;
    gpiochip->to_irq = gpiochip_to_irq;
    gpiochip->lock_key = lock_key;
    gpiochip->irqdomain = irq_domain_add_simple(of_node,
                    gpiochip->ngpio, first_irq,
                    &gpiochip_domain_ops, gpiochip);
    if (!gpiochip->irqdomain) {
        gpiochip->irqchip = NULL;
        return -EINVAL;
    }

    /*
     * It is possible for a driver to override this, but only if the
     * alternative functions are both implemented.
     */
    if (!irqchip->irq_request_resources &&
        !irqchip->irq_release_resources) {
        irqchip->irq_request_resources = gpiochip_irq_reqres;
        irqchip->irq_release_resources = gpiochip_irq_relres;
    }

    acpi_gpiochip_request_interrupts(gpiochip);

    return 0;
}
EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_key);
  • 코드 라인 6~8에서 디바이스 트리 노드가 있으면서 IRQ_TYPE_NONE 타입을 사용하지 않은 경우 경고 메시지를 출력하고 IRQ_TYPE_NONE을 강제 지정한다.
  • 코드 라인 9~13에서 버스 디바이스가 ACPI를 지원하고 IRQ_TYPE_NONE 타입이 아닌 경우 역시 경고 메시지를 출력하고 IRQ_TYPE_NONE을 강제 지정한다.
  • 코드 라인 15~19에서 gpiochip의 멤버들을 설정하되 (*to_irq) 후크에는 gpiochip_to_irq() 함수를 대입한다.
  • 코드 라인 20~26에서 simple 방식으로 gpio 핀 개수만큼 irq_domain을 생성한다. irq_domain용 오퍼레이션은 전역에 고정 선언된 &gpiochip_domain_ops를 사용한다.
  • 코드 라인 32~36에서 인자로 전달받은 irqchip의 두 후크 (*irq_request_resources) 및 (*irq_release_resources) 둘 다 설정되어 있지 않으면 각각  gpiochip_irq_reqres() 함수와 gpiochip_irq_reqres() 함수를 대입하여 사용한다.
    • gpiochip_irq_reqres()
      • gpio 디스크립터를 반환해오되 인터럽트와 연결되었음을 알리는 gpio 플래그를 설정하고 label 명으로 “interrupt”를 사용한다.
    • gpiochip_irq_relres()
      • gpio 디스크립터의 플래그에서 인터럽트와 연결되었음을 알리는 플래그를 클리어하고, label 명에 null을 대입하여 클리언한다.
  • 코드 라인 38에서 ACPI가 지원되는 경우 gpiochip에 대한 ACPI 이벤트를 위해 isr을 등록한다.

 

gpiochip_set_chained_irqchip()

chained 방식 interrupt 처리가 가능한 gpio controller를 위해 irq 및 irq 핸들러가 등록되었을 때, 인터럽트가 발생 시 처리되는 모습을 보여준다.

  • 먼저 interrupt controller의 ISR을 통해 발생한 인터럽트 400번 디스크립터를 찾는다.
  • 400번 디스크립터에 등록된 핸들러 함수 foo_gpio_irq_handler() 함수를 호출한다.
  • foo_gpio_irq_handler() 함수는 인터럽트가 설정된 gpio 핀들을 검사하여 인터럽트가 발생한 gpio 핀을 찾아 해당 child irq 번호를 찾는다.
  • 찾은 child irq에 해당하는 irq 디스크립터에 있는 irq subsystem core가 제공하는  generic 함수인 handle_simple_irq()를 호출한다.
  • handle_simple_irq() 함수는 custom 디바이스의 인터럽트 핸들러 루틴을 찾아 호출한다.
    • Custom 디바이스의 인터럽트 핸들러 등록은 다음 함수들을 사용한다.
      • request_irq() & devm_request_irq()
      • request_threaded_irq() & devm_request_threaded_irq()
      • request_any_context_irq() & devm_request_any_context_irq()
      • request_percpu_irq()

 

아래 그림은 cascaded irq 처리를 chained irq 방식으로 처리하고 있는 모습을 보여준다.

  • irq12번과 irq21번에 연결된 핸들러들이 irq context에서 처리되는 것을 알 수 있다.

drivers/gpio/gpiolib.c

/**                          
 * gpiochip_set_chained_irqchip() - connects a chained irqchip to a gpiochip
 * @gpiochip: the gpiochip to set the irqchip chain to
 * @irqchip: the irqchip to chain to the gpiochip
 * @parent_irq: the irq number corresponding to the parent IRQ for this
 * chained irqchip     
 * @parent_handler: the parent interrupt handler for the accumulated IRQ
 * coming out of the gpiochip. If the interrupt is nested rather than
 * cascaded, pass NULL in this handler argument
 */ 
void gpiochip_set_chained_irqchip(struct gpio_chip *gpiochip,
                  struct irq_chip *irqchip,
                  unsigned int parent_irq,
                  irq_flow_handler_t parent_handler)
{       
    gpiochip_set_cascaded_irqchip(gpiochip, irqchip, parent_irq,
                      parent_handler);
}      
EXPORT_SYMBOL_GPL(gpiochip_set_chained_irqchip);

chain된 irqchip을 gpiochip에 연결시킨다. 상위 인터럽트 컨트롤러에 연결될 parent_irq 번호와  이에 해당하는 핸들러도 같이 주어진다.

 

gpiochip_set_nested_irqchip()

아래 그림과 같이 cascaded interrupt 처리를 nested irq 방식으로 처리한 모습을 보여준다.

  • irq12번의 인터럽트 핸들러는 irq context에서 처리되지만, irq21번의 인터럽트 핸들러는 process context에서 처리되는 것을 알 수 있다.

 

drivers/gpio/gpiolib.c

/**
 * gpiochip_set_nested_irqchip() - connects a nested irqchip to a gpiochip
 * @gpiochip: the gpiochip to set the irqchip nested handler to
 * @irqchip: the irqchip to nest to the gpiochip
 * @parent_irq: the irq number corresponding to the parent IRQ for this
 * nested irqchip
 */
void gpiochip_set_nested_irqchip(struct gpio_chip *gpiochip,
                                 struct irq_chip *irqchip,
                                 unsigned int parent_irq)
{
        if (!gpiochip->irq_nested) {
                chip_err(gpiochip, "tried to nest a chained gpiochip\n");
                return;
        }
        gpiochip_set_cascaded_irqchip(gpiochip, irqchip, parent_irq,
                                      NULL);
}
EXPORT_SYMBOL_GPL(gpiochip_set_nested_irqchip);

 

 

gpiochip_set_cascaded_irqchip()

drivers/gpio/gpiolib.c

/**
 * gpiochip_set_cascaded_irqchip() - connects a cascaded irqchip to a gpiochip
 * @gpiochip: the gpiochip to set the irqchip chain to
 * @irqchip: the irqchip to chain to the gpiochip
 * @parent_irq: the irq number corresponding to the parent IRQ for this
 * chained irqchip
 * @parent_handler: the parent interrupt handler for the accumulated IRQ
 * coming out of the gpiochip. If the interrupt is nested rather than
 * cascaded, pass NULL in this handler argument
 */
static void gpiochip_set_cascaded_irqchip(struct gpio_chip *gpiochip,
                      struct irq_chip *irqchip,
                      unsigned int parent_irq,
                      irq_flow_handler_t parent_handler)
{
    unsigned int offset;

    if (!gpiochip->irqdomain) {
        chip_err(gpiochip, "called %s before setting up irqchip\n",
             __func__);
        return;
    }

    if (parent_handler) {
        if (gpiochip->can_sleep) {
            chip_err(gpiochip,
                 "you cannot have chained interrupts on a "
                 "chip that may sleep\n");
            return;
        }
        /*             
         * The parent irqchip is already using the chip_data for this
         * irqchip, so our callbacks simply use the handler_data.
         */
        irq_set_chained_handler_and_data(parent_irq, parent_handler,
                         gpiochip);
        
        gpiochip->irq_chained_parent = parent_irq;
    }

    /* Set the parent IRQ for all affected IRQs */
    for (offset = 0; offset < gpiochip->ngpio; offset++) {
        if (!gpiochip_irqchip_irq_valid(gpiochip, offset))
            continue;
        irq_set_parent(irq_find_mapping(gpiochip->irqdomain, offset),
                   parent_irq);
    }
}

chained irq 처리를 위해 gpio 핀들에서 사용하는 모든 irq 디스크립터에 parent_irq를 설정한다.

  • 코드 라인 18~22에서 gpiochip에 irqdomain이 설정되지 않은 경우 에러 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 24~39에서 chain된 parent_irq 번호와 핸들러를 등록한다. gpiochip에 sleep 기능이 있으면 인터럽트 처리를 할 수 없으므로 gpiochip 에서는 chained 인터럽트를 설정 시 에러가 발생한다.
  • 코드 라인 42~47에서 gpio 칩에서 irq를 사용하는 모든 gpio핀에 연결된 irq 디스크립터에 parent_irq를 설정한다.

 

 

Generic Memory Mapped GPIO (bgpio)

레지스터 조작 기반의 GPIO 컨트롤러를 구현하는 또 다른 방법이 있다. 대부분의 GPIO가 SoC 내부에서 메모리 매핑된 GPIO 레지스터 조작만으로 동작하므로 이를 구현하기 편리하도록 제공하고 있다. 이 방법의 특징은 다음과 같다.

  • CONFIG_GPIO_GENERIC 커널 옵션을 사용한다.
  • 메모리가 매핑되어 빠르게 조작가능하므로 cascaded irq 구현 시 chained irq 연동 방식을 사용한다.
  • gpio_chip 및 irq_chip에 대한 operation 구현에 이미 준비된 후크 함수들을 사용하므로 별도로 작성할 필요가 없다.
  • irq_domain 은 미리 구성하여 사용한다.
  • memory mapped gpio 구성에 필요한 API 들
    • bgpio_init()
    • devm_gpiochip_add_data()
  • chained irq 구성에 필요한 추가 API 들
    • irq_domain 구성 API들은 생략
    • irq_alloc_generic_chip() & devm_irq_alloc_generic_chip()
    • irq_setup_generic_chip() & devm_irq_setup_generic_chip()
      • 또는 irq_set_chip_and_handler()
    • gpiochip_set_chained_irqchip()
      • 또는 irq_set_chained_handler_and_data()
  • 참고: Using gpio-generic and irq_chip_generic subsystems for gpio driver | Maquefel’s Stash

 

참고

 

 

댓글 남기기