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

嵌入式Linux驱动开发:i.MX6ULL平台设备驱动

嵌入式Linux驱动开发:i.MX6ULL平台设备驱动

1. 概述

本文档详细记录了基于i.MX6ULL开发板的平台设备驱动开发过程。通过实现一个LED控制驱动,深入理解Linux内核中的平台总线(Platform Bus)、平台设备(Platform Device)和平台驱动(Platform Driver)三者之间的关系与工作原理。

本项目包含两个主要模块:

  • leddevice.c:平台设备定义与注册
  • leddriver.c:平台驱动实现
  • platledAPP.c:用户空间测试应用程序

2. 平台总线、设备与驱动模型

2.1 模型架构

Linux内核采用总线-设备-驱动模型来管理硬件设备。对于SoC内部集成的外设(如GPIO、I2C、SPI控制器等),由于它们直接连接到处理器内部总线,无法被动态探测,因此引入了平台总线platform_bus_type)这一虚拟总线概念。

该模型的核心组件包括:

  • 平台总线 (platform_bus):系统级虚拟总线,所有平台设备都挂载于此
  • 平台设备 (platform_device):描述具体的硬件设备信息
  • 平台驱动 (platform_driver):提供设备的操作方法

三者通过匹配机制关联,当设备与驱动的名称一致时,内核会调用驱动的probe函数完成初始化。

2.2 匹配过程

  1. 内核启动时扫描设备树或静态注册的平台设备
  2. 当注册新的平台驱动时,遍历所有未匹配的平台设备
  3. 比较platform_device.nameplatform_driver.driver.name
  4. 名称匹配成功后调用驱动的probe函数
  5. 设备移除时调用remove函数

3. 设备树分析 (imx6ull-alientek-emmc.dts)

提供的设备树文件定义了i.MX6ULL开发板的完整硬件配置。以下是与本项目相关的关键节点分析:

3.1 自定义LED节点

alphaled {#address-cells = <1>;#size-cells = <1>;compatible = "alientek,alphaled";status = "okay";reg = <0x020C406C 0x04 0x020E0068 0x04 0x020E02F4 0x04 0x0209C000 0x04 0x0209C004 0x04>;
};
  • compatible: 匹配字符串"alientek,alphaled",用于驱动匹配
  • reg: 定义了5个寄存器的物理地址和长度:
    • 0x020C406C: CCM_CCGR1 (时钟控制)
    • 0x020E0068: SW_MUX_GPIO1_IO03 (复用配置)
    • 0x020E02F4: SW_PAD_GPIO1_IO03 (电气特性)
    • 0x0209C000: GPIO1_DR (数据寄存器)
    • 0x0209C004: GPIO1_GDIR (方向寄存器)

3.2 GPIO LED节点

dtsled{compatible = "gpio-leds";led0{label = "red";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpioled>;gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;default-state = "on";linux,default-trigger = "heartbeat";};
};

使用标准的gpio-leds兼容性字符串,由内核自带的LED驱动处理。

3.3 引脚控制组

pinctrl_gpioled: ledgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10b0>;
};

定义了GPIO1_IO03引脚的复用功能和电气参数(0x10b0)。

4. 平台设备实现 (leddevice.c)

4.1 寄存器地址定义

#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

这些宏定义了LED控制所需的关键寄存器物理地址。

4.2 资源描述符

static struct resource led_resources[] = {[0] = {.start = CCM_CCGR1_BASE,.end = CCM_CCGR1_BASE + REGISTER_LNE - 1,.flags = IORESOURCE_MEM,},// ... 其他寄存器
};

resource结构体用于描述设备的硬件资源(内存、中断等)。这里定义了5个内存资源,每个长度为4字节(REGISTER_LNE=4)。

4.3 平台设备结构体

static struct platform_device leddevice = {.name = "imx6ull-led",.id = -1,.dev = {.release = leddevice_realease,},.num_resources = ARRAY_SIZE(led_resources),.resource = led_resources,
};
  • name: 匹配标识符,必须与驱动的.driver.name相同
  • id: 设备ID,-1表示单个设备
  • dev.release: 设备释放回调函数
  • num_resources/resource: 指向资源数组

4.4 设备注册

static int __init leddevice_init(void)
{platform_device_register(&leddevice);return 0;
}

在模块加载时注册平台设备。注意:在设备树环境下,通常不需要手动创建平台设备,设备树会自动解析并创建。

5. 平台驱动实现 (leddriver.c)

5.1 驱动结构体

static struct platform_driver leddriver = {.driver = {.name = "imx6ull-led",},.probe = led_probe,.remove = led_remove,
};
  • .driver.name: 匹配字符串,必须与设备的.name字段一致
  • .probe: 设备匹配成功后的初始化函数
  • .remove: 设备移除时的清理函数

5.2 probe函数详解

5.2.1 获取资源
for (i = 0; i < 5; i++) {ledresources[i] = platform_get_resource(drv, IORESOURCE_MEM, i);if (ledresources[i] == NULL) {return -EINVAL;}
}

platform_get_resource()从设备的资源数组中获取指定类型的资源。这里依次获取5个内存资源。

5.2.2 I/O内存映射
IMX6U_CCM_CCGR1 = ioremap(ledresources[0]->start, resource_size(ledresources[0]));
// ... 其他寄存器映射

ioremap()将物理地址映射为内核虚拟地址,以便安全访问。resource_size()获取资源的大小。

5.2.3 硬件初始化
时钟使能
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);

配置CCM_CCGR1寄存器,使能GPIO1时钟(BIT26-27置为11)。

引脚复用
writel(5, SW_MUX_GPIO1_IO03);

设置SW_MUX_GPIO1_IO03为模式5(GPIO功能)。

电气特性
writel(0x10B0, SW_PAD_GPIO1_IO03);

配置驱动强度、上下拉等电气参数,与设备树中定义一致。

GPIO方向设置
val = readl(GPIO1_GDIR);
val |= (1 << 3);
writel(val, GPIO1_GDIR);

设置GPIO1_3为输出模式。

初始状态
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);

默认关闭LED(高电平熄灭,因GPIO_ACTIVE_LOW)。

5.3 字符设备框架

5.3.1 设备号管理
if (newchrled.major) {newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, LED_CNT, LED_NAME);
} else {alloc_chrdev_region(&newchrled.devid, 0, LED_CNT, LED_NAME);newchrled.major = MAJOR(newchrled.devid);
}

动态或静态分配设备号。

5.3.2 cdev注册
cdev_init(&newchrled.cdev, &newchrled_fops);
cdev_add(&newchrled.cdev, newchrled.devid, LED_CNT);

初始化并添加字符设备到系统。

5.3.3 设备类与设备节点创建
newchrled.class = class_create(THIS_MODULE, LED_NAME);
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, LED_NAME);

创建设备类并在/dev/目录下生成设备节点。

5.4 LED控制函数

void led_switch(u8 sta)
{u32 val = 0;if (sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);} else if (sta == LEDOFF) {val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);}
}

直接操作GPIO数据寄存器控制LED状态。

5.5 文件操作函数

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0) {return -EFAULT;}if (databuf[0] == LEDON) {led_switch(LEDON);} else if (databuf[0] == LEDOFF) {led_switch(LEDOFF);}return 0;
}

write函数从用户空间接收控制命令(0开灯,1关灯)。

5.6 remove函数

static int led_remove(struct platform_device *drv)
{led_switch(LEDOFF);iounmap(IMX6U_CCM_CCGR1);// ... 其他iounmapcdev_del(&newchrled.cdev);unregister_chrdev_region(newchrled.devid, LED_CNT);device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);return 0;
}

设备移除时的清理工作,确保资源正确释放。

6. 用户空间应用程序 (platledAPP.c)

int main(int argc, char *argv[])
{int fd;unsigned char databuf[1];if (argc != 3) {printf("Error Usage!\r\n");return -1;}fd = open(argv[1], O_RDWR);if (fd < 0) {printf("file open failed!\r\n");return -1;}databuf[0] = atoi(argv[2]);write(fd, databuf, sizeof(databuf));close(fd);return 0;
}

用法:./platledAPP /dev/platled 0(开灯)或 ./platledAPP /dev/platled 1(关灯)

7. Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := leddevice.o leddriver.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
  • KERNERDIR: 内核源码路径
  • obj-m: 指定编译为模块的目标文件
  • M=$(CURRENTDIR): 告诉内核构建系统在当前目录查找模块源码

8. 编译与测试流程

8.1 编译模块

make

生成leddevice.koleddriver.ko

8.2 加载模块

# 先加载设备模块
insmod leddevice.ko
# 再加载驱动模块
insmod leddriver.ko

8.3 查看日志

dmesg | tail

应看到类似输出:

newcheled major=250,minor=0

8.4 测试功能

# 开灯
./platledAPP /dev/platled 0
# 关灯
./platledAPP /dev/platled 1

8.5 卸载模块

rmmod leddriver
rmmod leddevice

9. 总结

本项目完整实现了基于i.MX6ULL的平台设备驱动开发,涵盖了以下核心知识点:

  1. 平台总线模型:理解设备、驱动、总线三者的关系
  2. 资源管理:使用resource结构体描述硬件资源
  3. I/O内存访问ioremap()iounmap()的正确使用
  4. 设备树集成:如何通过设备树传递硬件信息
  5. 字符设备框架:完整的字符设备驱动实现
  6. 模块化编程:驱动的加载与卸载机制

10. 源码仓库

https://gitee.com/dream-cometrue/linux_driver_imx6ull

http://www.xdnf.cn/news/1395469.html

相关文章:

  • 使用 Docker 部署 Squid 为 Kubernetes 中的 Nexus3 提供公网代理访问
  • linux 条件变量与生产消费者模型
  • 玳瑁的嵌入式日记D29-0829(进程间通信)
  • Python OpenCV图像处理与深度学习:Python OpenCV开发环境搭建与入门
  • 基于能量方法的纳维-斯托克斯方程高阶范数有界性理论推导-陈墨仙
  • STM32CubeMX + HAL 库:基于 I²C 通信的 AHT20 高精度温湿度测量实验
  • 【系列03】端侧AI:构建与部署高效的本地化AI模型 第2章:端侧AI硬件入门
  • 134-细粒度多尺度符号熵和鲸鱼优化算法的滚动轴承故障诊断技术MSVM
  • Redis搭建哨兵模式一主两从三哨兵
  • 线程安全及死锁问题
  • 【好题推荐】运算符的构造运用
  • 光伏发多少电才够用?匹配家庭用电需求
  • #医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(五)
  • Linux内核进程管理子系统有什么第三十八回 —— 进程主结构详解(34)
  • JUC并发编程09 - 内存(01) - JMM/cache
  • 嵌入式Linux设备树驱动开发 - dtsof驱动
  • Unity DateTime 相关
  • 处理器(CPU/MPU)的双发射是什么?
  • 命令扩展与重定向
  • 可解释人工智能XAI
  • 【机器学习深度学习】Embedding 与 RAG:让 AI 更“聪明”的秘密
  • leetcode 191 位1的个数
  • 【0422】SMgrRelationData 中 md_num_open_segs 和 md_seg_fds 数组为什么是 4 个元素? 第四个元素表示什么?
  • Ubuntu磁盘分区重新挂载读写指南
  • 不一样的发票管理模式-发票识别+发票查验接口
  • ContextMenuManager for Win:优化右键菜单,解决用户痛点
  • lxml库如何使用
  • ElasticSearch对比Solr
  • C语言————操作符详解
  • TypeScript的Type