Habr, halo!
Artikel ini adalah tentang mengembangkan modul GPIO (General-Purpose Input / Output) dari kernel Linux. Seperti pada
artikel sebelumnya
, kami menerapkan struktur dasar driver GPIO dengan dukungan interrupt (IRQ: Interrupt Request).

Data input mirip dengan artikel sebelumnya: blok GPIO dikembangkan untuk prosesor baru "kabel" ke FPGA dan menjalankan Linux versi 3.18.19.
Untuk mengembangkan driver GPIO, kita perlu melakukan langkah-langkah berikut:
- Memahami prinsip interaksi antara driver GPIO dan antarmuka ruang pengguna;
- Tambahkan modul kernel ke rakitan dan jelaskan perangkat keras di bagan perangkat;
- Menerapkan kerangka dasar pengemudi, serta titik masuk dan pengambilannya;
- Menerapkan bagian fungsional driver GPIO;
- Tambahkan dukungan IRQ ke implementasi driver.
Contoh driver GPIO dapat ditemukan di
sini .
Langkah pertama
Pertama, mari kita berkenalan dengan prinsip interaksi antara driver GPIO melalui konsol pengguna.
Menggunakan skrip bash kecil, buat kontrol untuk setiap GPIO di / sysfs. Untuk melakukan ini, tulis skrip berikut di baris perintah:
for i in {248..255}; do echo $i > /sys/class/gpio/export; done
Selanjutnya, mari kita lihat fitur apa yang disediakan / sysfs untuk mengkonfigurasi setiap 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
Saat ini kami tertarik pada bidang-bidang berikut:
- direction - mengatur arah garis. Dapat mengambil nilai "dalam" atau "keluar";
- nilai - memungkinkan Anda untuk mengatur sinyal tinggi atau rendah pada saluran (jika arah diatur ke "keluar"), jika tidak (arah diatur ke "dalam") memungkinkan Anda untuk membaca status saluran;
- edge - memungkinkan Anda untuk mengkonfigurasi peristiwa yang menyebabkan interupsi terjadi. Ini dapat mengambil nilai-nilai berikut: "tidak ada", "naik", "jatuh" atau "keduanya".
Setelah berkenalan dengan antarmuka driver melalui sysfs, Anda dapat mempertimbangkan bagaimana driver memproses perintah pengguna. Kernel Linux memiliki struktur gpio_chip yang menjelaskan fungsi dari kontroler gpio. Bidang-bidang berikut ada di dalamnya:
- direction_input : mengatur baris ke input. Dipanggil pada entri berikut: echo "in"> / sys / class / gpio / gpio248 / direction;
- direction_output : mengatur garis untuk keluar. Dipanggil pada entri berikut: echo "out"> / sys / class / gpio / gpio248 / direction;
- get : membaca nilai yang ditetapkan di telepon. Dipanggil pada entri berikut: cat / sys / class / gpio / gpio248 / value;
- set : mengatur nilai pada baris. Dipanggil pada entri berikut: echo 1/0> / sys / class / gpio / gpio248 / value;
Untuk menjelaskan konfigurasi IRQ di Linux, ada struktur irq_chip yang berisi bidang-bidang berikut:
- irq_set_type : menyetel jenis peristiwa yang dengannya interupsi akan terjadi. Dipanggil pada entri berikut: echo> "naik" / "jatuh" / "keduanya"> / sys / class / gpio / gpio248 / edge;
- irq_mask : melarang interupsi. Dipanggil pada entri berikut: echo "none"> / sys / class / gpio / gpio248 / edge;
- irq_unmask : Mengaktifkan interupsi pada acara yang diatur ke irq_set_type. Disebut segera setelah irq_set_type dieksekusi.
Langkah kedua
Sekarang Anda dapat menambahkan driver ke rakitan dan menjelaskan perangkat keras, mengikuti langkah-langkah standar. Pertama, buat file sumber:
cd drivers/gpio/ vim gpio-skel.c :wq
Setelah kami menambahkan konfigurasi
driver ke
driver / gpio / Kconfig :
config GPIO_SKEL tristate "SKEL GPIO" help Say yes here to support SKEL GPIO.
Tambahkan driver ke rakitan di
driver / gpio / Makefile :
obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o
Dan akhirnya, tambahkan deskripsi blok GPIO ke devicetree (* .dts):
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>; } ;
Anda dapat membaca lebih lanjut tentang devicetree di
sini .
Langkah ketiga
Mari kita beralih ke bagian yang paling menarik bagi kita!
Kami memulai pengembangan driver dengan menghubungkan file header yang diperlukan dan menjelaskan kerangka lengkap driver tanpa dukungan IRQ. Selanjutnya, kita akan secara berurutan mengisi setiap fungsi dengan kode dan menyertai penjelasan yang diperlukan.
Kerangka driver GPIO #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");
Seperti yang dapat Anda lihat dari implementasi, kerangka pengemudi terlihat cukup sederhana dan tidak mengandung banyak fungsi dan struktur yang diperlukan.
Untuk menggambarkan driver masa depan, kita membutuhkan elemen-elemen berikut:
- platform_driver skel_gpio_driver - menjelaskan titik masuk skel_gpio_probe saat memuat driver dan skel_gpio_remove ketika diekstraksi dari kernel;
- struct of_device_id skel_gpio_of_match - berisi tabel yang menggambarkan perangkat keras dari blok GPIO;
- struct skel_gpio_chip - berisi bidang yang diperlukan untuk mengontrol driver blok GPIO.
Selanjutnya, untuk memuat / mengekstrak driver ke / dari Linux, perlu untuk mengimplementasikan metode
.probe dan
.remove yang ditentukan dalam struktur
skel_gpio_driver .
Implementasi 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; }
Implementasi 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; }
Fungsi skel_gpio_remove hanya menghapus driver GPIO terdaftar dari kernel, jadi pertimbangkan poin utama dalam skel_gpio_probe:
- devm_kzalloc - mengalokasikan memori untuk struktur skel_gpio_chip;
- platform_get_resource - membaca alamat awal kartu register GPIO dari devicetree;
- devm_ioremap_resource - melakukan pemetaan alamat fisik ke alamat virtual;
- of_property_read_u32 - membaca jumlah GPIO yang tersedia dari devicetree;
- skel_gc-> gchip. * - mengisi bidang struktur yang diperlukan;
- gpiochip_add - menambahkan pengendali GPIO ke driver;
Sejauh ini, belum dijelaskan mengapa angka ajaib seperti 248 ... 255 digunakan. Entri skel_gc-> gchip.base = -1; meminta kernel untuk secara dinamis mengalokasikan angka yang digunakan oleh GPIO. Untuk mengetahui angka-angka ini di akhir driver, output ditambahkan:
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio);
Tentu saja, Linux menyediakan kemampuan untuk mengatur angka secara manual, tetapi mari kita lihat komentar dalam
kode sumber:
@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.
Langkah keempat
Pertimbangkan bagian fungsional driver, yaitu, kami menerapkan metode berikut:
.direction_output ,
.direction_input ,
.get dan
.set . Selanjutnya, kode yang tergantung pada perangkat keras akan ditampilkan, yang dalam banyak kasus akan berbeda.
Implementasi Skel_gpio_direction_input 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; }
Metode skel_gpio_direction_input melakukan tindakan berikut:
- Dipanggil dengan perintah berikut echo "in"> / sys / class / gpio / gpioN / direction;
- Membaca register SKEL_GPIO_PAD_DIR, yang bertanggung jawab untuk mengatur arah pin GPIO;
- Setel topeng yang dibutuhkan;
- Menulis nilai yang diterima kembali ke SKEL_GPIO_PAD_DIR.
Implementasi Skel_gpio_direction_output 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; }
Metode skel_gpio_direction_output melakukan tindakan yang mirip dengan skel_gpio_direction_inut kecuali ia dipanggil dengan perintah berikut:
- echo "out"> / sys / class / gpio / gpioN / direction;
- gema "tinggi"> / sys / class / gpio / gpioN / direction, atur nilainya menjadi 1;
- gema "rendah"> / sys / class / gpio / gpioN / direction, atur nilainya ke 0.
value - nilai yang menentukan level sinyal pada jalur output.
Implementasi 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); }
Metode skel_gpio_set melakukan tindakan berikut:
- Dipanggil dengan perintah berikut echo 1/0> / sys / class / gpio / gpioN / value;
- Membaca register SKEL_GPIO_WR_DATA, yang menunjukkan nilai sinyal saat ini di telepon;
- Set atau setel ulang bit yang diperlukan dengan offset;
- Menulis nilai yang diterima kembali ke SKEL_GPIO_WR_DATA.
Implementasi 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)); }
Metode skel_gpio_get membaca nilai sinyal di telepon dengan membaca register SKEL_GPIO_RD_DATA.
Setelah kami menjelaskan semua metode dan struktur yang diperlukan, Anda dapat menggabungkan semuanya dan melihat versi finalnya.
Implementasi driver GPIO #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");
Driver yang diimplementasikan berisi fungsionalitas yang diperlukan untuk mengendalikan GPIO, tetapi saat ini driver tidak mendukung penanganan interupsi, sehingga Anda dapat pergi ke langkah berikutnya.
Langkah kelima
Menambahkan IRQ ke driver GPIO dapat dibagi menjadi tiga langkah:
- Deskripsi metode yang didukung dalam struktur data kernel;
- Mengaktifkan dukungan IRQ pada saat driver dimuat ke dalam sistem;
- Implementasi metode yang didukung.
Awalnya, kami menggambarkan rangkaian operasi yang diperlukan:
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, };
Oleh karena itu, pengandar dapat mengaktifkan (skel_gpio_irq_unmask) / menonaktifkan (skel_gpio_irq_mask) gangguan dan menunjukkan jenis peristiwa yang akan dihasilkan (skel_gpio_irq_set_type).
Selanjutnya, kami menjelaskan satu-satunya metode yang bertanggung jawab untuk memetakan nomor irq virtual ke perangkat keras.
static struct irq_domain_ops skel_gpio_irq_domain_ops = { .map = skel_gpio_irq_domain_map, };
Kemudian kami akan memberi tahu kernel bahwa dukungan driver yang dimuat bekerja dengan IRQ. Untuk melakukan ini, tambahkan kode berikut ke fungsi probe:
Menambahkan Dukungan 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);
Dalam kode di atas terjadi:
- Alokasi dan inisialisasi area di bawah irq_domain;
- Baca nomor interupsi dengan devicetree;
- pemetaan antara interupsi virtual dan perangkat keras;
- Mendaftarkan pawang interupsi dan mengatur data untuk pengiriman ke pawang;
Kami terus menerapkan beberapa metode yang dijelaskan di atas.
skel_gpio_to_irq - Membuat pemetaan antara perangkat keras dan interupsi virtual. Jika pemetaan ini telah dibuat, ia mengembalikan jumlah interupsi virtual yang dibuat.
Implementasi 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 adalah interrupt handler yang:
- Membaca register status interupsi;
- Membaca register topeng interupsi;
- Lemparan menyela menunggu pemrosesan;
- Untuk setiap GPIO di mana terjadi gangguan, panggil generic_handle_irq.
Implementasi Interrupt Handler 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); }
Itu saja, dalam artikel ini kami mempelajari bagaimana driver GPIO berinteraksi dengan sistem file virtual sysfs, menerapkan struktur dasar driver GPIO, dan juga memeriksa metode yang diperlukan untuk mendukung IRQ.
Artikel ini tidak menjelaskan implementasi metode skel_gpio_irq_unmask, skel_gpio_irq_mask, dan skel_gpio_irq_set_type karena dua alasan. Pertama, metode ini mudah diterapkan. Kedua, tergantung pada perangkat keras. Mereka bertanggung jawab untuk mengizinkan atau menonaktifkan interupsi untuk peristiwa tertentu yang didukung oleh pengendali GPIO.
Tolong, jika Anda menemukan kesalahan / ketidakakuratan, atau Anda memiliki sesuatu untuk ditambahkan, tulis di PM atau di komentar.
Terima kasih atas perhatian anda!