Хабр, привет!
Данная статья посвящена разработке GPIO (General-Purpose Input/Output) модуля ядра Linux. Как и в предыдущей статье мы реализуем базовую структуру GPIO драйвера с поддержкой прерываний (IRQ: Interrupt Request).
Входные данные аналогичны предыдущей статье: разработанный GPIO блок для нового процессора «зашитый» на ПЛИС и запущенный Linux версии 3.18.19.
Для того чтобы разработать GPIO драйвер, нам потребуется выполнить следующие шаги:
Примеры GPIO драйверов можно посмотреть тут.
Для начала познакомимся с принципом взаимодействия GPIO драйвера через консоль пользователя.
С помощью небольшого bash скрипта, создадим в /sysfs элементы управления каждого GPIO. Для этого в командой строке нужно написать следующий скрипт:
Далее посмотрим какие возможности предоставляет /sysfs для конфигурации каждого GPIO:
В данный момент нас интересуют следующие поля:
После беглого знакомства с интерфейсом взаимодействия драйвера через sysfs, можно рассмотреть как драйвер обрабатывает команды пользователя. В ядре Linux есть структура gpio_chip которая описывает функционал gpio контроллера. В ней присутствуют следующие поля:
Для описания конфигурации IRQ в Linux существует структура irq_chip, которая содержит следующие поля:
Теперь можно добавить драйвер в сборку и описать аппаратную часть, выполнив уже стандартные действия. Первым делом создадим исходный файл:
После добавим конфигурацию драйвера в drivers/gpio/Kconfig:
Добавим в сборку драйвер в drivers/gpio/Makefile:
И, наконец, добавим в devicetree (*.dts) описание GPIO блока:
Более подробную информацию про devicetree можно прочитать тут.
Перейдем к самой интересной для нас части!
Разработку драйвера начнем с подключения необходимых заголовочных файлов и описания полного скелета драйвера без поддержки IRQ. Далее последовательно будем наполнять каждую функцию кодом и сопровождать необходимыми пояснениями.
Как видно из реализации, скелет драйвера выглядит достаточно просто и содержит не так много необходимых функций и структур.
Для того чтобы описать будущий драйвер нам потребуются следующие элементы:
Далее, чтобы загрузить/извлечь драйвер в/из Linux, необходимо реализовать указанные в структуре skel_gpio_driver методы .probe и .remove.
Функция skel_gpio_remove просто удаляет зарегистрированный GPIO драйвер из ядра, поэтому рассмотрим основные моменты в skel_gpio_probe:
До сих пор не было описано почему же используется такие магические числа как 248… 255. Запись skel_gc->gchip.base = -1; просит ядро динамически выделить номера используемых GPIO. Чтобы узнать данные номера в конце драйвера добавлен вывод:
Конечно, Linux предоставляет возможность задать номера вручную, но давайте посмотрим на комментарий в исходном коде:
Рассмотрим функциональную часть драйвера, а именно реализуем следующие методы:
.direction_output, .direction_input, .get и .set. Далее будет показан аппаратно-зависимый код, который в большинстве случаев будет отличаться.
Метод skel_gpio_direction_input выполняет следующие действия:
Метод skel_gpio_direction_output выполняет действия аналогичные skel_gpio_direction_inut за исключением того, что вызывается при следующих командах:
value — значение, определяющее уровень сигнала на выходной линии.
Метод skel_gpio_set выполняет следующие действия:
Метод skel_gpio_get считывает значение сигнала на линии, прочитав регистр SKEL_GPIO_RD_DATA.
После того как мы описали все необходимые методы и структуры, можно собрать все вместе и взглянуть на итоговый вариант.
Реализованный драйвер содержит необходимый функционал для управления GPIO, но в данный момент в драйвере отсутствует поддержка работы с прерываниями, поэтому можно перейти к следующему шагу.
Добавление IRQ в GPIO драйвер можно разделить на три шага:
Первоначально опишем необходимый набор операций:
Следовательно драйвер может разрешать(skel_gpio_irq_unmask)/запрещать(skel_gpio_irq_mask) прерывания и указать тип события по которому оно будет генерироваться (skel_gpio_irq_set_type).
Далее опишем единственный метод, который отвечает за сопоставление виртуального irq number с аппаратным.
Затем укажем ядру, что загружаемый драйвер поддерживает работу с IRQ. Для этого необходимо добавить в probe функцию следующий код:
В выше приведенном коде происходит:
Приступим к реализации некоторых выше описанных методов.
skel_gpio_to_irq — создает mapping между аппаратным и виртуальным прерыванием. Если же данный mapping уже был создан, то возвращает номер созданного виртуального прерывания.
skel_irq_handler — обработчик прерывания, который:
Вот и все, в данной статье мы узнали как происходит взаимодействие GPIO драйвера с виртуальной файловой системой sysfs, реализовали базовую структуру GPIO драйвера, а также рассмотрели методы которые требуются для поддержки IRQ.
В статье не приведена реализация методов skel_gpio_irq_unmask, skel_gpio_irq_mask и skel_gpio_irq_set_type по двум причинам. Во-первых, данные методы просты в реализации. Во-вторых, аппаратно-зависимы. Они отвечают за разрешение или запрет прерываний по определенным событиям, которые поддерживает GPIO контроллер.
Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Спасибо за ваше внимание!
Данная статья посвящена разработке GPIO (General-Purpose Input/Output) модуля ядра Linux. Как и в предыдущей статье мы реализуем базовую структуру GPIO драйвера с поддержкой прерываний (IRQ: Interrupt Request).
Входные данные аналогичны предыдущей статье: разработанный GPIO блок для нового процессора «зашитый» на ПЛИС и запущенный Linux версии 3.18.19.
Для того чтобы разработать GPIO драйвер, нам потребуется выполнить следующие шаги:
- Понять принцип взаимодействия GPIO драйвера с user space интерфейсом;
- Добавить модуль ядра в сборку и описать аппаратную часть в device tree;
- Реализовать базовый скелет драйвера, а также его точки входа и извлечения;
- Реализовать функциональную часть GPIO драйвера;
- Добавить к реализации драйвера поддержку IRQ.
Примеры GPIO драйверов можно посмотреть тут.
Шаг первый
Для начала познакомимся с принципом взаимодействия GPIO драйвера через консоль пользователя.
С помощью небольшого bash скрипта, создадим в /sysfs элементы управления каждого GPIO. Для этого в командой строке нужно написать следующий скрипт:
for i in {248..255}; do echo $i > /sys/class/gpio/export; done
Далее посмотрим какие возможности предоставляет /sysfs для конфигурации каждого GPIO:
root@zed-slave:/sys/class/gpio# ls -l gpio248/
total 0
-rw-r--r-- 1 root root 4096 Jan 7 20:50 active_low
-rw-r--r-- 1 root root 4096 Jan 7 20:50 direction
-rw-r--r-- 1 root root 4096 Jan 7 20:50 edge
drwxr-xr-x 2 root root 0 Jan 7 20:50 power
lrwxrwxrwx 1 root root 0 Jan 7 20:50 subsystem -> ../../../../class/gpio
-rw-r--r-- 1 root root 4096 Jan 7 20:10 uevent
-rw-r--r-- 1 root root 4096 Jan 7 20:50 value
В данный момент нас интересуют следующие поля:
- direction — задает направление линии. Может принимать значения «in» или «out»;
- value — позволяет выставить высокий или низкий сигнал на линии (если direction установлен в «out»), в противном случае (direction установлен в «in») позволяет прочитать состояние линии;
- edge — позволяет настроить событие по которому происходит прерывание. Может принимать следующие значения: «none», «rising», «falling» или «both».
После беглого знакомства с интерфейсом взаимодействия драйвера через sysfs, можно рассмотреть как драйвер обрабатывает команды пользователя. В ядре Linux есть структура gpio_chip которая описывает функционал gpio контроллера. В ней присутствуют следующие поля:
- direction_input: настраивает линию на вход. Вызывается при следующей записи: echo «in» > /sys/class/gpio/gpio248/direction;
- direction_output: настраивает линию на выход. Вызывается при следующей записи: echo «out» > /sys/class/gpio/gpio248/direction;
- get: считывает установленное на линии значение. Вызывается при следующей записи: cat /sys/class/gpio/gpio248/value;
- set: устанавливает значение на линии. Вызывается при следующей записи: echo 1/0 > /sys/class/gpio/gpio248/value;
Для описания конфигурации IRQ в Linux существует структура irq_chip, которая содержит следующие поля:
- irq_set_type: настраивает тип события по которому будет происходить прерывание. Вызывается при следующей записи: echo > «rising»/«falling»/«both» > /sys/class/gpio/gpio248/edge;
- irq_mask: запрещает прерывания. Вызывается при следующей записи: echo «none» > /sys/class/gpio/gpio248/edge;
- irq_unmask: разрешает прерывание по событию, которое было установлено в irq_set_type. Вызывается сразу после выполнения irq_set_type.
Шаг второй
Теперь можно добавить драйвер в сборку и описать аппаратную часть, выполнив уже стандартные действия. Первым делом создадим исходный файл:
cd drivers/gpio/
vim gpio-skel.c
:wq
После добавим конфигурацию драйвера в drivers/gpio/Kconfig:
config GPIO_SKEL
tristate "SKEL GPIO"
help
Say yes here to support SKEL GPIO.
Добавим в сборку драйвер в drivers/gpio/Makefile:
obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o
И, наконец, добавим в devicetree (*.dts) описание GPIO блока:
gpio: gpio@f8f01d00 {
compatible = "skel-gpio";
rcm,ngpio = <8>;
rcm,interrupt-type = <IRQ_TYPE_EDGE_RISING>;
clocks = <&clkc 42>;
gpio-controller ;
interrupt-parent = <&ps7_scugic_0>;
interrupts = <0 29 4>;
reg = <0x43c00000 0x100>;
} ;
Более подробную информацию про devicetree можно прочитать тут.
Шаг третий
Перейдем к самой интересной для нас части!
Разработку драйвера начнем с подключения необходимых заголовочных файлов и описания полного скелета драйвера без поддержки IRQ. Далее последовательно будем наполнять каждую функцию кодом и сопровождать необходимыми пояснениями.
Скелет драйвера GPIO
/* gpio-skel.c: GPIO driver
*
* Name Surname <email>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/irqdomain.h>
#include <linux/bitops.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio/driver.h>
#include <linux/platform_device.h>
#define SKEL_GPIO_VER 0x04
#define SKEL_GPIO_PAD_DIR 0x08
#define SKEL_GPIO_WR_DATA 0x0C
#define SKEL_GPIO_RD_DATA 0x10
#define SKEL_GPIO_WR_DATA1 0x1C
#define SKEL_GPIO_WR_DATA0 0x20
#define SKEL_GPIO_SRC 0x24
#define SKEL_GPIO_MAX_NGPIO 8
#define GPIO_OFFSET 4
struct skel_gpio_chip {
struct gpio_chip gchip;
spinlock_t lock;
void __iomem *regs;
u32 type;
};
static inline void gpio_write(uint32_t value, void *base, uint32_t addr)
{
writel(value, base + addr);
#if defined DEBUG
dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr);
#endif
}
static inline uint32_t gpio_read(void *base, uint32_t addr)
{
uint32_t reg = readl(base + addr);
#if defined DEBUG
dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg);
#endif
return reg;
}
static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip)
{
}
/*
* echo "in" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
}
/*
* echo "out" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
}
static int skel_gpio_get(struct gpio_chip *chip, unsigned offset)
{
}
static void skel_gpio_set(struct gpio_chip *chip,
unsigned offset, int value)
{
}
static int skel_gpio_probe(struct platform_device *pdev)
{
}
static int skel_gpio_remove(struct platform_device *pdev)
{
}
static const struct of_device_id skel_gpio_of_match[] = {
{ .compatible = "skel-gpio" },
{ },
};
MODULE_DEVICE_TABLE(of, skel_gpio_of_match);
static struct platform_driver skel_gpio_driver = {
.probe = skel_gpio_probe,
.remove = skel_gpio_remove,
.driver = {
.name = "skel-gpio",
.of_match_table = of_match_ptr(skel_gpio_of_match),
},
};
module_platform_driver(skel_gpio_driver);
MODULE_DESCRIPTION("GPIO driver");
MODULE_AUTHOR("Name Surname");
MODULE_LICENSE("GPL");
Как видно из реализации, скелет драйвера выглядит достаточно просто и содержит не так много необходимых функций и структур.
Для того чтобы описать будущий драйвер нам потребуются следующие элементы:
- platform_driver skel_gpio_driver — описывает точку входа skel_gpio_probe при загрузке драйвера и skel_gpio_remove при его извлечении из ядра;
- struct of_device_id skel_gpio_of_match — содержит таблицу, которая описывает аппаратную часть GPIO блока;
- struct skel_gpio_chip — содержит необходимые поля для управления драйвером GPIO блоком.
Далее, чтобы загрузить/извлечь драйвер в/из Linux, необходимо реализовать указанные в структуре skel_gpio_driver методы .probe и .remove.
Реализация skel_gpio_probe
static int skel_gpio_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct skel_gpio_chip *skel_gc;
struct resource *res;
int ngpio, ret;
skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL);
if (!skel_gc)
return -ENOMEM;
spin_lock_init(&skel_gc->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n");
return -EINVAL;
}
skel_gc->regs = devm_ioremap_resource(&pdev->dev, res);
if (!skel_gc->regs)
goto free;
if (!of_property_read_u32(node, "skel,ngpio", &ngpio))
skel_gc->gchip.ngpio = ngpio;
else
skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO;
if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) {
dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n");
skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO;
}
skel_gc->gchip.direction_input = skel_gpio_direction_input;
skel_gc->gchip.direction_output = skel_gpio_direction_output;
skel_gc->gchip.get = skel_gpio_get;
skel_gc->gchip.set = skel_gpio_set;
skel_gc->gchip.owner = THIS_MODULE;
skel_gc->gchip.base = -1;
platform_set_drvdata(pdev, skel_gc);
ret = gpiochip_add(&skel_gc->gchip);
if (ret) {
dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
return ret;
}
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n",
skel_gc->gchip.base,
skel_gc->gchip.base + skel_gc->gchip.ngpio);
return 0;
}
Реализация skel_gpio_remove
static int skel_gpio_remove(struct platform_device *pdev)
{
struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev);
gpiochip_remove(&skel_gc->gchip);
return 0;
}
Функция skel_gpio_remove просто удаляет зарегистрированный GPIO драйвер из ядра, поэтому рассмотрим основные моменты в skel_gpio_probe:
- devm_kzalloc — выделяет память под структуру skel_gpio_chip;
- platform_get_resource — читает адрес начала регистровой карты GPIO из devicetree;
- devm_ioremap_resource — выполняет mapping физического адреса на виртуальный;
- of_property_read_u32 — читает количество доступных GPIO из devicetree;
- skel_gc->gchip.* — заполняет необходимые для работы поля структуры;
- gpiochip_add — добавляет GPIO контроллер в драйвер;
До сих пор не было описано почему же используется такие магические числа как 248… 255. Запись skel_gc->gchip.base = -1; просит ядро динамически выделить номера используемых GPIO. Чтобы узнать данные номера в конце драйвера добавлен вывод:
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n",
skel_gc->gchip.base,
skel_gc->gchip.base + skel_gc->gchip.ngpio);
Конечно, Linux предоставляет возможность задать номера вручную, но давайте посмотрим на комментарий в исходном коде:
@base: identifies the first GPIO number handled by this chip;
* or, if negative during registration, requests dynamic ID allocation.
* DEPRECATION: providing anything non-negative and nailing the base
* offset of GPIO chips is deprecated. Please pass -1 as base to
* let gpiolib select the chip base in all possible cases. We want to
* get rid of the static GPIO number space in the long run.
Шаг четвертый
Рассмотрим функциональную часть драйвера, а именно реализуем следующие методы:
.direction_output, .direction_input, .get и .set. Далее будет показан аппаратно-зависимый код, который в большинстве случаев будет отличаться.
Реализация skel_gpio_direction_input
/*
* echo "in" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
u32 data_dir;
spin_lock_irqsave(&gc->lock, flag);
data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR);
data_dir &= ~BIT(offset);
gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR);
spin_unlock_irqrestore(&gc->lock, flag);
return 0;
}
Метод skel_gpio_direction_input выполняет следующие действия:
- Вызывается при следующей команде echo «in» > /sys/class/gpio/gpioN/direction;
- Считывает регистр SKEL_GPIO_PAD_DIR, который отвечает за установку направления пина GPIO;
- Устанавливает необходимую маску;
- Записывает полученное значение обратно в SKEL_GPIO_PAD_DIR.
Реализация skel_gpio_direction_output
/*
* echo "out" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
u32 data_reg, data_dir;
spin_lock_irqsave(&gc->lock, flag);
data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA);
if (value)
data_reg |= BIT(offset);
else
data_reg &= ~BIT(offset);
gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA);
data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR);
data_dir |= BIT(offset);
gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR);
spin_unlock_irqrestore(&gc->lock, flag);
return 0;
}
Метод skel_gpio_direction_output выполняет действия аналогичные skel_gpio_direction_inut за исключением того, что вызывается при следующих командах:
- echo «out» > /sys/class/gpio/gpioN/direction;
- echo «high» > /sys/class/gpio/gpioN/direction, при этом устанавливает значение value в 1;
- echo «low» > /sys/class/gpio/gpioN/direction, при этом устанавливает значение value в 0.
value — значение, определяющее уровень сигнала на выходной линии.
Реализация skel_gpio_set
static void skel_gpio_set(struct gpio_chip *chip,
unsigned offset, int value)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
unsigned int data_reg;
spin_lock_irqsave(&gc->lock, flag);
data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA);
if (value)
data_reg |= BIT(offset);
else
data_reg &= ~BIT(offset);
gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA);
spin_unlock_irqrestore(&gc->lock, flag);
}
Метод skel_gpio_set выполняет следующие действия:
- Вызывается при следующей команде echo 1/0 > /sys/class/gpio/gpioN/value;
- Считывает регистр SKEL_GPIO_WR_DATA, который показывает значение текущего сигнала на линии;
- Устанавливает или сбрасывает необходимый бит по offset;
- Записывает полученное значение обратно в SKEL_GPIO_WR_DATA.
Реализация skel_gpio_get
static int skel_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset));
}
Метод skel_gpio_get считывает значение сигнала на линии, прочитав регистр SKEL_GPIO_RD_DATA.
После того как мы описали все необходимые методы и структуры, можно собрать все вместе и взглянуть на итоговый вариант.
Реализация GPIO драйвера
/* gpio-skel.c: GPIO driver
*
* Name Surname <email>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/irqdomain.h>
#include <linux/bitops.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio/driver.h>
#include <linux/platform_device.h>
#define SKEL_GPIO_VER 0x04
#define SKEL_GPIO_PAD_DIR 0x08
#define SKEL_GPIO_WR_DATA 0x0C
#define SKEL_GPIO_RD_DATA 0x10
#define SKEL_GPIO_WR_DATA1 0x1C
#define SKEL_GPIO_WR_DATA0 0x20
#define SKEL_GPIO_SRC 0x24
#define SKEL_GPIO_MAX_NGPIO 8
#define GPIO_OFFSET 4
struct skel_gpio_chip {
struct gpio_chip gchip;
spinlock_t lock;
void __iomem *regs;
u32 type;
};
static inline void gpio_write(uint32_t value, void *base, uint32_t addr)
{
writel(value, base + addr);
#if defined DEBUG
dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr);
#endif
}
static inline uint32_t gpio_read(void *base, uint32_t addr)
{
uint32_t reg = readl(base + addr);
#if defined DEBUG
dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg);
#endif
return reg;
}
static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip)
{
return container_of(chip, struct skel_gpio_chip, gchip);
}
/*
* echo > "out" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
u32 data_reg, data_dir;
spin_lock_irqsave(&gc->lock, flag);
data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA);
if (value)
data_reg |= BIT(offset);
else
data_reg &= ~BIT(offset);
gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA);
data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR);
data_dir |= BIT(offset);
gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR);
spin_unlock_irqrestore(&gc->lock, flag);
return 0;
}
/*
* echo > "in" > /sys/class/gpio/gpioN/direction
*/
static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
u32 data_dir;
spin_lock_irqsave(&gc->lock, flag);
data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR);
data_dir &= ~BIT(offset);
gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR);
spin_unlock_irqrestore(&gc->lock, flag);
return 0;
}
static int skel_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset));
}
static void skel_gpio_set(struct gpio_chip *chip,
unsigned offset, int value)
{
struct skel_gpio_chip *gc = to_skel_gpio(chip);
unsigned long flag;
unsigned int data_reg;
spin_lock_irqsave(&gc->lock, flag);
data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA);
if (value)
data_reg |= BIT(offset);
else
data_reg &= ~BIT(offset);
gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA);
spin_unlock_irqrestore(&gc->lock, flag);
}
static int skel_gpio_remove(struct platform_device *pdev)
{
struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev);
gpiochip_remove(&skel_gc->gchip);
return 0;
}
static int skel_gpio_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct skel_gpio_chip *skel_gc;
struct resource *res;
int ngpio, ret;
skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL);
if (!skel_gc)
return -ENOMEM;
spin_lock_init(&skel_gc->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n");
return -EINVAL;
}
skel_gc->regs = devm_ioremap_resource(&pdev->dev, res);
if (!skel_gc->regs)
return -ENXIO;
if (!of_property_read_u32(node, "skel,ngpio", &ngpio))
skel_gc->gchip.ngpio = ngpio;
else
skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO;
if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) {
dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n");
skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO;
}
skel_gc->gchip.direction_input = skel_gpio_direction_input;
skel_gc->gchip.direction_output = skel_gpio_direction_output;
skel_gc->gchip.get = skel_gpio_get;
skel_gc->gchip.set = skel_gpio_set;
skel_gc->gchip.owner = THIS_MODULE;
skel_gc->gchip.base = -1;
platform_set_drvdata(pdev, skel_gc);
ret = gpiochip_add(&skel_gc->gchip);
if (ret) {
dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
return ret;
}
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n",
skel_gc->gchip.base,
skel_gc->gchip.base + skel_gc->gchip.ngpio);
return 0;
}
static const struct of_device_id skel_gpio_of_match[] = {
{ .compatible = "skel-gpio" },
{ },
};
MODULE_DEVICE_TABLE(of, skel_gpio_of_match);
static struct platform_driver skel_gpio_driver = {
.probe = skel_gpio_probe,
.remove = skel_gpio_remove,
.driver = {
.name = "skel-gpio",
.of_match_table = of_match_ptr(skel_gpio_of_match),
},
};
module_platform_driver(skel_gpio_driver);
MODULE_DESCRIPTION("GPIO driver");
MODULE_AUTHOR("Name Surname");
MODULE_LICENSE("GPL");
Реализованный драйвер содержит необходимый функционал для управления GPIO, но в данный момент в драйвере отсутствует поддержка работы с прерываниями, поэтому можно перейти к следующему шагу.
Шаг пятый
Добавление IRQ в GPIO драйвер можно разделить на три шага:
- Описание поддерживаемых методов в структурах данных ядра;
- Включение поддержки IRQ в момент загрузки драйвера в систему;
- Реализация поддерживаемых методов.
Первоначально опишем необходимый набор операций:
static struct irq_chip skel_irq_chip = {
.name = "skel-gpio",
.irq_mask = skel_gpio_irq_mask,
.irq_unmask = skel_gpio_irq_unmask,
.irq_set_type = skel_gpio_irq_set_type,
};
Следовательно драйвер может разрешать(skel_gpio_irq_unmask)/запрещать(skel_gpio_irq_mask) прерывания и указать тип события по которому оно будет генерироваться (skel_gpio_irq_set_type).
Далее опишем единственный метод, который отвечает за сопоставление виртуального irq number с аппаратным.
static struct irq_domain_ops skel_gpio_irq_domain_ops = {
.map = skel_gpio_irq_domain_map,
};
Затем укажем ядру, что загружаемый драйвер поддерживает работу с IRQ. Для этого необходимо добавить в probe функцию следующий код:
Добавление поддержки IRQ
skel_gc->gchip.to_irq = skel_gpio_to_irq;
skel_gc->domain = irq_domain_add_linear(pdev->dev.of_node,
rcm_gc->gchip.ngpio,
&skel_gpio_irq_domain_ops,
skel_gc);
if (!skel_gc->domain)
return -ENODEV;
skel_gc->irq = platform_get_irq(pdev, 0);
if (skel_gc->irq < 0)
goto free;
for (i = 0; i < skel_gc->gchip.ngpio; i++) {
int irq = rcm_gpio_to_irq(&skel_gc->gchip, i);
irq_set_chip_and_handler(irq, &skel_irq_chip, handle_simple_irq);
#ifdef CONFIG_ARM
set_irq_flags(irq, IRQF_VALID);
#else
irq_set_noprobe(irq);
#endif
}
irq_set_chained_handler(skel_gc->irq, skel_irq_handler);
irq_set_handler_data(skel_gc->irq, skel_gc);
В выше приведенном коде происходит:
- Выделение и инициализация области под irq_domain;
- Считывание номера прерывания с devicetree;
- mapping между виртуальным и аппаратным прерыванием;
- Регистрация обработчика прерывания и установка данных на передачу в обработчик;
Приступим к реализации некоторых выше описанных методов.
skel_gpio_to_irq — создает mapping между аппаратным и виртуальным прерыванием. Если же данный mapping уже был создан, то возвращает номер созданного виртуального прерывания.
Реализация skel_gpio_to_irq
static int skel_gpio_to_irq(struct gpio_chip *chip, unsigned gpio)
{
struct skel_gpio_chip *rcm = to_skel_gpio(chip);
return irq_create_mapping(rcm->domain, gpio);
}
skel_irq_handler — обработчик прерывания, который:
- Считывает регистр статуса прерывания;
- Считывает регистр маски прерывания;
- Выделяет прерывания ожидающие обработки;
- Для каждого GPIO в котором возникло прерывание вызывает generic_handle_irq.
Реализация обработчика прерывания
static void skel_irq_handler(unsigned int irq, struct irq_desc *desc)
{
struct skel_gpio_chip *skel_gc = irq_get_handler_data(irq);
struct irq_chip *chip = irq_desc_get_chip(desc);
void __iomem *base;
u32 status, mask, gpio, pending;
chained_irq_enter(chip, desc);
base = skel_gc->regs;
status = gpio_read(base, SKEL_GPIO_STATUS);
mask = gpio_read(base, SKEL_GPIO_IRQ_MASK);
pending = status & mask;
while (pending) {
gpio = __ffs(pending);
pending &= ~BIT(gpio);
generic_handle_irq(
irq_find_mapping(skel_gc->domain, gpio));
}
chained_irq_exit(chip, desc);
}
Вот и все, в данной статье мы узнали как происходит взаимодействие GPIO драйвера с виртуальной файловой системой sysfs, реализовали базовую структуру GPIO драйвера, а также рассмотрели методы которые требуются для поддержки IRQ.
В статье не приведена реализация методов skel_gpio_irq_unmask, skel_gpio_irq_mask и skel_gpio_irq_set_type по двум причинам. Во-первых, данные методы просты в реализации. Во-вторых, аппаратно-зависимы. Они отвечают за разрешение или запрет прерываний по определенным событиям, которые поддерживает GPIO контроллер.
Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Спасибо за ваше внимание!