Linuxデバイスドライバー(3) --LEDキャラクタードライバー

コード学習教材は以下から提供されます。

講義4.1 Linux LEDランプ駆動実験(レジスタ直接操作) - アドレスマッピング_哔哩哔哩_bilibili

個人的な研究/レビュー用にのみ、侵入は削除されます

1. LEDの駆動原理

IMX6ULL シリーズ チップは、ARM Cortex A7 コアをベースとした低電力、高性能、低コストのアプリケーション プロセッサです。

Linux 環境での周辺機器ドライバは最終的に対応するハードウェア レジスタを設定する必要があるため、この章の LED ライト ドライバは最終的に IMX6ULL の IO ポート用に設定されます。Linux 環境でドライバを作成するには、Linux ドライバ フレームワークに準拠する必要があります。IMX6U-ALPHA 開発ボードIMX6ULL の LED は GPIO_IO3 のこのピンに接続されています。

cat /proc/devices はすべてのデバイスを表示しますが、デバイスを操作するには、対応する /dev ディレクトリ内のデバイス ノードを制御する必要があります。

2. アドレスマッピング

Linux ドライバー開発では、レジスタ アドレスを直接読み書きすることはできません。たとえば、レジ​​スタ A の物理アドレスは 0x01010101 です。ベアメタル開発は物理アドレスで直接操作できますが、Linux ではできません。Linux では MMU が有効になり、MMU の正式名はメモリ マネージャー ユニット、つまりメモリ管理ユニットであるためです。古いバージョンの Linux では、プロセッサに MMU が必要でしたが、現在、Linux カーネルはすでに MMU なしのプロセッサをサポートしています。MMU の主な機能は次のとおりです。

1)仮想空間から物理空間へのマッピング (アドレス マッピングとも呼ばれます) を完了します。

2) メモリ保護、メモリの保護権限を設定します

仮想アドレス範囲が物理アドレス範囲よりも大きいという問題について詳しく説明する必要はありません。

Linux のすべての操作は仮想アドレスであるため、最初に仮想アドレス 0x01010101 を取得する必要があります。たとえば、IMX6ULL の GPIO_IO03 ピンの多重化レジスタ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 のアドレスは 0X020E0068 です。MMU が有効になっていない場合は、0X020E0068 レジスタにデータを直接書き込み、GPIO1_IO03 の多重化機能を設定します。MMU がオンになったので、Linux システムの物理アドレス 0X020E0068 に対応する仮想アドレスを取得する必要があります。これには物理メモリと仮想メモリ間の変換が含まれ、 ioremap と iounmap の 2 つの関数を取得する必要があります

アーチ/arm64/include/asm/io.h

#define ioremap(addr, size)             __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
static inline void __iomem *__ioremap(unsigned long port, unsigned long size,
                                      unsigned long flags)
{
        return ioremap(port, size);
}
参数说明:
addr:起始地址
size:要映射的内存空间大小
flags:ioremap类型,选择MT_DEVICE

返回值:__iomem类型的指针,指向映射后的虚拟地址首地址

#define iounmap                         __iounmap
extern void __iounmap(volatile void __iomem *addr);

次の 2 つの関数を例として取り上げます。

#define SW_MUX_GPIO1_IO03_BASE 0X020E0068
static void __iomem *SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
...
iounmap(SW_MUX_GPIO1_IO03_BASE);

宏SW_MUX_GPIO1_IO03_BASE是寄存器物理地址,SW_MUX_GPIO1_IO03是映射后的虚拟地址。对于IMX6ULL的一个寄存器是4字节的,所以映射内存的长度为4,映射完成以后就可以直接对SW_MUX_GPIO1_IO03进行读写操作即可。

卸载驱动时需要iounmap释放虚拟地址映射

3. LEDライトキャラクター駆動

STM32 で IO を設定する場合は、次の点を行う必要があります。

1) 指定した GPIO のクロックを有効にします。

2) 出力機能、プルアップ、速度などの GPIO を初期化します。

3) STM32 の一部の IO ポートは、他のペリフェラルのピンとして使用することができます (IO マルチプレクス)。IO を他のペリフェラルのピンとして使用する場合は、IO のマルチプレクス機能を設定する必要があります。

4) 最後に、GPIO がハイレベルまたはローレベルを出力するように設定します。

GPIO の各レジスタの意味と機能については、以下を参照してください。

LEDライトを点灯させるIMX6ULLベアメタル開発 - Swiler's Blog - CSDN Blog

GPIO ブロック図には、次の 8 つのレジスタがあります。

データ レジスタ (GPIO_DR): データ レジスタ (32 ビット)。GPIO グループには最大 32 個の IO があるため、DR レジスタの各ビットは GPIO に対応します。

GPIO 方向レジスタ (GPIO_GDIR): 入力と出力を構成するための方向レジスタ (32 ビット)。0: 入力、1: 出力。

パッド サンプル レジスタ (GPIO_PSR): ステータス レジスタ (32 ビット)。GPIO の高レベル値と低レベル値を取得するために使用されます。

割り込み制御レジスタ (GPIO_ICR1、GPIO_ICR2): 割り込み制御レジスタ。ICR1 は下位 16 個の GPIO を構成し、ICR2 は上位 16 個の GPIO を構成します。

2 ビットは 4 つのトリガー状態に対応します: 00: ローレベルトリガー、01: ハイレベルトリガー、10: 立ち上がりエッジトリガー、11: 立ち下がりエッジトリガー。

エッジ選択レジスタ (GPIO_EDGE_SEL): エッジ割り込みレジスタ (32 ビット) を設定します。このレジスタを設定すると ICR1 および ICR2 の設定が上書きされ、1 に設定するとダブルエッジ トリガになります。

割り込みマスク レジスタ (GPIO_IMR): 割り込みマスク レジスタ (32 ビット)。割り込みを有効または無効にするために使用されます。

割り込みステータス レジスタ (GPIO_ISR): 割り込みフラグ ビット レジスタ (32 ビット)。GPIO 割り込みが発生している限り、ISR の対応するビットが 1 に設定されます。このレジスタの値を読み取ることで、割り込みが生成されたかどうかを判断できますが、割り込みの処理後は必ず手動で割り込みフラグ ビットをクリアすること、つまり ISR の対応するビットに 1 を書き込むことを忘れないでください。

照明制御が必要なレジスタは以下のとおりです。

#define CCM_CCGR1_BASE (0X020C406C)     // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)     // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)     // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)     // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO
#define GPIO1_GDIR_BASE  (0X0209C004)   // 配置输入输出。0:输入,1:输出

4. I/Oメモリアクセス機能

ioremap 関数を使用してレジスタの物理アドレスを仮想アドレスにマップした後、ポインタを介してこれらのアドレスに直接アクセスできますが、Linux ではこれを推奨しませんが、マップされたメモリの読み書きに一連の操作関数を使用することを推奨します。

1) リードオペレーション機能

extern inline u8 readb(const volatile void __iomem *addr)
extern inline u16 readw(const volatile void __iomem *addr)
extern inline u32 readl(const volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit读操作,参数addr就是要读取内存地址,返回值为读到的数据

2) ライト操作機能

extern inline void writeb(u8 b, volatile void __iomem *addr)
extern inline void writew(u16 b, volatile void __iomem *addr)
extern inline void writel(u32 b, volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit写操作,参数value表示要写入的数值,addr是写入的地址

5. LEDキャラクタードライバーデモ

LED.C

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>

#define LED_MAJOR 200
#define LED_NAME "led"

/* register physical address */
#define CCM_CCGR1_BASE (0X020C406C)        // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)        // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)        // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)        // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO,低电平0 led亮
#define GPIO1_GDIR_BASE  (0X0209C004)        // 方向寄存器,设置为输入还输出。0:输入,1:输出

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0;        // 关闭
#define LEDON 1;        // 打开

static ssize_t led_write(struct file *file,
                                char __user *buf,
                                     size_t count, loff_t *offset)
{
        int retval;
        unsigned char databuf[1];
        retval = copy_from_user(databuf, buf, count);
        if (retval < 0) {
                printk("kernel write failed! \n");
                return -EFAULT;
        }

        if (databuf[1] == LEDOFF) {
                val = readl(GPIO1_DR);
                val |= (1 << 3);        // bit3置1,关闭led
                writel(val, GPIO1_DR);
        }

        if (databuf[1] == LEDON) {
                val = readl(GPIO1_DR);
                val &= ~(1 << 3);        // bit3置0,打开led
                writel(val, GPIO1_DR);
        }

        return 0;
}

static int led_open(struct inode *inode, struct file *file)
{
        printk("led_open success \n");
        return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
        printk("led close success \n");
        return 0;
}

static const struct file_operations led_fops = {
        .owner = THIS_MODULE,
        .write = led_write,
        .open = led_open,
        .release = led_release,
};

static int __init led_init(void)
{
        int ret = 0;
        int val = 0;

        /*init led, address remap*/
        IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
        SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
        SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
        GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
        GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

        val = readl(IMX6U_CCM_CCGR1);
        val &= ~(3 << 26);        // 先清除以前的配置bit26和bit27
        val |= 3 << 26;                // bit26,bit27置1
        writel(val, IMX6U_CCM_CCGR1);

        writel(0x5, SW_MUX_GPIO1_IO03);                // 设置复用
        writel(0x1080, SW_PAD_GPIO1_IO03);        // 设置电气属性

        val = readl(GPIO1_GDIR);
        val |= 1 << 3;                // bit3置1,设置为输出
        writel(val, GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val &= ~(1 << 3);        // bit3清零,打开led
        writel(val, GPIO1_DR);

        ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
        if (ret < 0) {
                printk("led register failed \n");
                return -EIO;
        }

        printk("led_init \n");
        return 0;
}

static void __exit led_exit(void)
{
        int val = 0;

        iounmap(IMX6U_CCM_CCGR1);
        iounmap(SW_MUX_GPIO1_IO03);
        iounmap(SW_PAD_GPIO1_IO03);
        iounmap(GPIO1_DR);
        iounmap(GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val |= (1 << 3);        // bit3置1,关闭led
        writel(val, GPIO1_DR);

        unregister_chrdev(LED_MAJOR, LED_NAME);

        printk("led_exit \n");
}

module_init(led_init);
module_exit(led_exit);

LEDAPP.c

#include <sys/types.h>                                                                                                                                                                                      
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 * ./ledAPP <filename> <1/2>
 * 1 读数据
 * 2 写数据
 * */ 

#define LEDOFF 0
#define LEDON 1

int main(int argc, int *argv[])
{
        char *filename;
        int fd;
        int ret;
        unsigned char databuf[1];
        
        filename = argv[1];

        if (argc < 3) {
                printf("usage error \n");
        }

        fd = open(filename, O_RDWR);
        if (fd < 0) {
                printf("open %s failed\n", filename);
                return -1;
        }

        databuf[0] = atoi(argv[2]);
        ret = write(fd, databuf, 1);            // 打开led灯
        if (ret < 0) {
                printf("LED control failed \n");
        }   

        ret = close(fd);
        if (ret < 0) {
                printf("close %s failed\n", filename);
                return -1; 
        }   

        return 0;
}

おすすめ

転載: blog.csdn.net/qq_58550520/article/details/129162807
おすすめ