恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
linux platform设备驱动
首页
资讯中心
/
linux platform设备驱动
linux platform设备驱动
发布时间:2026/9/5 7:29:59
一、Linux驱动的分离与分层1、驱动的分隔与分离对于 Linux 这样一个成熟、庞大、复杂的操作系统代码的重用性非常重要否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序因为驱动程序占用了 Linux 内核代码量的大头如果不对驱动程序加以管理任由重复的代码肆意增加那么用不了多久 Linux 内核的文件数量就庞大到无法接受的地步。假如现在有三个平台 A、B 和 C这三个平台 (这里的平台说的是 SOC) 上都有 MPU6050 这个 I2C 接口的六轴传感器按照我们写裸机 I2C 驱动的时候的思路每个平台都有一个 MPU6050 的驱动因此编写出来的最简单的驱动框架如图所示从图可以看出每种平台下都有一个主机驱动和设备驱动主机驱动肯定是必须要的毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个因为不管对于那个 SOC 来说MPU6050 都是一样通过 I2C 接口读写数据就行了只需要一个 MPU6050 的驱动程序即可。如果再来几个 I2C 设备比如 AT24C02、FT5206 (电容触摸屏) 等如果按照图中的写法那么设备端的驱动将会重复的编写好几次。显然在 Linux 驱动程序中这种写法是不推荐的最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口 (也叫做主机驱动)每个设备的话也只提供一个驱动程序 (设备驱动)每个设备通过统一的 I2C 接口驱动来访问这样就可以大大简化驱动文件比如图中三种平台下的 MPU6050 驱动框架就可以简化为下图所示实际的I2C驱动设备肯定有很多种不止MPU6050这一个那么实际的驱动架构如图所示这个就是驱动的分隔也就是将主机驱动和设备驱动分隔开来比如 I2C、SPI 等等都会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中一般 I2C 主机控制器驱动已经由半导体厂家编写好了而设备驱动一般也由设备器件的厂家编写好了我们只需要提供设备信息即可比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上I2C 的速度是多少等等。相当于将设备信息从设备驱动中剥离开来驱动使用标准方法去获取到设备信息 (比如从设备树中获取到设备信息)然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动设备只负责设备想办法将两者进行匹配即可。这个就是 Linux 中的总线 (bus)、驱动 (driver) 和设备 (device) 模型也就是常说的驱动分离。总线就是驱动和设备信息的月老负责给两者牵线搭桥如图所示当我们向系统注册一个驱动的时候总线就会在右侧的设备中查找看看有没有与之匹配的设备如果有的话就将两者联系起来。同样的当向系统中注册一个设备的时候总线就会在左侧的驱动中查找看有没有与之匹配的设备有的话也联系起来。Linux 内核中大量的驱动程序都采用总线、驱动和设备模式我们一会要重点讲解的 platform 驱动就是这一思想下的产物。2、驱动的分层上一小节讲了驱动的分隔与分离本节我们来简单看一下驱动的分层大家应该听说过网络的 7 层模型不同的层负责不同的内容。同样的Linux 下的驱动往往也是分层的分层的目的也是为了在不同的层处理不同的内容。以其他书籍或者资料常常使用到的 input (输入子系统) 为例简单介绍一下驱动的分层。input 子系统负责管理所有跟输入有关的驱动包括键盘、鼠标、触摸等最底层的就是设备原始驱动负责获取输入设备的原始值获取到的输入事件上报给 input 核心层。input 核心层会处理各种 IO 模型并且提供 file_operations 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可至于如何处理这些上报的输入事件那是上层去考虑的我们不用管。可以看出借助分层模型可以极大的简化我们的驱动编写对于驱动编写来说非常的友好。二、platform 平台驱动模型简介前面我们讲了设备驱动的分离并且引出了总线 (bus)、驱动 (driver) 和设备 (device) 模型比如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的但是又要使用总线、驱动和设备模型该怎么办呢为了解决此问题Linux 提出了 platform 这个虚拟总线相应的就有 platform_driver 和 platform_device。1、platform 总线Linux 系统内核使用bus_type结构体表示总线此结构体定义在文件include/linux/device.hbus_type结构体内容如下struct bus_type { const char *name; /* 总线名字 */ const char *dev_name; struct device *dev_root; struct device_attribute *dev_attrs; 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 (*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; };第 10 行match函数此函数很重要单词 match 的意思就是 “匹配、相配”因此此函数就是完成设备和驱动之间匹配的总线就是使用 match 函数来根据注册的设备来查找对应的驱动或者根据注册的驱动来查找相应的设备因此每一条总线都必须实现此函数。match函数有两个参数dev和drv这两个参数分别为device和device_driver类型也就是设备和驱动。platform 总线是bus_type的一个具体实例定义在文件drivers/base/platform.cplatform 总线定义如下struct bus_type platform_bus_type { .name platform, .dev_groups platform_dev_groups, .match platform_match, .uevent platform_uevent, .pm platform_dev_pm_ops, };platform_bus_type就是 platform 平台总线其中platform_match就是匹配函数。我们来看一下驱动和设备是如何匹配的platform_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); }第 11~12 行第一种匹配方式 OF 类型的匹配也就是设备树采用的匹配方式of_driver_match_device函数定义在文件include/linux/of_device.h中。device_driver结构体 (表示设备驱动) 中有个名为of_match_table的成员变量此成员变量保存着驱动的compatible匹配表设备树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较查看是否有相同的条目如果有的话就表示设备和此驱动匹配设备和驱动匹配成功以后probe函数就会执行。第 15~16 行第二种匹配方式ACPI 匹配方式。第 19~20 行第三种匹配方式id_table匹配每个platform_driver结构体有一个id_table成员变量顾名思义保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。第 23 行第四种匹配方式如果第三种匹配方式的id_table不存在的话就直接比较驱动和设备的name字段看看是不是相等如果相等的话就匹配成功。对于支持设备树的 Linux 版本号一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在第三种和第四种只要存在一种就可以一般用的最多的还是第四种也就是直接比较驱动和设备的name字段毕竟这种方式最简单了。2、platform 驱动platform_driver结构体表示 platform 驱动此结构体定义在include/linux/platform_device.hstruct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; bool prevent_deferred_probe; };第 2 行probe 函数当驱动与设备匹配成功以后 probe 函数就会执行非常重要的函数 一般驱动的提供者会编写如果自己要编写一个全新的驱动那么 probe 就需要自行实现。第 7 行driver 成员为 device_driver 结构体变量Linux 内核里面大量使用到了面向对象的思维device_driver 相当于基类提供了最基础的驱动框架。platform_driver 继承了这个基类然后在此基础上又添加了一些特有的成员变量。第 8 行id_table 表也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的第三种方法id_table 是个表 (也就是数组)每个元素的类型为 platform_device_idplatform_device_id 结构体内容如下struct platform_device_id { char name[PLATFORM_NAME_SIZE]; kernel_ulong_t driver_data; };device_driver结构体定义在include/linux/device.hdevice_driver结构体内容如下struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built‑in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table; const struct acpi_device_id *acpi_match_table; 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; };第 10 行of_match_table就是采用设备树的时候驱动使用的匹配表同样是数组每个匹配项都为of_device_id结构体类型此结构体定义在文件include/linux/mod_devicetable.h中内容如下struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; };第 4 行的compatible非常重要因为对于设备树而言就是通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员进行比较如果有相等的就表示设备和此驱动匹配成功。在编写 platform 驱动的时候首先定义一个platform_driver结构体变量然后实现结构体中的各个成员变量重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执行具体的驱动程序在probe函数里面编写比如字符设备驱动等等。当我们定义并初始化好platform_driver结构体变量以后需要在驱动入口函数里面调用platform_driver_register函数向 Linux 内核注册一个 platform 驱动platform_driver_register函数原型如下所示int platform_driver_register (struct platform_driver *driver)函数参数和返回值含义如下driver要注册的 platform 驱动。返回值负数失败0成功。还需要在驱动卸载函数中通过platform_driver_unregister函数卸载 platform 驱动platform_driver_unregister函数原型如下void platform_driver_unregister(struct platform_driver *drv)函数参数和返回值含义如下drv要卸载的 platform 驱动。返回值无。platform 驱动框架示例代码/* 设备结构体 */ struct xxx_dev{ struct cdev cdev; /* 设备结构体其他具体内容 */ }; struct xxx_dev xxxdev; /* 定义个设备结构体变量 */ static int xxx_open(struct inode *inode, struct file *filp) { /* 函数具体内容 */ return 0; } static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { /* 函数具体内容 */ return 0; } /* 字符设备驱动操作集 */ static struct file_operations xxx_fops { .owner THIS_MODULE, .open xxx_open, .write xxx_write, }; /* * platform驱动的probe函数 * 驱动与设备匹配成功以后此函数就会执行 */ static int xxx_probe(struct platform_device *dev) { ...... cdev_init(xxxdev.cdev, xxx_fops); /* 注册字符设备驱动 */ /* 函数具体内容 */ return 0; } static int xxx_remove(struct platform_device *dev) { ...... cdev_del(xxxdev.cdev);/* 删除 cdev */ /* 函数具体内容 */ return 0; } /* 匹配列表 */ static const struct of_device_id xxx_of_match[] { { .compatible xxx-gpio }, { /* Sentinel */ } }; /* platform平台驱动结构体 */ static struct platform_driver xxx_driver { .driver { .name xxx, .of_match_table xxx_of_match, }, .probe xxx_probe, .remove xxx_remove, }; /* 驱动模块加载 */ static int __init xxxdriver_init(void) { return platform_driver_register(xxx_driver); } /* 驱动模块卸载 */ static void __exit xxxdriver_exit(void) { platform_driver_unregister(xxx_driver); } module_init(xxxdriver_init); module_exit(xxxdriver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(gui);3、platform 设备platform 驱动已经准备好了我们还需要 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字段相同否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的name字段为 “xxx‑gpio”那么此name字段也要设置为 “xxx‑gpio”。num_resources表示资源数量一般为下一行resource资源的大小。resource表示资源也就是设备信息比如外设寄存器等。Linux 内核使用resource结构体表示资源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里面。#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 ...... /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */ #define IORESOURCE_PCI_FIXED (14) /* Do not move resource */在以前不支持设备树的 Linux 版本中用户需要编写platform_device变量来描述设备信息然后使用platform_device_register函数将设备信息注册到 Linux 内核中此函数原型如下int platform_device_register(struct platform_device *pdev)参数pdev要注册的 platform 设备。返回值负数失败0成功。如果不再使用 platform 的话可以通过platform_device_unregister函数注销掉相应的 platform 设备void platform_device_unregister(struct platform_device *pdev)函数参数和返回值含义如下pdev要注销的 platform 设备。返回值无。platform设备信息框架如下所示/* 寄存器地址定义 */ #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设1寄存器首地址 */ #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设2寄存器首地址 */ #define REGISTER_LENGTH 4 /* 资源 */ static struct resource xxx_resources[] { [0] { .start PERIPH1_REGISTER_BASE, .end (PERIPH1_REGISTER_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, [1] { .start PERIPH2_REGISTER_BASE, .end (PERIPH2_REGISTER_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, }; /* platform设备结构体 */ static struct platform_device xxxdevice { .name xxx‑gpio, .id -1, .num_resources ARRAY_SIZE(xxx_resources), .resource xxx_resources, }; /* 设备模块加载 */ static int __init xxxdevice_init(void) { return platform_device_register(xxxdevice); } /* 设备模块注销 */ static void __exit xxx_resourcesdevice_exit(void) { platform_device_unregister(xxxdevice); } module_init(xxxdevice_init); module_exit(xxx_resourcesdevice_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(gui);第 7~18 行数组xxx_resources表示设备资源一共有两个资源分别为设备外设 1 和外设 2 的寄存器信息。因此 flags 都为IORESOURCE_MEM表示资源为内存类型的。第 21~26 行platform 设备结构体变量注意name字段要和所使用的驱动中的name字段一致否则驱动和设备无法匹配成功。num_resources表示资源大小其实就是数组xxx_resources的元素数量这里用ARRAY_SIZE来测量一个数组的元素个数。第 29~32 行设备模块加载函数在此函数中调用platform_device_register向 Linux 内核注册 platform 设备。第 35~38 行设备模块卸载函数在此函数中调用platform_device_unregister从 Linux 内核中卸载 platform 设备。示例代码主要是在不支持设备树的 Linux 版本中使用的当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述Linux 内核启动的时候会从设备树中读取设备信息然后将其组织成platform_device形式至于设备树到platform_device的具体过程就不去详细的追究了。三、demo#include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/of_gpio.h #include linux/semaphore.h #include linux/timer.h #include linux/irq.h #include linux/wait.h #include linux/poll.h #include linux/fs.h #include linux/fcntl.h #include linux/platform_device.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h /*************************************************************** 文件名 : leddevice.c 版本 : V1.0 描述 : platform设备 其他 : 无 ***************************************************************/ /* * 寄存器地址定义 */ #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) #define REGISTER_LENGTH 4 /* description : 释放flatform设备模块的时候此函数会执行 * param - dev : 要释放的设备 * return : 无 */ static void led_release(struct device *dev) { printk(led device released!\r\n); } /* * 设备资源信息也就是LED0所使用的所有寄存器 */ static struct resource led_resources[] { [0] { .start CCM_CCGR1_BASE, .end (CCM_CCGR1_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, [1] { .start SW_MUX_GPIO1_IO03_BASE, .end (SW_MUX_GPIO1_IO03_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, [2] { .start SW_PAD_GPIO1_IO03_BASE, .end (SW_PAD_GPIO1_IO03_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, [3] { .start GPIO1_DR_BASE, .end (GPIO1_DR_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, [4] { .start GPIO1_GDIR_BASE, .end (GPIO1_GDIR_BASE REGISTER_LENGTH - 1), .flags IORESOURCE_MEM, }, }; /* * platform设备结构体 */ static struct platform_device leddevice { .name imx6ul-led, .id -1, .dev { .release led_release, }, .num_resources ARRAY_SIZE(led_resources), .resource led_resources, }; /* * description : 设备模块加载 * param : 无 * return : 无 */ static int __init leddevice_init(void) { return platform_device_register(leddevice); } /* * description : 设备模块注销 * param : 无 * return : 无 */ static void __exit leddevice_exit(void) { platform_device_unregister(leddevice); } module_init(leddevice_init); module_exit(leddevice_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(gui);#include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/of_gpio.h #include linux/semaphore.h #include linux/timer.h #include linux/irq.h #include linux/wait.h #include linux/poll.h #include linux/fs.h #include linux/fcntl.h #include linux/platform_device.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h /*************************************************************** 文件名 : leddriver.c 版本 : V1.0 描述 : platform驱动 其他 : 无 ***************************************************************/ #define LEDDEV_CNT 1 /* 设备号长度 */ #define LEDDEV_NAME platled /* 设备名字 */ #define LEDOFF 0 #define LEDON 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; /* leddev设备结构体 */ struct leddev_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ }; struct leddev_dev leddev; /* led设备 */ /* * description : LED打开/关闭 * param - sta : LEDON(0) 打开LEDLEDOFF(1) 关闭LED * return : 无 */ void led0_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); } } /* * description : 打开设备 * param - inode : 传递给驱动的inode * param - filp : 设备文件file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * return : 0 成功;其他 失败 */ static int led_open(struct inode *inode, struct file *filp) { filp-private_data leddev; /* 设置私有数据 */ return 0; } /* * description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符 * param - buf : 要写给设备写入的数据 * param - cnt : 要写入的数据长度 * param - offt : 相对于文件首地址的偏移 * return : 写入的字节数如果为负值表示写入失败 */ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue; unsigned char databuf[1]; unsigned char ledstat; retvalue copy_from_user(databuf, buf, cnt); if(retvalue 0) { return -EFAULT; } ledstat databuf[0]; /* 获取状态值 */ if(ledstat LEDON) { led0_switch(LEDON); /* 打开LED灯 */ }else if(ledstat LEDOFF) { led0_switch(LEDOFF); /* 关闭LED灯 */ } return 0; } /* 设备操作函数 */ static struct file_operations led_fops { .owner THIS_MODULE, .open led_open, .write led_write, }; /* * description : flatform驱动的probe函数当驱动与 * 设备匹配以后此函数就会执行 * param - dev : platform设备 * return : 0成功;其他负值,失败 */ static int led_probe(struct platform_device *dev) { int i 0; int ressize[5]; u32 val 0; struct resource *ledsource[5]; printk(led driver and device has matched!\r\n); /* 1、获取资源 */ for (i 0; i 5; i) { ledsource[i] platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */ if (!ledsource[i]) { dev_err(dev-dev, No MEM resource for always on\n); return -ENXIO; } ressize[i] resource_size(ledsource[i]); } /* 2、初始化LED */ /* 寄存器地址映射 */ IMX6U_CCM_CCGR1 ioremap(ledsource[0]-start, ressize[0]); SW_MUX_GPIO1_IO03 ioremap(ledsource[1]-start, ressize[1]); SW_PAD_GPIO1_IO03 ioremap(ledsource[2]-start, ressize[2]); GPIO1_DR ioremap(ledsource[3]-start, ressize[3]); GPIO1_GDIR ioremap(ledsource[4]-start, ressize[4]); val readl(IMX6U_CCM_CCGR1); val ~(3 26); /* 清除以前的设置 */ val | (3 26); /* 设置新值 */ writel(val, IMX6U_CCM_CCGR1); /* 设置GPIO1_IO03复用功能将其复用为GPIO1_IO03 */ writel(5, SW_MUX_GPIO1_IO03); writel(0x10B0, SW_PAD_GPIO1_IO03); /* 设置GPIO1_IO03为输出功能 */ val readl(GPIO1_GDIR); val ~(1 3); /* 清除以前的设置 */ val | (1 3); /* 设置为输出 */ writel(val, GPIO1_GDIR); /* 默认关闭LED1 */ val readl(GPIO1_DR); val | (1 3) ; writel(val, GPIO1_DR); /* 注册字符设备驱动 */ /*1、创建设备号 */ if (leddev.major) { /* 定义了设备号 */ leddev.devid MKDEV(leddev.major, 0); register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME); } else { /* 没有定义设备号 */ alloc_chrdev_region(leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申请设备号 */ leddev.major MAJOR(leddev.devid); /* 获取分配号的主设备号 */ } /* 2、初始化cdev */ leddev.cdev.owner THIS_MODULE; cdev_init(leddev.cdev, led_fops); /* 3、添加一个cdev */ cdev_add(leddev.cdev, leddev.devid, LEDDEV_CNT); /* 4、创建类 */ leddev.class class_create(THIS_MODULE, LEDDEV_NAME); if (IS_ERR(leddev.class)) { return PTR_ERR(leddev.class); } /* 5、创建设备 */ leddev.device device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME); if (IS_ERR(leddev.device)) { return PTR_ERR(leddev.device); } return 0; } /* * description : platform驱动的remove函数移除platform驱动的时候此函数会执行 * param - dev : platform设备 * return : 0成功;其他负值,失败 */ static int led_remove(struct platform_device *dev) { iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); cdev_del(leddev.cdev);/* 删除cdev */ unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */ device_destroy(leddev.class, leddev.devid); class_destroy(leddev.class); return 0; } /* platform驱动结构体 */ static struct platform_driver led_driver { .driver { .name imx6ul-led, /* 驱动名字用于和设备匹配 */ }, .probe led_probe, .remove led_remove, }; /* * description : 驱动模块加载函数 * param : 无 * return : 无 */ static int __init leddriver_init(void) { return platform_driver_register(led_driver); } /* * description : 驱动模块卸载函数 * param : 无 * return : 无 */ static void __exit leddriver_exit(void) { platform_driver_unregister(led_driver); } module_init(leddriver_init); module_exit(leddriver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(gui);#include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include stdlib.h #include string.h /*************************************************************** 文件名 : ledApp.c 版本 : V1.0 描述 : platform驱动驱测试APP。 其他 : 无 使用方法 ./ledApp /dev/platled 0 关闭LED ./ledApp /dev/platled 1 打开LED ***************************************************************/ #define LEDOFF 0 #define LEDON 1 /* * description : main主程序 * param - argc : argv数组元素个数 * param - argv : 具体参数 * return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { int fd, retvalue; char *filename; unsigned char databuf[2]; if(argc ! 3){ printf(Error Usage!\r\n); return -1; } filename argv[1]; /* 打开led驱动 */ fd open(filename, O_RDWR); if(fd 0){ printf(file %s open failed!\r\n, argv[1]); return -1; } databuf[0] atoi(argv[2]); /* 要执行的操作打开或关闭 */ retvalue write(fd, databuf, sizeof(databuf)); if(retvalue 0){ printf(LED Control Failed!\r\n); close(fd); return -1; } retvalue close(fd); /* 关闭文件 */ if(retvalue 0){ printf(file %s close failed!\r\n, argv[1]); return -1; } return 0; }觉得有帮助的话打赏一下呗。。