当前位置: 首页 > 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.dtcms.com/a/357777.html

相关文章:

  • 第22章笔记|把“可传参脚本”打磨成“高级好用的工具”
  • 博客系统测试实战:功能和性能的全面解析
  • selenium自动下载更新浏览器对应的webdriver
  • 关于shell命令的扩展
  • log4jshell CVE-2021-44228 复现
  • R1 快开门式压力容器操作证考试大纲解读:重点模块与考核标准
  • Linux Shell 编程
  • 淘宝商品评论接口技术实现:从评论获取到情感分析全流程方案
  • SQL知识
  • 序列容器(vector,deque,list)
  • 4x12G-SDI(四链接12G-SDI)
  • PCIe 6.0 TLP结构解析:深入理解事务层数据包的设计与实现
  • Windows Command Line Windows 命令行
  • 【RAG Agent实战】告别“单线程”RAG:用查询理解与LangGraph构建能处理复杂意图的高级代理
  • ros2--action/动作--接口
  • 2024年12月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 中科米堆CASAIM五金配件三维扫描测量尺寸形位公差
  • MySQL 中如果发生死锁应该如何解决?
  • OpenAI宣布正式推出Realtime API
  • ADC模数转换
  • Set和Map
  • AI的“科学革命”:Karpathy吹响号角,从“经院哲学”走向“实验科学”
  • 【.net core】【NPOI】读取表格信息(处理合并行表格数据)
  • vscode里面可以批量放弃更改
  • Linux驱动异步通知机制详解
  • Labview邪修01:贪吃蛇
  • 【完整源码+数据集+部署教程】控制台缺陷检测系统源码和数据集:改进yolo11-repvit
  • IDEA编译报错:Error:(3, 28) java: 程序包com.alibaba.fastjson不存在
  • GPFS性能优化
  • zyplayer-doc:AI 驱动的智能知识库