We are writing a Linux kernel module: GPIO with IRQ support

Habr, greetings!
 
 
This article is devoted to the development of GPIO (General-Purpose Input /Output) of the Linux kernel module. As in the previous article we implement the basic structure of the GPIO driver with interrupt support (IRQ: Interrupt Request).
 
 
We are writing a Linux kernel module: GPIO with IRQ support  
here .
 
 

Step One


 
First, let's get acquainted with the principle of GPIO driver interaction through the user console.
 
Using a small bash script, create /sysfs controls for each GPIO. To do this, enter the following script in the command line:
 
 
for i in {248255}; do echo $ i> /sys /class /gpio /export; done

 
 
Next, see what features /sysfs provides for the configuration of each GPIO:
 
 
root @ zed-slave: /sys /class /gpio # ls -l gpio248 /
total 0
-rw-r - r-- 1 root root 4096 Jan ???:50 active_low
-rw-r-r- 1 root root 4096 Jan ???:50 direction
-rw-r-r- 1 root root 4096 Jan ???:50 edge
drwxr-xr-x 2 root root 0 Jan ???:50 power
lrwxrwxrwx 1 root root 0 Jan ???:50 subsystem -> ////class/gpio
-rw-r -r- 1 root root 4096 Jan ???:10 uevent
-rw-r - r-- 1 root root 4096 Jan ???:50 value

 
At the moment we are interested in the following fields:
 
 
 
direction - sets the direction of the line. Can take the values ​​"in" or "out";
 
value - allows you to set a high or low signal on the line (if the direction is set to "out"), otherwise (direction set to "in") allows you to read the state of the line;
 
edge - allows you to configure the event by which the interrupt occurs. It can take the following values: "none", "rising", "falling" or "both".
 
 
After a quick familiarization with the interface of driver interaction via sysfs, you can consider how the driver processes the user's commands. In the Linux kernel, there is a gpio_chip structure that describes the functionality of the gpio controller. It contains the following fields:
 
 
 
direction_input : adjusts the line to the input. Called at the following entry: echo "in"> /sys /class /gpio /gpio248 /direction;
 
direction_output : adjusts the line to the output. Called at the following entry: echo "out"> /sys /class /gpio /gpio248 /direction;
 
get : Sets the received value to the line. Called at the following entry: cat /sys /class /gpio /gpio248 /value;
 
set : reads the value set on the line. It is called at the following record: echo 1/0 /sys /class /gpio /gpio248 /value;
 
 
To describe the IRQ configuration in Linux, there is an irq_chip structure that contains the following fields:
 
 
 
irq_set_type : sets the type of event by which the interrupt will occur. It is called at the following entry: echo> "rising" /"falling" /"both"> /sys /class /gpio /gpio248 /edge;
 
irq_mask : disables interrupts. Called at the following entry: echo "none"> /sys /class /gpio /gpio248 /edge;
 
irq_unmask : Allows an interrupt on the event that was set in irq_set_type. It is called immediately after irq_set_type.
 
 

Step two of the second


 
Now you can add the driver to the assembly and describe the hardware by performing the standard actions already. First of all, create the source file:
 
 
cd drivers /gpio /
vim gpio-skel.c
: wq

 
After add the driver configuration to drivers /gpio /Kconfig :
 
 
config GPIO_SKEL
tristate "SKEL GPIO"
help
Say yes here to support SKEL GPIO.

 
Add the driver to the assembly in drivers /gpio /Makefile :
 
 
obj - $ (CONFIG_GPIO_SKEL) + = gpio-skel.o

 
Finally, add a GPIO block description to devicetree (* .dts):
 
 
gpio: gpio @ f8f01d00 {
compatible = "skel-gpio";
rcm, ngpio = <8> ;
rcm, interrupt-type =
;
clocks = <&clkc 42> ;
gpio-controller;
interrupt-parent = <&ps7_scugic_0> ;
interrupts = <0 29 4> ;
reg = <0x43c00000 0x100> ;
};};

 
 
More detailed information about devicetree can be read here .
 
 

Step Three


 
Let's move on to the most interesting part for us!
 
 
We'll start the driver development by connecting the necessary header files and describing the complete driver skeleton without IRQ support. Next, we will consistently fill each function with a code and accompany with the necessary explanations.
 
 
The skeleton of the driver is GPIO [/b]
/* gpio-skel.c: GPIO driver
*
* Name Surname
*
* This file is licensed under the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
* /
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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_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");

 
 
As you can see from the implementation, the driver's skeleton looks quite simple and does not contain many necessary functions and structures.
 
 
In order to describe the future driver, we need the following elements:
 
 
 
platform_driver skel_gpio_driver - describes the entry point skel_gpio_probe when the driver is loaded and skel_gpio_remove when it is extracted from the kernel;
 
struct of_device_id skel_gpio_of_match - contains a table that describes the hardware of the GPIO block;
 
struct skel_gpio_chip - contains the necessary fields for controlling the GPIO driver by the block.
 
 
Next, in order to load /extract the driver to /from Linux, it is necessary to implement the specified in the structure skel_gpio_driver methods .probe and .remove .
 
 
Implementation of skel_gpio_probe [/b]
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 gpiochipn");
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;
}

 
 
Implementation of skel_gpio_remove [/b]
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;
}

 
 
The skel_gpio_remove function simply deletes the registered GPIO driver from the kernel, so consider the highlights in skel_gpio_probe:
 
 
 
devm_kzalloc - allocates memory under the skel_gpio_chip structure;
 
platform_get_resource - reads the address of the beginning of the GPIO register card from devicetree;
 
devm_ioremap_resource - Performs mapping of physical address to virtual address;
 
of_property_read_u32 - reads the number of available GPIO from devicetree;
 
skel_gc-> gchip. * - fills in the structures necessary for the work;
 
gpiochip_add - adds a GPIO controller to the driver;
 
 
So far, it has not been described why it uses such magic numbers as 248 255. Recording skel_gc-> gchip.base = -1; asks the kernel to dynamically allocate the numbers used by the GPIO. To find out the numbers at the end of the driver, the output is added:
 
 
dev_info (& pdev-> dev, "SKEL GPIO probe complete: (% d % d) n",
skel_gc-> gchip.base,
skel_gc-> gchip.base + skel_gc-> gchip.ngpio);

 
Of course, Linux provides the ability to manually specify numbers, but let's look at the comment in the original code :
 
 
@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
GPIO number space in the long run.

 
 

Step Fourth


 
Consider the functional part of the driver, namely, we implement the following methods:
 
.direction_output , .direction_input , .get and .set . Next, you will see the hardware-dependent code, which in most cases will be different.
 
 
Implementation of skel_gpio_direction_input [/b]
/*
* 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;
}

 
 
The skel_gpio_direction_input method does the following:
 
 
 
Invoked with the following command: echo "in"> /sys /class /gpio /gpioN /direction;
 
Reads the register SKEL_GPIO_PAD_DIR, which is responsible for setting the direction of the GPIO pin;
 
Sets the required mask;
 
Writes the received value back to SKEL_GPIO_PAD_DIR.
 
 
Implementation of skel_gpio_direction_output [/b]
/*
* 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;
}

 
 
The skel_gpio_direction_output method does the same as skel_gpio_direction_inut, except that it is called with the following commands:
 
 
 
echo "out"> /sys /class /gpio /gpioN /direction;
 
echo "high"> /sys /class /gpio /gpioN /direction, setting value to 1;
 
echo "low"> /sys /class /gpio /gpioN /direction, setting the value to 0.
 
 
value is the value that determines the level of the signal on the output line.
 
 
Implementation of skel_gpio_set [/b]
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);
}

 
 
The skel_gpio_set method performs the following actions:
 
 
 
It is called at the following command echo 1/0> /sys /class /gpio /gpioN /value;
 
Reads the register SKEL_GPIO_WR_DATA, which shows the value of the current signal on the line;
 
Set or reset the required bit by offset;
 
Writes the received value back to SKEL_GPIO_WR_DATA.
 
 
Implementation of skel_gpio_get [/b]
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));
}

 
 
The skel_gpio_get method reads the signal value on the line by reading the SKEL_GPIO_RD_DATA register.
 
 
After we have described all the necessary methods and structures, you can collect everything together and take a look at the final version.
 
 
Implementation of GPIO driver [/b]
/* gpio-skel.c: GPIO driver
*
* Name Surname
*
* This file is licensed under the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
* /
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 gpiochipn");
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_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");

 
 
The implemented driver contains the necessary functionality for managing the GPIO, but at the moment the driver does not support interrupts, so you can go to the next step.
 
 

Step Five


 
Adding an IRQ to the GPIO driver can be divided into three steps:
 
 
 
Description of supported methods in the data structures of the kernel;
 
Enable IRQ support when the driver is loaded into the system;
 
Implementation of supported methods.
 
 
Initially, we describe the required set of operations:
 
 
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,
};};

 
Therefore, the driver can resolve (skel_gpio_irq_unmask) /disallow (skel_gpio_irq_mask) interrupts and specify the type of event by which it will be generated (skel_gpio_irq_set_type).
 
Next, we describe the only method that is responsible for mapping the virtual irq number to the hardware one.
 
 
static struct irq_domain_ops skel_gpio_irq_domain_ops = {
.map = skel_gpio_irq_domain_map,
};};

 
Then we tell the kernel that the loadable driver supports working with IRQ. To do this, add the following code to the probe function:
 
 
Adding IRQ support [/b]
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);

 
 
In the above code occurs:
 
 
 
Selection and initialization of the area under irq_domain;
 
Read interrupt number with devicetree;
 
mapping between virtual and hardware interrupts;
 
Register the interrupt handler and set the data to the handler;
 
 
Let us proceed to implement some of the methods described above.
 
skel_gpio_to_irq - creates mapping between the hardware and virtual interrupts. If this mapping has already been created, it returns the number of the created virtual interrupt.
 
 
Implementation of skel_gpio_to_irq [/b]
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 - interrupt handler, which is:
 
 
 
Reads the interrupt status register;
 
Reads the register of the interrupt mask;
 
Highlights interrupts pending processing;
 
For each GPIO in which the interrupt occurred, generic_handle_irq is called.
 
 
Implementing the interrupt handler [/b]
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);
}

 
 
That's all, in this article we learned how the GPIO driver interacts with the virtual sysfs file system, implemented the basic structure of the GPIO driver, and also examined the methods that are required to support the IRQ.
 
 
The article does not provide the implementation of the skel_gpio_irq_unmask, skel_gpio_irq_mask, and skel_gpio_irq_set_type methods for two reasons. First, these methods are easy to implement. Secondly, it is hardware-dependent. They are responsible for enabling or disabling interrupts for certain events that the GPIO controller supports.
 
 
Please, if you find errors /inaccuracies, or you have something to add - write in the LS or in the comments.
 
 
Thank you for your attention!
+ 0 -

Add comment