当前位置: 首页 > news >正文

【Linux-2】字符设备编写不同模板

引言

在嵌入式Linux开发中,LED驱动是最基础也是最常见的驱动程序之一。本文将详细介绍三种不同的LED驱动实现方式:手动注册字符设备、自动注册混杂设备和使用Platform总线。

1.手动注册字符设备

1.1 实现原理

手动注册是最基础的驱动开发方式,需要开发者手动完成设备号分配、字符设备注册、设备类创建和设备节点生成等所有步骤。

LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上

1、地址映射

物理内存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap。

1.1、ioremap 函数

#define ioremap(cookie,size)        __arm_ioremap((cookie), (size), MT_DEVICE)        void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)                 
{                                                                               return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));                                           
}                              

2.1、iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射

void iounmap (volatile void __iomem *addr) 

2. I/O 内存访问函数

使用 ioremap 函数将寄存器的物 理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

2.1、读操作函数

#define readb(c)        ({ u8  __v = readb_relaxed(c); __iormb(); __v; })       
#define readw(c)        ({ u16 __v = readw_relaxed(c); __iormb(); __v; })       
#define readl(c)        ({ u32 __v = readl_relaxed(c); __iormb(); __v; })                                                                                

readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要 读取写内存地址,返回值就是读取到的数据。

2.2、写操作函数

#define writeb(v,c)     ({ __iowmb(); writeb_relaxed(v,c); })                   
#define writew(v,c)     ({ __iowmb(); writew_relaxed(v,c); })                   
#define writel(v,c)     ({ __iowmb(); writel_relaxed(v,c); })

writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要 写入的数值,addr 是要写入的地址。

3. 驱动代码实现

驱动代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>#define MAJOR_NUM 255
#define MINOR_NUM 0
#define DEV_NAME "led"
#define LED_IOMUXC 0X020E0068
#define LED_PAD_CTL 0X020E02f4
#define LED_GDIR 0x0209C004
#define LED_DR 0x0209C000// 寄存器映射指针static volatile unsigned long *imuxrcsw;  // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器(复用功能选择)
static volatile unsigned long *imuxrcpad; // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器(电气特性配置)
static volatile unsigned long *gpiodir;   // GPIO1_GDIR 寄存器(方向控制)
static volatile unsigned long *gpiodat;   // GPIO1_DR 寄存器(数据寄存器)static dev_t dev;
static struct cdev cdev;
static struct class *pclass = NULL;
static struct device *pdevice = NULL;static void init_led(void)
{*imuxrcsw = 0x05;*imuxrcpad = 0x10b0;*gpiodir |= (1 << 3);*gpiodat |= (1 << 3);
}static void led_on(void)
{printk("led_on   ...\n");*gpiodat &= ~(1 << 3);
}static void led_off(void)
{printk("led_off  ...\n");*gpiodat |= (1 << 3);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static int __init led_init(void)
{int ret = 0;pr_info("led_init >>>>>>>\n");dev = MKDEV(MAJOR_NUM, MINOR_NUM);              // 设备号ret = register_chrdev_region(dev, 1, DEV_NAME); // 注册设备号if (ret < 0){pr_info("fail to register_chrdev_region\n");goto err_register_chrdev_region;}cdev_init(&cdev, &fops);       // 初始化一个cdev 结构体ret = cdev_add(&cdev, dev, 1); // 添加一个字符设备进系统if (ret < 0){pr_info("fail to cdev_add\n");goto err_cdev;}// 创建一个类结构体pclass = class_create(THIS_MODULE, "led");if (pclass == NULL){pr_info("fail to class_create\n");goto err_class_create;}// 创建一个设备节点并注册进sysfspdevice = device_create(pclass, NULL, dev, NULL, "led");if (pdevice == NULL){pr_info("fail to device_create\n");goto err_device_create;}imuxrcsw = ioremap(LED_IOMUXC, 4);imuxrcpad = ioremap(LED_PAD_CTL, 4);gpiodir = ioremap(LED_GDIR, 4);gpiodat = ioremap(LED_DR, 4);printk("###########################################   led_init!\n");return 0;err_device_create:device_destroy(pclass, dev);
err_class_create:class_destroy(pclass);err_cdev:cdev_del(&cdev);
err_register_chrdev_region:unregister_chrdev_region(dev, 1);return ret;
}static void __exit led_exit(void)
{iounmap(gpiodat);iounmap(gpiodir);iounmap(imuxrcpad);iounmap(imuxrcsw);device_destroy(pclass, dev);class_destroy(pclass);cdev_del(&cdev);unregister_chrdev_region(dev, 1);printk("###########################################   led_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

Makefile

# 定义目标模块名称(编译后生成 led_drv.ko)
target=led_drv# 指定要编译的内核模块:
# obj-m 表示编译为可加载模块
# += $(target).o 表示源文件是 led_drv.c(自动推导)
obj-m+=$(target).o# 指定内核源码目录路径(需要根据实际环境修改)
kerdir=/home/qq/linux_imx6ull/linux-imx# 设置交叉编译环境变量(针对ARM架构)
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-# 获取当前目录路径(使用shell命令)
curdir=$(shell pwd)# 默认构建目标
all:# 调用内核构建系统编译模块:# -C $(kerdir)    切换到内核源码目录# M=$(curdir)     指定模块源码位置(当前目录)# ARCH=$(ARCH)    指定目标架构# CROSS_COMPILE...指定交叉编译器前缀# modules         编译模块目标make -C $(kerdir) M=$(curdir) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules# 将编译好的模块复制到NFS共享目录(方便开发板测试)cp $(target).ko /home/qq/nfs/rootfs/# 声明伪目标(避免与同名文件冲突)
.PHONY:
clean:# 清理编译生成的文件:# *.o   - 目标文件# *.ko  - 内核模块# *.order, *.symvers - 内核构建系统生成的文件# *.mod.c - 模块中间文件rm -rf *.o *.ko *.order *.symvers *.mod.c

应用层代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int fd = 0;char tmpbuff[256] = {0};fd = open("/dev/led", O_RDWR);if (fd == -1){perror("fail to open\n");return -1;}while (1){sprintf(tmpbuff, "led_on");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);sprintf(tmpbuff, "led_off");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);}close(fd);return 0;
}

Makefile

OBJ=led_appOBJS+=main.c# 设置交叉编译环境变量(针对ARM架构)
CC = arm-linux-gnueabihf-gcc$(OBJ):$(OBJS)$(CC) $^ -o $@cp $(OBJ) ~/nfs/rootfs.PHONY:
clean:rm $(OBJ)

为了方便编译在顶层目录书写Makefile

qq@ubuntu:~/linux_imx6ull/imx/demo0$ ls
demo_app  demo_drv  Makefile

Makefile

appdir=demo_app
drvdir=demo_drvall:make -C $(appdir)make -C $(drvdir).PHONY:
clean:make -C $(appdir) cleanmake -C $(drvdir) clean

4. 运行结果

/ # insmod led_drv.ko 
[ 3286.455050] led_init >>>>>>>
[ 3286.458871] ###########################################   led_init!
/ # ls /dev/led 
/dev/led
/ # cat /proc/device
device-tree/  devices
/ # cat /proc/devices led
Character devices:
255 led1 mem4 /dev/vc/0/ # ./led_app 
[ 3430.497347] kernel open  led
[ 3430.500285] data: led_on
[ 3430.502825] led_on   ...
[ 3430.505532] kernel write  led
[ 3431.508699] data: led_off
[ 3431.511341] led_off  ...
[ 3431.513911] kernel write  led
[ 3432.516999] data: led_on
[ 3432.519548] led_on   ...
[ 3432.522085] kernel write  led
[ 3433.525192] data: led_off
[ 3433.527826] led_off  ...
[ 3433.530363] kernel write  led
^C[ 3433.690538] led_off  ...
[ 3433.693089] kernel close led/ # rmmod led_drv.ko 
[ 3437.419421] ###########################################   led_exit!
/ # 

2.自动注册(混杂注册)

2.1 驱动原理

混杂设备(miscdevice)是Linux内核提供的一种简化设备注册机制。主设备号固定为10,次设备号动态分配,大大简化了设备注册过程。

注册一个混杂设备,即不需要注册设备节点,混杂设备的主设备号是10。

/ # cat proc/devices
Character devices:1 mem4 /dev/vc/04 tty5 /dev/tty5 /dev/console5 /dev/ptmx7 vcs10 misc13 input29 fb81 video4linux89 i2c

2.2 优缺点分析

​优点:​

  • 代码简洁,注册过程简单

  • 主设备号统一管理

  • 适合简单的字符设备

​缺点:​

  • 灵活性较低

  • 所有混杂设备共享主设备号10

2.3 驱动代码实现

2.3.1 关键代码实现

#include <linux/miscdevice.h>// 混杂设备定义
static struct miscdevice misc_led = {.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号.name = "led",               // 设备名称.fops = &fops,               // 文件操作
};static int __init misc_led_init(void)
{int ret;// 注册混杂设备(一行代码完成设备注册)ret = misc_register(&misc_led);if (ret) {printk(KERN_ERR "Failed to register misc device\n");return ret;}// ... 硬件初始化部分与手动注册相同 ...
}module_init(misc_led_init);

2.3.2 代码实现

驱动代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>#define DEV_NAME "led"
#define LED_IOMUXC 0X020E0068
#define LED_PAD_CTL 0X020E02f4
#define LED_GDIR 0x0209C004
#define LED_DR 0x0209C000// 寄存器映射指针static volatile unsigned long *imuxrcsw;  // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器(复用功能选择)
static volatile unsigned long *imuxrcpad; // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器(电气特性配置)
static volatile unsigned long *gpiodir;   // GPIO1_GDIR 寄存器(方向控制)
static volatile unsigned long *gpiodat;   // GPIO1_DR 寄存器(数据寄存器)static void init_led(void)
{*imuxrcsw = 0x05;*imuxrcpad = 0x10b0;*gpiodir |= (1 << 3);*gpiodat |= (1 << 3);
}static void led_on(void)
{printk("led_on   ...\n");*gpiodat &= ~(1 << 3);
}static void led_off(void)
{printk("led_off  ...\n");*gpiodat |= (1 << 3);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static struct miscdevice misc_device = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops,
};static int __init led_init(void)
{int ret = 0;pr_info("led_init >>>>>>>\n");// 创建一个混杂设备ret = misc_register(&misc_device);if (ret < 0){goto err_misc_register;}imuxrcsw = ioremap(LED_IOMUXC, 4);imuxrcpad = ioremap(LED_PAD_CTL, 4);gpiodir = ioremap(LED_GDIR, 4);gpiodat = ioremap(LED_DR, 4);printk("###########################################   led_init!\n");return 0;err_misc_register:ret = misc_deregister(&misc_device);return ret;
}static void __exit led_exit(void)
{iounmap(gpiodat);iounmap(gpiodir);iounmap(imuxrcpad);iounmap(imuxrcsw);misc_deregister(&misc_device);printk("###########################################   led_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

3. platform 总线

3.1 驱动原理

Platform总线是Linux内核中用于连接片上外设的虚拟总线。采用"驱动"与"设备"分离的设计理念,通过设备树描述硬件资源,实现驱动与硬件的解耦。

Linux 中的总线(bus)、驱动(driver)和 设备(device)模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线 搭桥,如图所示:

当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配 的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。

3.2 优缺点分析

​优点:​

  • 驱动与硬件分离,可移植性强

  • 支持设备树,硬件配置灵活

  • 符合Linux驱动模型标准

​缺点:​

  • 代码结构复杂

  • 需要设备树知识

  • 开发调试难度较大

3.3 platform 平台驱动模型简介

设备驱动的分离,包含总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、SPI、USB 等总线。但是soc有些外设没有总线的概念,但是又要使用总线、驱动和设备模型,为此 Linux 提出了 platform 这个虚拟总线,相应 的就有 platform_driver 和 platform_device。

3.3.1. platform 总线

Linux系统内核使用bus_type 结构体表示总线此结构体定义在文件 include/linux/device.h中

/*** struct bus_type - 设备的总线类型** @name:   总线的名称。* @dev_name:   用于子系统枚举设备(例如 ("foo%u", dev->id))。* @dev_root:   用作父设备的默认设备。* @dev_attrs:  总线上设备的默认属性。* @bus_groups: 总线的默认属性。* @dev_groups: 总线上设备的默认属性。* @drv_groups: 总线上设备驱动程序的默认属性。* @match:  每当有新的设备或驱动程序添加到该总线时被调用(可能多次调用)。如果给定的驱动程序可以处理给定的设备,它应返回非零值。* @uevent: 当设备被添加、移除或发生其他一些会生成 uevent 的事件时被调用,用于添加环境变量。* @probe:  当新的设备或驱动程序添加到该总线时被调用,并回调特定驱动程序的 probe 函数来初始化匹配的设备。* @remove: 当设备从该总线移除时被调用。* @shutdown:   在关机时被调用以使设备静默(停止活动)。** @online: 被调用以使设备重新上线(在将其下线之后)。* @offline:    被调用以使设备下线(用于热移除)。可能会失败。** @suspend:    当该总线上的设备想要进入睡眠模式时被调用。* @resume: 被调用以使该总线上的设备退出睡眠模式。* @pm:     该总线的电源管理操作,回调特定设备驱动程序的 pm-ops。* @iommu_ops:  该总线的 IOMMU 特定操作,用于将 IOMMU 驱动程序实现附加到总线,并允许驱动程序执行总线特定的设置。* @p:      驱动程序核心的私有数据,只有驱动程序核心可以访问。* @lock_key:   用于锁验证器的锁类键。** 总线(bus)是处理器与一个或多个设备之间的通道。就设备模型(device model)而言,所有设备都通过总线连接,即使它是内部的、虚拟的“平台(platform)”总线。总线可以相互插入。例如,USB 控制器通常是一个 PCI 设备。设备模型代表了总线与其控制的设备之间的实际连接。* 总线由 bus_type 结构体表示。它包含名称、默认属性、总线的方法(函数)、电源管理(PM)操作以及驱动程序核心的私有数据。*/
struct bus_type {                                                               const char      *name;                                                      const char      *dev_name;                                                  struct device       *dev_root;                                              struct device_attribute *dev_attrs; /* use dev_groups instead */            const struct attribute_group **bus_groups;                                  const struct attribute_group **dev_groups;                                                                                                                                                            const struct attribute_group **drv_groups;                                  int (*match)(struct device *dev, struct device_driver *drv);                int (*uevent)(struct device *dev, struct kobj_uevent_env *env);             int (*probe)(struct device *dev);                                           int (*remove)(struct device *dev);                                          void (*shutdown)(struct device *dev);                                       int (*online)(struct device *dev);                                          int (*offline)(struct device *dev);                                         int (*suspend)(struct device *dev, pm_message_t state);                     int (*resume)(struct device *dev);                                          const struct dev_pm_ops *pm;                                                const struct iommu_ops *iommu_ops;                                          struct subsys_private *p;                                                   struct lock_class_key lock_key;                                             
};

match 函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

match 函数定义在文件 drivers/base/platform.c 中

static int platform_match(struct device *dev, struct device_driver *drv)                  
{                                                                               struct platform_device *pdev = to_platform_device(dev);                     struct platform_driver *pdrv = to_platform_driver(drv);                     /* When driver_override is set, only bind to the matching driver */         if (pdev->driver_override)                                                  return !strcmp(pdev->driver_override, drv->name);                       /* Attempt an OF style match first */                                       if (of_driver_match_device(dev, drv))                                       return 1;                                                               /* Then try ACPI style match */                                             if (acpi_driver_match_device(dev, drv))                                     return 1;                                                               /* Then try to match against the id table */                                if (pdrv->id_table)                                                         return platform_match_id(pdrv->id_table, pdev) != NULL;                 /* fall-back to driver name match */                                        return (strcmp(pdev->name, drv->name) == 0);                                
}     

驱动和设备的四种匹配方式:

  1. 设备中的driver_override与驱动名字相同。
  2. 驱动中的of_match_table与设备名相同。OF 类型的匹配,也就是设备树采用的匹配方式,device_driver 结构体(表示 设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表, 设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较
  3. ACPI匹配方式(PC架构平台)。
  4. 驱动中的id_tables与设备名相同。每个 platform_driver 结构体有一个 id_table 成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱 动类型。
  5. 驱动名与设备名相同匹配。直接比较驱动和设备的 name 字段

一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第2种匹配方式一般都会存在,第4种和第5种只要存在一种就可以,一般用的最多的还是第5种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

3.3.2 platform 驱动

platform_driver 结构体表示 platform 驱动 , 此结构体定义在文件 include/linux/platform_device.h 中

/*** struct platform_driver - 平台设备驱动结构体* * 用于描述与平台总线(platform bus)关联的设备驱动,管理片上系统(SoC)中的集成设备。* 驱动开发者需实现关键回调函数以处理设备初始化、资源管理、电源控制等。*/
struct platform_driver {/*** @probe: 设备探测回调函数* - 功能: 当设备与驱动匹配时调用,负责硬件初始化、资源分配(如寄存器映射、中断申请)。* - 参数: platform_device * - 指向匹配的平台设备,包含设备资源(寄存器地址、IRQ 等)。* - 返回: 成功返回 0;失败返回负错误码(如 -ENOMEM、-EINVAL)。* - 示例场景: 在 probe 中调用 devm_ioremap_resource() 映射寄存器,并注册字符设备。*/int (*probe)(struct platform_device *);/*** @remove: 设备移除回调函数* - 功能: 当设备移除或驱动卸载时调用,释放所有资源(如内存、中断)。* - 参数: platform_device * - 指向要移除的设备。* - 返回: 成功返回 0;失败返回负错误码。* - 注意: 必须与 probe 对称,避免资源泄漏。*/int (*remove)(struct platform_device *);/*** @shutdown: 设备关闭回调函数* - 功能: 系统关机时调用,确保设备安全进入休眠状态(如关闭电源、保存状态)。* - 参数: platform_device * - 指向要关闭的设备。* - 注意: 此函数不允许失败,需无条件执行。*/void (*shutdown)(struct platform_device *);/*** @suspend: 设备挂起回调函数(电源管理)* - 功能: 系统进入低功耗状态(如睡眠)前调用,保存设备状态并降低功耗。* - 参数: *   - platform_device * - 指向要挂起的设备。*   - pm_message_t state - 目标电源状态(如 PMSG_SUSPEND)。* - 返回: 成功返回 0;失败返回负错误码。*/int (*suspend)(struct platform_device *, pm_message_t state);/*** @resume: 设备恢复回调函数(电源管理)* - 功能: 系统从低功耗状态恢复后调用,恢复设备运行状态。* - 参数: platform_device * - 指向要恢复的设备。* - 返回: 成功返回 0;失败返回负错误码。*/int (*resume)(struct platform_device *);/*** @driver: 内嵌的通用设备驱动结构体(device_driver)* - 功能: 包含驱动元数据,用于核心驱动管理。* - 关键成员:*   - .name: 驱动名称(字符串,如 "my-device")。*   - .owner: 模块所有者(通常为 THIS_MODULE)。*   - .of_match_table: 设备树兼容性匹配表(用于设备树绑定)。*   - .pm: 电源管理操作集(dev_pm_ops结构体)。* - 注意: 此成员通过 platform_driver_register 自动初始化。*/struct device_driver driver;/*** @id_table: 平台设备 ID 匹配表* - 功能: 在非设备树系统中通过设备名称匹配驱动(如 ACPI 或传统平台数据)。* - 结构: 包含 platform_device_id 数组,以空条目结束。* - 示例: *   static const struct platform_device_id my_id[] = {*       { "my-device", 0 },  // 设备名称和私有数据*       { }  // 结束标记*   };*/const struct platform_device_id *id_table;/*** @prevent_deferred_probe: 延迟探测阻止标志* - 功能: 控制内核在资源不足时的重试行为。* - 值: *   - true: 阻止延迟探测(探测失败后不自动重试)。*   - false: 允许延迟探测(默认值,内核在资源可用时重试)。* - 使用场景: 处理驱动间依赖(如时钟或电源未就绪时避免循环探测)。*/bool prevent_deferred_probe;
};

device_driver 结构体定义在 include/linux/device.h

/*** struct device_driver - 基础设备驱动结构体* * 设备驱动模型(device driver-model)跟踪系统中所有已知的驱动程序。* 主要目的是使驱动核心能够将驱动程序与新设备匹配。* 此外,驱动程序作为系统内已知对象后,还可实现其他功能(如导出独立于特定设备的信息和配置变量)。* * @name: 驱动程序的名称(字符串标识)* @bus: 该驱动所属的总线类型(如 &platform_bus_type)* @owner: 拥有该驱动的模块(通常为 THIS_MODULE)* @mod_name: 内置模块的名称(用于非模块化驱动)* @suppress_bind_attrs: 禁用通过sysfs绑定/解绑操作*     true = 隐藏sysfs中的bind/unbind属性* @of_match_table: 设备树(Open Firmware)匹配表*     包含.compatible字符串列表,用于设备树匹配* @acpi_match_table: ACPI设备匹配表*     用于ACPI系统的设备匹配* @probe: 探测回调函数*     - 调用时机:当设备与驱动匹配时*     - 功能:检查设备存在性,确认驱动兼容性,绑定驱动到设备*     - 参数:struct device *dev - 要探测的设备*     - 返回:成功=0,失败=负错误码* @remove: 移除回调函数*     - 调用时机:设备从系统移除时*     - 功能:解绑设备与驱动*     - 参数:struct device *dev - 要移除的设备* @shutdown: 关闭回调函数*     - 调用时机:系统关机时*     - 功能:使设备静默(停止活动)* @suspend: 挂起回调函数(电源管理)*     - 调用时机:设备进入睡眠模式前*     - 参数:*         struct device *dev - 要挂起的设备*         pm_message_t state - 目标电源状态* @resume: 恢复回调函数(电源管理)*     - 调用时机:设备从睡眠模式唤醒时*     - 参数:struct device *dev - 要恢复的设备* @groups: 默认属性组*     由驱动核心自动创建的默认sysfs属性组* @pm: 电源管理操作集*     指向dev_pm_ops结构的指针,定义设备电源管理方法* @p: 驱动核心私有数据*     仅驱动核心可访问,存储内部管理数据*/
struct device_driver {const char *name;                    // 驱动名称struct bus_type *bus;                // 所属总线struct module *owner;                // 模块所有者const char *mod_name;                // 内置模块名bool suppress_bind_attrs;           // 禁用sysfs绑定属性const struct of_device_id *of_match_table;   // 设备树匹配表const struct acpi_device_id *acpi_match_table; // ACPI匹配表int (*probe)(struct device *dev);    // 设备探测函数int (*remove)(struct device *dev);   // 设备移除函数void (*shutdown)(struct device *dev); // 设备关闭函数int (*suspend)(struct device *dev, pm_message_t state); // 挂起函数int (*resume)(struct device *dev);   // 恢复函数const struct attribute_group **groups; // 属性组数组const struct dev_pm_ops *pm;          // 电源管理操作集struct driver_private *p;             // 驱动核心私有数据
};

 const struct of_device_id *of_match_table;    就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹 配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内 容如下:

首先定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用 platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数 原型如下所示:

#define platform_driver_register(drv) \                                         __platform_driver_register(drv, THIS_MODULE)int __platform_driver_register(struct platform_driver *drv,                          struct module *owner)

驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动, platform_driver_unregister 函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)

platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套 上了一张“platform”的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。

3.3.3 platform 设备

platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。当然了,你如果 一定要用 platform_device 来描述设备信息的话也是可以的。platform_device 结构体定义在文件 include/linux/platform_device.h 中,结构体内容如下:

struct platform_device {                                                        const char  *name;                                                          int     id;                                                                 bool        id_auto;                                                           struct device   dev;                                                        u32     num_resources;                                                      struct resource *resource;                                                  const struct platform_device_id *id_entry;                                  char *driver_override; /* Driver name to force a match */                   /* MFD cell pointer */                                                      struct mfd_cell *mfd_cell;                                                  /* arch specific additions */                                               struct pdev_archdata    archdata;                                           
}; 
  • name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同
  • num_resources 表示资源数量
  • resource 表示资源,也就是设备信息,比如外设寄存器等

resource 结构体表示资源,在文件include/linux/ioport.h中, resource 结构体内容如下:

struct resource {                                                                         resource_size_t start;                                                      resource_size_t end;                                                        const char *name;                                                           unsigned long flags;                                                        struct resource *parent, *sibling, *child;                                  
};
  • start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止 地址
  • name 表示资源名字
  • flags 表示资源类型

可选的资源类型都定义在了文件 include/linux/ioport.h 里面,如下所示:

/*                                                                              * IO resources have these defined flags.                                       */                                                                             
#define IORESOURCE_BITS     0x000000ff  /* Bus-specific bits */                 #define IORESOURCE_TYPE_BITS    0x00001f00  /* Resource type */                 
#define IORESOURCE_IO       0x00000100  /* PCI/ISA I/O ports */                 
#define IORESOURCE_MEM      0x00000200                                          
#define IORESOURCE_REG      0x00000300  /* Register offsets */                  
#define IORESOURCE_IRQ      0x00000400                                          
#define IORESOURCE_DMA      0x00000800                                          
#define IORESOURCE_BUS      0x00001000                                          #define IORESOURCE_PREFETCH 0x00002000  /* No side effects */                   
#define IORESOURCE_READONLY 0x00004000                                          
#define IORESOURCE_CACHEABLE    0x00008000                                      
#define IORESOURCE_RANGELENGTH  0x00010000                                      
#define IORESOURCE_SHADOWABLE   0x00020000                                      #define IORESOURCE_SIZEALIGN    0x00040000  /* size indicates alignment */      
#define IORESOURCE_STARTALIGN   0x00080000  /* start field is alignment */      #define IORESOURCE_MEM_64   0x00100000                                          
#define IORESOURCE_WINDOW   0x00200000  /* forwarded by bridge */               
#define IORESOURCE_MUXED    0x00400000  /* Resource is software muxed */        #define IORESOURCE_EXCLUSIVE    0x08000000  /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000                                          
#define IORESOURCE_UNSET    0x20000000  /* No address assigned yet */           
#define IORESOURCE_AUTO     0x40000000                                          
#define IORESOURCE_BUSY     0x80000000  /* Driver has marked this resource busy */

在不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息, 然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

通过 platform_device_unregister 函数注销掉相应的 platform 设备,platform_device_unregister 函数原型如下:

void platform_device_unregister(struct platform_device *pdev) 

3.3 驱动代码实现

demo_device.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>#define DEV_NAME "led"
#define LED_IOMUXC 0X020E0068
#define LED_PAD_CTL 0X020E02f4
#define LED_GDIR 0x0209C004
#define LED_DR 0x0209C000static struct resource res[4] = {[0] = {.start = LED_IOMUXC,.end = LED_IOMUXC + 4 - 1,.name = "led_iomuxc",},[1] = {.start = LED_PAD_CTL,.end = LED_PAD_CTL + 4 - 1,.name = "led_pad_ctl",},[2] = {.start = LED_GDIR,.end = LED_GDIR + 4 - 1,.name = "led_gdir",},[3] = {.start = LED_DR,.end = LED_DR + 4 - 1,.name = "led_dr",},
};void release(struct device *dev)
{printk("###########################################   led_release!\n");
}static struct platform_device pdev = {.name = DEV_NAME,.id = -1,.dev = {.release = release,},.num_resources = sizeof(res) / sizeof(res[0]),.resource = res,
};static int __init led_init(void)
{int ret = 0;pr_info("led_device_init >>>>>>>\n");ret = platform_device_register(&pdev);if (ret < 0){goto err_platfrom_device_register;}printk("###########################################   led_device_init!\n");return 0;err_platfrom_device_register:platform_device_unregister(&pdev);return ret;
}static void __exit led_exit(void)
{platform_device_unregister(&pdev);printk("###########################################   led_device_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

demo_driver.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>#define DEV_NAME "led"// 寄存器映射指针
static volatile unsigned long *imuxrcsw;  // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器(复用功能选择)
static volatile unsigned long *imuxrcpad; // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器(电气特性配置)
static volatile unsigned long *gpiodir;   // GPIO1_GDIR 寄存器(方向控制)
static volatile unsigned long *gpiodat;   // GPIO1_DR 寄存器(数据寄存器)static void init_led(void)
{*imuxrcsw = 0x05;*imuxrcpad = 0x10b0;*gpiodir |= (1 << 3);*gpiodat |= (1 << 3);
}static void led_on(void)
{printk("led_on   ...\n");*gpiodat &= ~(1 << 3);
}static void led_off(void)
{printk("led_off  ...\n");*gpiodat |= (1 << 3);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static struct miscdevice misc_device = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops,
};static int probe(struct platform_device *pdev)
{int ret = 0;// 创建一个混杂设备ret = misc_register(&misc_device);if (ret < 0){goto err_misc_register;}imuxrcsw = ioremap(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start - 1);imuxrcpad = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start - 1);gpiodir = ioremap(pdev->resource[2].start, pdev->resource[2].end - pdev->resource[2].start - 1);gpiodat = ioremap(pdev->resource[3].start, pdev->resource[3].end - pdev->resource[3].start - 1);printk("###########################################   led_probe!\n");return 0;err_misc_register:ret = misc_deregister(&misc_device);return ret;
}
static int remove(struct platform_device *dev)
{iounmap(gpiodat);iounmap(gpiodir);iounmap(imuxrcpad);iounmap(imuxrcsw);misc_deregister(&misc_device);printk("###########################################   led_remove!\n");return 0;
}struct platform_driver drv = {.probe = probe,.remove = remove,.driver = {.name = DEV_NAME,},
};static int __init led_init(void)
{int ret = 0;pr_info("led_init >>>>>>>\n");ret = platform_driver_register(&drv);if (ret == -1){goto err_platform_driver_register;}printk("###########################################   led_driver_init!\n");return 0;err_platform_driver_register:platform_driver_unregister(&drv);return ret;
}static void __exit led_exit(void)
{platform_driver_unregister(&drv);printk("###########################################   led_driver_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

Makefile

# 定义目标模块名称
target_device = demo_device
target_driver = demo_driver# 指定要编译的内核模块
obj-m += $(target_device).o
obj-m += $(target_driver).o# 指定内核源码目录路径
kerdir = /home/qq/linux_imx6ull/linux-imx# 设置交叉编译环境变量
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-# 获取当前目录路径
curdir = $(shell pwd)# 默认构建目标
all:make -C $(kerdir) M=$(curdir) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modulescp $(target_device).ko /home/qq/nfs/rootfs/cp $(target_driver).ko /home/qq/nfs/rootfs/.PHONY:
# 清理目标
clean:make -C $(kerdir) M=$(curdir) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) cleanrm -f /home/qq/nfs/rootfs/$(target_device).korm -f /home/qq/nfs/rootfs/$(target_driver).ko

用户层代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int fd = 0;char tmpbuff[256] = {0};fd = open("/dev/led0", O_RDWR);if (fd == -1){perror("fail to open\n");return -1;}while (1){sprintf(tmpbuff, "led_on");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);sprintf(tmpbuff, "led_off");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);}close(fd);return 0;
}

Makefile

OBJ=led_appOBJS+=main.c# 设置交叉编译环境变量(针对ARM架构)
CC = arm-linux-gnueabihf-gcc$(OBJ):$(OBJS)$(CC) $^ -o $@cp $(OBJ) ~/nfs/rootfs.PHONY:
clean:rm $(OBJ)

顶层目录Makefile

qq@ubuntu:~/linux_imx6ull/imx/demo2$ ls
demo_app  demo_drv  Makefile
appdir=demo_app
drvdir=demo_drvall:make -C $(appdir)make -C $(drvdir).PHONY:
clean:make -C $(appdir) cleanmake -C $(drvdir) clean

4. Linux 设备树

4.1 什么是设备树?

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等

4.2 DTS、DTB 和 DTC

  • DTS 是设备树源码文件
  • DTB 是将 DTS 编译以后得到的二进制文件
  • 将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb 需要什么工具呢?需要用到 DTC 工具

如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命 令:

make all 或make dtbs

“make all”命令是编译 Linux 源码中的所有东西,包括 zImage,.ko 驱动模块以及设备 树,如果只是编译设备树的话建议使用“make dtbs”命令。

4.3 DTS 语法

4.3.1 .dtsi 头文件

和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在 imx6ull-alientekemmc.dts 中有如下所示内容:

#include <dt-bindings/input/input.h>                                            
#include "imx6ull.dtsi"  

一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范 围,比如 UART、IIC 等等。

4.3.2 设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设 备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。

1. “/”是根节点,每个设备树文件只有一个根节点。imx6ull.dtsi 和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为 这两个“/”根节点的内容会合并成一个根节点。

2. aliases、cpus 和 intc 是三个子节点,设备树中节点命名格式如下:

node-name@unit-address 

其中“node-name”是节点名字,“unit-address”一般表示设备的地址或寄 存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、 “interrupt-controller@00a01000”。

有的节点命名却如下所示

cpu0:cpu@0

上述命令并不是“node-name@unit-address”这样的格式,而是用“:”隔开成了两部分

“:” 前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示

label: node-name@unit-address 

引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过 &cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。

4.3.3 标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以 自定义属性。除了用户自定义属性,有很多属性是标准属性。

1、compatible 属性

compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible 属性的值是 一个字符串列表,compatible 属性用于将设备和驱动绑定起来。

字符串列表用于选择设备所要 使用的驱动程序,compatible 属性的值格式如下所示:

"manufacturer,model"

其中 manufacturer 表示厂商,model 一般是模块对应的驱动名字。

2、model 属性

model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:

model = "wm8960-audio";

3、status 属性

status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的 状态信息,可选的状态如表所示:

描述
“okay”表明设备是可操作的。
“disabled”表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。
“fail”表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可 操作。
“fail-sss”含义和“fail”相同,后面的 sss 部分是检测到的错误内容。

4、#address-cells 和#size-cells 属性

这两个属性的值都是无符号 32 位整形,用于描述子节点的地址信息。

#address-cells 属性值决定了子节点 reg 属 性中地址信息所占用的字长(32 位)

#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。

reg 属性的格式一为:

reg = <address1 length1 address2 length2 address3 length3……> 

每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用 的字长。

5、reg 属性

reg 属性一般用于描 述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息

4.4 向节点追加或修改内容

4.5 设备树在系统中的体现

Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹,

/ # cd proc//device-tree/
/sys/firmware/devicetree/base # ls
#address-cells                 interrupt-controller@00a01000
#size-cells                    memory
aliases                        model
backlight                      name
chosen                         pxp_v4l2
clocks                         regulators
compatible                     reserved-memory
cpus                           sii902x-reset
gsl_beep                       soc
gsl_key                        sound
gsl_led                        spi4

可以看到 gsl_led 节点,还可以看到节点信息。

/sys/firmware/devicetree/base # cd gsl_led/
/sys/firmware/devicetree/base/gsl_led # ls
#address-cells  compatible      nameqwe         status
#size-cells     name            reg
/sys/firmware/devicetree/base/gsl_led # 

4.6 设备树常用 OF 操作函数

设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的, 我们在编写驱动的时候需要获取到这些信息。Linux 内核给我们提供了一系列的函数来获 取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资 料里面也被叫做 OF 函数。这些 OF 函数原型都定义在 include/linux/of.h 文件中。

1、 查找节点的 OF 函数

设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中,定义如下:

struct device_node {                                                        const char *name;     const char *type;     phandle phandle;     const char *full_name;     struct fwnode_handle fwnode;     struct  property *properties;     struct  property *deadprops;    /* removed properties */     struct  device_node *parent;     struct  device_node *child;     struct  device_node *sibling;     struct  kobject kobj;     unsigned long _flags;     void    *data;     
#if defined(CONFIG_SPARC)     const char *path_component_name;     unsigned int unique_id;     struct of_irq_controller *irq_trans;     
#endif     
};   

与查找节点有关的 OF 函数有 5 个

  • of_find_node_by_path 函数通过路径来查找指定的节点
  • of_find_node_by_name 函数通过节点名字查找指定的节点
  • of_find_node_by_type 函数通过 device_type 属性查找指定的节点
  • of_find_compatible_node 函数根据 device_type 和 compatible 这两个属性查找指定的节点
  • of_find_matching_node_and_match 函数 通过 of_device_id 匹配表来查找指定的节点

2、提取属性值的 OF 函数

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux 内核中使用结构体 property 表示属性,此结构体同样定义在文件 include/linux/of.h 中

struct property {                                                           char    *name;     int length;     void    *value;     struct property *next;     unsigned long _flags;     unsigned int unique_id;     struct bin_attribute attr;     
};  

Linux 内核也提供了提取属性值的 OF 函数:

of_property_read_u8 函数

of_property_read_u16 函数

of_property_read_u32 函数

of_property_read_u64 函数用于读取这种只有一个整形值的属性

of_property_read_string 函数用于读取属性中字符串值

3、其他常用的 OF 函数

of_iomap 函数用于直接内存映射。以前我们会通过 ioremap 函数来完成物理地址到虚拟地 址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址, 不需要使用 ioremap 函数了。当然 ioremap 函数还可以。

void __iomem *of_iomap(struct device_node *np, int index) 

4.7 代码实现

4.7.1 设备树+混杂

1. 设备树编写

在这个 arch/arm/boot/dts/imx6ull-alientek-emmc.dts 文件中

在根节点“/”下创建一个名为“gsl_led”的子节点

    gsl_led {                                                                                    #address-cells = <1>;                                                   #size-cells = <1>;                                                      compatible = "gsl_led";                                                 nameqwe = "gongsilei";                                                  status = "okay";                                                        reg = <                                                                 0x020e0068 0x4                                                  0x020e02f4 0x4                                                  0x0209c004 0x4                                                  0x0209c000 0x4                                                  >;                                                                     };   

设备树修改完成以后输入如下命令重新编译一下

qq@ubuntu:~/linux_imx6ull/linux-imx$ vim arch/arm/boot/dts/imx6ull-alientek-emmc.dts 
qq@ubuntu:~/linux_imx6ull/linux-imx$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbsCHK     include/config/kernel.releaseCHK     include/generated/uapi/linux/version.hCHK     include/generated/utsrelease.h
make[1]: “include/generated/mach-types.h”已是最新。CHK     include/generated/bounds.hCHK     include/generated/asm-offsets.hCALL    scripts/checksyscalls.shDTC     arch/arm/boot/dts/imx6ull-alientek-emmc.dtbDTC     arch/arm/boot/dts/imx6ull-alientek-nand.dtb
qq@ubuntu:~/linux_imx6ull/linux-imx$ cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/tftpboot/
qq@ubuntu:~/linux_imx6ull/linux-imx$ vim arch/arm/boot/dts/imx6ull-alientek-emmc.dts 
qq@ubuntu:~/linux_imx6ull/linux-imx$ 

使用新的 imx6ull-alientek-emmc.dtb 启动 Linux 内核。Linux 启动成功以后进入到/proc/device-tree/目录中查看

/ # lsmod
Module                  Size  Used by    Not tainted
/ # cd /proc/device-tree/
/sys/firmware/devicetree/base # ls
#address-cells                 interrupt-controller@00a01000
#size-cells                    memory
aliases                        model
backlight                      name
chosen                         pxp_v4l2
clocks                         regulators
compatible                     reserved-memory
cpus                           sii902x-reset
gsl_beep                       soc
gsl_key                        sound
gsl_led                        spi4
/sys/firmware/devicetree/base # cd gsl_led/
/sys/firmware/devicetree/base/gsl_led # ls
#address-cells  compatible      nameqwe         status
#size-cells     name            reg

2. 驱动代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_address.h>#define DEV_NAME "led"// 寄存器映射指针
static volatile unsigned long *imuxrcsw;  // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器(复用功能选择)
static volatile unsigned long *imuxrcpad; // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器(电气特性配置)
static volatile unsigned long *gpiodir;   // GPIO1_GDIR 寄存器(方向控制)
static volatile unsigned long *gpiodat;   // GPIO1_DR 寄存器(数据寄存器)static void init_led(void)
{*imuxrcsw = 0x05;*imuxrcpad = 0x10b0;*gpiodir |= (1 << 3);*gpiodat |= (1 << 3);
}static void led_on(void)
{printk("led_on   ...\n");*gpiodat &= ~(1 << 3);
}static void led_off(void)
{printk("led_off  ...\n");*gpiodat |= (1 << 3);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static struct miscdevice misc_device = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops,
};static int __init led_init(void)
{int ret = 0;struct device_node *pdtsdevice_node = NULL;const char *pname = NULL;const char *pcom = NULL;unsigned int reg[8] = {0};pr_info("led_init >>>>>>>\n");// 创建一个混杂设备ret = misc_register(&misc_device);if (ret < 0){goto err_misc_register;}pdtsdevice_node = of_find_node_by_path("/gsl_led");if (pdtsdevice_node == NULL){goto err_of_find_node_by_path;}ret = of_property_read_string(pdtsdevice_node, "compatible", &pcom);if (ret < 0){goto err_of_find_node_by_path;}ret = of_property_read_string(pdtsdevice_node, "nameqwe", &pname);if (ret < 0){goto err_of_find_node_by_path;}ret = of_property_read_u32_array(pdtsdevice_node, "reg", reg, sizeof(reg) / sizeof(reg[0]));if (ret < 0){goto err_of_find_node_by_path;}pr_info("compatible: %s\n", pcom);pr_info("name: %s\n", pname);pr_info("reg[%d] = 0x%X, reg[%d] = %d\n", 0, reg[0], 1, reg[1]);pr_info("reg[%d] = 0x%X, reg[%d] = %d\n", 2, reg[2], 3, reg[3]);pr_info("reg[%d] = 0x%X, reg[%d] = %d\n", 4, reg[4], 5, reg[5]);pr_info("reg[%d] = 0x%X, reg[%d] = %d\n", 6, reg[6], 7, reg[7]);#if 0imuxrcsw = ioremap(reg[0], reg[1]);imuxrcpad = ioremap(reg[2], reg[3]);gpiodir = ioremap(reg[4], reg[5]);gpiodat = ioremap(reg[6], reg[7]);
#elseimuxrcsw = of_iomap(pdtsdevice_node, 0);imuxrcpad = of_iomap(pdtsdevice_node, 1);gpiodir = of_iomap(pdtsdevice_node, 2);gpiodat = of_iomap(pdtsdevice_node, 3);
#endifprintk("###########################################   led_init!\n");return 0;
err_of_find_node_by_path:pr_err("fail to of_find_node_by_path\n");
err_misc_register:ret = misc_deregister(&misc_device);return ret;
}static void __exit led_exit(void)
{iounmap(gpiodat);iounmap(gpiodir);iounmap(imuxrcpad);iounmap(imuxrcsw);misc_deregister(&misc_device);printk("###########################################   led_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

Makefile

# 定义目标模块名称(编译后生成 led_drv.ko)
target=led_drv# 指定要编译的内核模块:
# obj-m 表示编译为可加载模块
# += $(target).o 表示源文件是 led_drv.c(自动推导)
obj-m+=$(target).o# 指定内核源码目录路径(需要根据实际环境修改)
kerdir=/home/qq/linux_imx6ull/linux-imx# 设置交叉编译环境变量(针对ARM架构)
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-# 获取当前目录路径(使用shell命令)
curdir=$(shell pwd)# 默认构建目标
all:# 调用内核构建系统编译模块:# -C $(kerdir)    切换到内核源码目录# M=$(curdir)     指定模块源码位置(当前目录)# ARCH=$(ARCH)    指定目标架构# CROSS_COMPILE...指定交叉编译器前缀# modules         编译模块目标make -C $(kerdir) M=$(curdir) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules# 将编译好的模块复制到NFS共享目录(方便开发板测试)cp $(target).ko /home/qq/nfs/rootfs/# 声明伪目标(避免与同名文件冲突)
.PHONY:
clean:# 清理编译生成的文件:# *.o   - 目标文件# *.ko  - 内核模块# *.order, *.symvers - 内核构建系统生成的文件# *.mod.c - 模块中间文件rm -rf *.o *.ko *.order *.symvers *.mod.c

3. 用户层代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int fd = 0;char tmpbuff[256] = {0};fd = open("/dev/led0", O_RDWR);if (fd == -1){perror("fail to open\n");return -1;}while (1){sprintf(tmpbuff, "led_on");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);sprintf(tmpbuff, "led_off");write(fd, tmpbuff, sizeof(tmpbuff));sleep(1);}close(fd);return 0;
}

Makefile

OBJ=led_appOBJS+=main.c# 设置交叉编译环境变量(针对ARM架构)
CC = arm-linux-gnueabihf-gcc$(OBJ):$(OBJS)$(CC) $^ -o $@cp $(OBJ) ~/nfs/rootfs.PHONY:
clean:rm $(OBJ)

顶层目录Makefile

qq@ubuntu:~/linux_imx6ull/imx/demo3-tree$ ls
demo_app  demo_drv  Makefile
qq@ubuntu:~/linux_imx6ull/imx/demo3-tree$ 
appdir=demo_app
drvdir=demo_drvall:make -C $(appdir)make -C $(drvdir).PHONY:
clean:make -C $(appdir) cleanmake -C $(drvdir) clean

4. 运行测试

/ # insmod led_drv.ko 
[  426.809726] led_init >>>>>>>
[  426.813508] compatible: gsl_led
[  426.817291] name: gongsilei
[  426.824898] reg[0] = 0x20E0068, reg[1] = 4
[  426.830565] reg[2] = 0x20E02F4, reg[3] = 4
[  426.842047] reg[4] = 0x209C004, reg[5] = 4
[  426.846390] reg[6] = 0x209C000, reg[7] = 4
[  426.850523] ###########################################   led_init!
/ # ./led_app 
[  435.932700] kernel open  led
[  435.935805] data: led_on
[  435.938355] led_on   ...
[  435.940892] kernel write  led
[  436.944135] data: led_off
[  436.946779] led_off  ...
[  436.949316] kernel write  led
[  437.952406] data: led_on
[  437.954980] led_on   ...
[  437.957518] kernel write  led
[  438.960598] data: led_off
[  438.963285] led_off  ...
[  438.965827] kernel write  led
^C[  439.067286] led_off  ...
[  439.069839] kernel close led/ # rmmod led_drv.ko 
[  443.521911] ###########################################   led_exit!
/ # 

5. pinctrl 和 gpio 子系统

借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。避免操作寄存器。

5.1 pinctrl 子系统

5.1.1 pinctrl 子系统简介

传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置 方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引 入的,pinctrl 子系统主要工作内容如下:

①、获取设备树中 pin 信息。

②、根据获取到的 pin 信息来设置 pin 的复用功能

③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。

5.1.2 PIN 配置信息详解

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要 根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信 息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:

&iomuxc {                                                                       pinctrl-names = "default";                                                  pinctrl-0 = <&pinctrl_hog_1>;                                               imx6ul-evk {                                                                pinctrl_hog_1: hoggrp-1 {                                               fsl,pins = <                                                        MX6UL_PAD_UART1_RTS_B__GPIO1_IO19   0x17059 /* SD1 CD */        MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT    0x17059 /* SD1 VSELECT */MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID    0x13058 /* USB_OTG1_ID */>;                                                                  };                                                                      pinctrl_csi1: csi1grp {                                                 fsl,pins = <                                                        MX6UL_PAD_CSI_MCLK__CSI_MCLK        0x1b008                     MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK    0x1b008                     MX6UL_PAD_CSI_VSYNC__CSI_VSYNC      0x1b008                     MX6UL_PAD_CSI_HSYNC__CSI_HSYNC      0x1b008                     MX6UL_PAD_CSI_DATA00__CSI_DATA02    0x1b008                     MX6UL_PAD_CSI_DATA01__CSI_DATA03    0x1b008                     MX6UL_PAD_CSI_DATA02__CSI_DATA04    0x1b008                     MX6UL_PAD_CSI_DATA03__CSI_DATA05    0x1b008                     MX6UL_PAD_CSI_DATA04__CSI_DATA06    0x1b008                     MX6UL_PAD_CSI_DATA05__CSI_DATA07    0x1b008                     MX6UL_PAD_CSI_DATA06__CSI_DATA08    0x1b008                     MX6UL_PAD_CSI_DATA07__CSI_DATA09    0x1b008                     >;                                                                  };                                                                      pinctrl_enet1: enet1grp {                                               fsl,pins = <                                                        MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN  0x1b0b0                     MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER  0x1b0b0                     MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0                 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0                 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN  0x1b0b0                     MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0                 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0                 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1  0x4001b031              >;                                                                  };                                                                      

对于一个 PIN 的配置主要包括两方面, 一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。

设置 PIN 的复用功能定义在文件 arch/arm/boot/dts/imx6ul-pinfunc.h 中

/*                                                                              * Copyright 2014 - 2015 Freescale Semiconductor, Inc.                          *                                                                              * This program is free software; you can redistribute it and/or modify         * it under the terms of the GNU General Public License version 2 as            * published by the Free Software Foundation.                                   *                                                                              */                                                                             #ifndef __DTS_IMX6UL_PINFUNC_H                                                  
#define __DTS_IMX6UL_PINFUNC_H                                                  /*                                                                              * The pin function ID is a tuple of                                            * <mux_reg conf_reg input_reg mux_mode input_val>                              */                                                                             
#define MX6UL_PAD_BOOT_MODE0__GPIO5_IO10                          0x0014 0x02A0 0x0000 0x5 0x0 
#define MX6UL_PAD_BOOT_MODE1__GPIO5_IO11                          0x0018 0x02A4 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER0__GPIO5_IO00                        0x001C 0x02A8 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01                        0x0020 0x02AC 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER2__GPIO5_IO02                        0x0024 0x02B0 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER3__GPIO5_IO03                        0x0028 0x02B4 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04                        0x002C 0x02B8 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER5__GPIO5_IO05                        0x0030 0x02BC 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER6__GPIO5_IO06                        0x0034 0x02C0 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07                        0x0038 0x02C4 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08                        0x003C 0x02C8 0x0000 0x5 0x0 
#define MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09                        0x0040 0x02CC 0x0000 0x5 0x0 
#define MX6UL_PAD_JTAG_MOD__SJC_MOD                               0x0044 0x02D0 0x0000 0x0 0x0 
#define MX6UL_PAD_JTAG_MOD__GPT2_CLK                              0x0044 0x02D0 0x05A0 0x1 0x0 
#define MX6UL_PAD_JTAG_MOD__SPDIF_OUT                             0x0044 0x02D0 0x0000 0x2 0x0
#define MX6UL_PAD_JTAG_MOD__ENET1_REF_CLK_25M                     0x0044 0x02D0 0x0000 0x3 0x0

另一个就是设置这个 PIN 的电气特性,也就是 conf_reg 寄存器值!通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。

5.1.3 设备树中添加 pinctrl 节点模板

1、创建对应的节点

pinctrl_test: testgrp { };

2、添加“fsl,pins”属性

pinctrl_test: testgrp { fsl,pins = <>;};

3、在“fsl,pins”属性中添加 PIN 配置信息

pinctrl_test: testgrp { fsl,pins = <MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/>;}; 

5.2 gpio 子系统

5.2.1 gpio 子系统简介

gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动 开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

5.2.2 设备树中的 gpio 信息

首先肯定是将 PIN 复用,并且设置电气属性,也就是上一小节讲的 pinctrl 节点。

例如 SD 卡连接在 I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个 节点就是 SD 卡设备节点,如下所示:

&usdhc1 {                                                                                  pinctrl-names = "default", "state_100mhz", "state_200mhz";                  pinctrl-0 = <&pinctrl_usdhc1>;                                              pinctrl-1 = <&pinctrl_usdhc1_100mhz>;                                       pinctrl-2 = <&pinctrl_usdhc1_200mhz>;                                       cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;                                     keep-power-in-suspend;                                                      enable-sdio-wakeup;                                                         vmmc-supply = <&reg_sd1_vmmc>;                                              no-1-8-v;                                                                   status = "okay";                                                            
}; 

属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个

  • “&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,
  • “19” 表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19 这 GPIO。
  • “GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表 示高电平有效。

 gpio 子系统的GPIO1 组定义在 imx6ull.dtsi 文件中

                                                                         gpio1: gpio@0209c000 {                                                                                                                                                                           compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";               reg = <0x0209c000 0x4000>;                                      interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,                  <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;                      gpio-controller;                                                #gpio-cells = <2>;                                              interrupt-controller;                                           #interrupt-cells = <2>;                                         };                                                                  gpio2: gpio@020a0000 {                                              compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";               reg = <0x020a0000 0x4000>;                                      interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,                  <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;                      gpio-controller;                                                #gpio-cells = <2>;                                              interrupt-controller;                                           #interrupt-cells = <2>;                                         };                 

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼容 属性 。

5.2.3 gpio 子系统 API 函数

gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离 的好处

  • gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请
  • gpio_free 函数 释放GPIO管脚
  • gpio_direction_input 函数 设置某个 GPIO 为输入
  • gpio_direction_output 函数 设置某个 GPIO 为输出,并且设置默认输出值
  • gpio_get_value 函数 获取某个 GPIO 的值(0 或 1),此函数是个宏
  • gpio_set_value 函数设置某个 GPIO 的值,此函数是个宏

5.2.4 与 gpio 相关的 OF 函数

  • of_get_named_gpio 函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号, 此函数会将设备树中类似的属性信息转换为对应的 GPIO 编 号,此函数在驱动中使用很频繁!
  • of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的 是空的 GPIO 信息也会被统计到
  • of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属 性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息

5.2.4 设备树中添加 gpio 节点模板

1、创建 test 设备节点

在根节点“/”下创建 test 设备子节点

test {};

2、添加 pinctrl 信息

pinctrl_test 节点描述了 test 设备所使用的 GPIO1_IO00 这 个 PIN 的信息,我们要将这节点添加到 test 设备节点中

test { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_test>; 
};

3、添加 GPIO 属性信息

在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚

test { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_test>; gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

5.3 驱动代码实现

为了方便,只给出驱动部分和设备树,其他部分和上面一样

设备树        arch/arm/boot/dts/imx6ull-alientek-emmc.dts    

gpio 子系统

    gsl_led {                                                                              #address-cells = <1>;                                                   #size-cells = <1>;                                                      compatible = "gsl_led";                                                 status = "okay";                                                        pinctrl-names = "default";                                              pinctrl-0 = <&pinctrl_gslled>;                                          led-gpio = <&gpio1 3 0>;                                                };                                                                          

pinctrl 子系统

&iomuxc {                                                                       pinctrl-names = "default";                                                  pinctrl-0 = <&pinctrl_hog_1>;                                               imx6ul-evk {                                                                pinctrl_hog_1: hoggrp-1 {                                               fsl,pins = <                                                        MX6UL_PAD_UART1_RTS_B__GPIO1_IO19   0x17059 /* SD1 CD */        MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT    0x17059 /* SD1 VSELECT */MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID    0x13058 /* USB_OTG1_ID */>;                                                                  };                                                                      pinctrl_gslled: gslledgrp {                                             fsl,pins = <                                                    MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0                     >;                                                                  };   

驱动代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>#define DEV_NAME "led"
static int gpiopin = -1;static void init_led(void)
{gpio_set_value(gpiopin, 0);
}static void led_on(void)
{printk("led_on   ...\n");gpio_set_value(gpiopin, 0);
}static void led_off(void)
{printk("led_off  ...\n");gpio_set_value(gpiopin, 1);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static struct miscdevice misc_device = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops,
};static int __init led_init(void)
{int ret = 0;struct device_node *pdtsdevice_node = NULL;const char *pcom = NULL;pr_info("led_init >>>>>>>\n");// 创建一个混杂设备ret = misc_register(&misc_device);if (ret < 0){goto err_misc_register;}pdtsdevice_node = of_find_node_by_path("/gsl_led");if (pdtsdevice_node == NULL){goto err_of_find_node_by_path;}ret = of_property_read_string(pdtsdevice_node, "compatible", &pcom);if (ret < 0){goto err_of_find_node_by_path;}pr_info("compatible: %s\n", pcom);gpiopin = of_get_named_gpio(pdtsdevice_node, "led-gpio", 0);if (gpiopin < 0){goto err_of_get_named_gpio;}pr_info("gpiopin = %d\n", gpiopin);ret = gpio_request(gpiopin, "myled");if (ret < 0){goto err_gpio_request;}ret = gpio_direction_output(gpiopin, 1);if (ret < 0){goto err_gpio_request;}printk("###########################################   led_init!\n");return 0;
err_gpio_request:gpio_free(gpiopin);
err_of_get_named_gpio:pr_err("fail to of_get_named_gpio\n");
err_of_find_node_by_path:pr_err("fail to of_find_node_by_path\n");
err_misc_register:ret = misc_deregister(&misc_device);return ret;
}static void __exit led_exit(void)
{gpio_free(gpiopin);misc_deregister(&misc_device);printk("###########################################   led_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

运行结果

/ # insmod led_drv.ko 
[   39.265546] led_init >>>>>>>
[   39.269181] compatible: gsl_led
[   39.273206] gpiopin = 3
[   39.277796] ###########################################   led_init!
/ # ./led_app 
[   61.171394] kernel open  led
[   61.174510] data: led_on
[   61.177061] led_on   ...
[   61.179600] kernel write  led
[   62.182789] data: led_off
[   62.185460] led_off  ...
[   62.188003] kernel write  led
[   63.191088] data: led_on
[   63.193638] led_on   ...
[   63.196202] kernel write  led
[   64.199281] data: led_off
[   64.201916] led_off  ...
[   64.204481] kernel write  led
^C[   64.509746] led_off  ...
[   64.512305] kernel close led/ # rmmod led_drv.ko 
[   70.038701] ###########################################   led_exit!

6. 设备树下的 platform 驱动编写

6.1 设备树下的 platform 驱动简介

platform 驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。

在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。

在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。

6.2 

这里的匹配要注意和没有设备树下的platform是不同的。

6.3 驱动代码编写

这里我们用到了gpio子系统和pinctrl子系统,所以代码为,gpio子系统+pinctrl子系统+misc+设备树+platform

驱动代码

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of.h>#define DEV_NAME "led"static int gpio_pin = -1;static void init_led(void)
{gpio_set_value(gpio_pin, 1);
}static void led_on(void)
{printk("led_on   ...\n");gpio_set_value(gpio_pin, 0);
}static void led_off(void)
{printk("led_off  ...\n");gpio_set_value(gpio_pin, 1);
}static int open(struct inode *node, struct file *file)
{init_led();printk("kernel open  led\n");return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{printk("kernel read led\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{int ret = 0;unsigned char data[20] = {0};unsigned long len_copy = len < sizeof(data) ? len : sizeof(data);ret = copy_from_user(data, buf, len_copy);if (ret < 0){return -1;}ret = len_copy;pr_info("data: %s\n", data);if (!strcmp(data, "led_on")){led_on();}else if (!strcmp(data, "led_off")){led_off();}else{ret = -EINVAL;}printk("kernel write  led\n");return 0;
}int close(struct inode *node, struct file *file)
{led_off();printk("kernel close led\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close,
};static struct miscdevice misc_device = {.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME, // 设备名.fops = &fops,
};static int probe(struct platform_device *pdev)
{int ret = 0;struct device_node *pdtsdevice_node = NULL;const char *pcom = NULL;// 创建一个混杂设备ret = misc_register(&misc_device);if (ret < 0){goto err_misc_register;}pdtsdevice_node = of_find_node_by_path("/gsl_led"); // 设备数节点名字if (pdtsdevice_node == NULL){goto err_of_find_node_by_path;}ret = of_property_read_string(pdtsdevice_node, "compatible", &pcom);if (ret < 0){goto err_of_find_node_by_path;}pr_info("compatible: %s\n", pcom);gpio_pin = of_get_named_gpio(pdtsdevice_node, "led-gpio", 0); // gpio子系统设备树中的名字if (gpio_pin < 0){goto err_of_get_named_gpio;}pr_info("gpio_pin = %d\n", gpio_pin);ret = gpio_request(gpio_pin, "myled");if (ret < 0){goto err_gpio_request;}ret = gpio_direction_output(gpio_pin, 1);if (ret < 0){goto err_gpio_request;}printk("###########################################   led_probe!\n");return 0;err_gpio_request:gpio_free(gpio_pin);
err_of_get_named_gpio:pr_err("fail to of_get_named_gpio\n");
err_of_find_node_by_path:pr_err("fail to of_find_node_by_path\n");
err_misc_register:ret = misc_deregister(&misc_device);return ret;
}
static int remove(struct platform_device *dev)
{gpio_free(gpio_pin);misc_deregister(&misc_device);printk("###########################################   led_driver_remove!\n");return 0;
}/* 匹配列表 */
struct of_device_id match_table[] ={{.compatible = "gsl_led",},{},
};/* platform 驱动结构体 */
struct platform_driver drv = {.probe = probe,.remove = remove,.driver = {.name = DEV_NAME,               /* 驱动名字,用于和设备匹配 */.of_match_table = &match_table, /* 设备树匹配表 */},
};static int __init led_init(void)
{int ret = 0;pr_info("led_init >>>>>>>\n");ret = platform_driver_register(&drv);if (ret == -1){goto err_platform_driver_register;}printk("###########################################   led_driver_init!\n");return 0;err_platform_driver_register:platform_driver_unregister(&drv);return ret;
}static void __exit led_exit(void)
{platform_driver_unregister(&drv);printk("###########################################   led_driver_exit!\n");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

其他部分没有变化

运行结果

/ # insmod led_driver.ko 
[   77.274747] led_init >>>>>>>
[   77.278930] compatible: gsl_led
[   77.282970] gpio_pin = 3
[   77.287661] ###########################################   led_probe!
[   77.302395] ###########################################   led_driver_init!
/ # ./led_app 
[   79.715434] kernel open  led
[   79.718375] data: led_on
[   79.720915] led_on   ...
[   79.723621] kernel write  led
[   80.726789] data: led_off
[   80.729431] led_off  ...
[   80.731971] kernel write  led
[   81.735109] data: led_on
[   81.737657] led_on   ...
[   81.740199] kernel write  led
^C[   82.363896] led_off  ...
[   82.366451] kernel close led/ # rmmod led_driver.ko 
[   84.633752] ###########################################   led_driver_remove!
[   84.641490] ###########################################   led_driver_exit!
/ # 

调试技巧​​:

  • 使用printk进行内核日志输出

  • 通过dmesg查看内核日志

  • 使用strace跟踪系统调用

http://www.dtcms.com/a/427247.html

相关文章:

  • 基于 Web3 + RWA 的品牌门店数字化范式
  • 惠州 网站建设公司简单制作网页
  • Gartner 2025 中国网络安全成熟度曲线深度解读:AI 安全如何重构防御逻辑
  • 为男人做购物网站超详细wordpress常用函数
  • 【C++ 语法】模板进阶
  • 【K8s】K8s的声明式API核心
  • 关于网站开发人员保密协议专业服务网站开发
  • supabase 实现聊天板(Chat Board)
  • PersistentVolume + NFS:网络共享存储
  • leetcode 1863 找出所有子集的异或总和再求和
  • 【C++】STL -- vector 的使用及模拟实现
  • 网站如何做图片特效erp软件实施
  • 【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】
  • 贡井区建设局网站淘宝客做自己的网站
  • 蓝牙发展史
  • 对LED点灯实验的C与汇编的深入分析,提及到volatile
  • 网站建设外包广州网站建设说说外链的建设
  • LevOJ P2080 炼金铺 II [矩阵解法]
  • wordpress网站映射wordpress免费网站国外
  • 哈尔滨企业建站系统西宁建设局官方网站
  • py_innodb_page_info.py表空间分析
  • 有什么做宝宝辅食的网站吗莱阳网站开发
  • tasklet
  • 页面 HTTPS 化实战,从证书部署到真机验证的全流程(证书链、重定向、混合内容、抓包排查)
  • 北京哪家公司做网站电脑上买wordpress
  • 家用机能否做网站服务器泰安房价走势图
  • MC33PT2000控制详解七:软件代码设计1-图形化设置
  • 在租用香港服务器之前如何判断质量
  • 【高清视频】CXL 2.0 over Fibre演示和答疑 - 将内存拉到服务器10米之外
  • 【STM32项目开源】基于STM32的独居老人监护系统