1386 字
7 分钟
Linux Platform驱动通用框架

设备树#

Platform驱动需要读取设备树中的compatible信息用于of_match_table进行匹配:

/* Custom LED pinctrl */
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "custom-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};

Platform驱动#

Platform驱动本质还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张”platform”的皮,目的是使用驱动->总线->设备这个驱动框架来实现驱动的分离和分层

Platform设备声明和注册#

需要手动定义一个platform_driver类型的结构体变量,然后实现结构体中的各个成员变量,主要是匹配方法和probe函数。然后,在驱动文件的xxx_open()函数中调用platform_driver_register函数以注册该Platform设备:

static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led", //Platform设备名称
.of_match_table = led_of_match, //Platform匹配表
},
.probe = led_probe, //Probe函数
.remove = led_remove, //驱动卸载函数
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver); //初始化设备时注册Platform设备
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver); //关闭设备时卸载Platform设备
}

Linux官方会使用module_platform_driver(gpio_led_driver)来向Kernel注册Platform设备,这个宏展开之后为:

static int __init gpio_led_driver_init(void)
{
return platform_driver_register (&(gpio_led_driver));
}
module_init(gpio_led_driver_init);
static void __exit gpio_led_driver_exit(void)
{
platform_driver_unregister (&(gpio_led_driver));
}
module_exit(gpio_led_driver_exit);

这实际上就是标准的注册和删除Platform驱动的步骤,使用这个宏可以大大简化Platform设备的驱动代码。

设备匹配表#

驱动中需要声明一个of_device_id类别的匹配表,用于Platform从设备树查找对应设备:

struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
}

通常都是使用compatible属性和设备树节点中的compatible值进行比对以进行匹配:

/* Platform匹配设备树 */
static const struct of_device_id led_of_match[] = {
{.compatible = "custom-gpioled"},
};

probe函数#

probe函数在驱动和设备匹配完成后执行,对于字符设备而言,原先在xxx_open中的代码可以移到probe函数中执行,即只在Platform驱动和设备匹配完成后执行,而不是在打开文件后就执行。probe函数的形参为已经声明并且注册过的platform_device类型的结构体

/* platfrom函数的probe驱动,匹配后此函数执行 */
static int led_probe (struct platform_device *dev)
{
printk("LED driver and device has beem matched!\r\n");
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);
leddev.minor = MINOR(leddev.devid);
}
printk("Device reg ok, major = %d, minor = %d\r\n", leddev.major, leddev.minor);
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(LEDDEV_NAME);
if (IS_ERR(leddev.class)){
return PTR_ERR(leddev.class);
}
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)){
return PTR_ERR(leddev.device);
}
leddev.nd = of_find_node_by_path("/gpioled");
if (leddev.nd == NULL){
printk("Unable find node\r\n");
return -EINVAL;
}
leddev.led0_gpio = of_get_named_gpio(leddev.nd, "led-gpio", 0);
if (leddev.led0_gpio < 0){
printk("unable find gpio\r\n");
return -EINVAL;
}
gpio_request(leddev.led0_gpio, "led0");
gpio_direction_output(leddev.led0_gpio, 1);
return 0;
}

完整代码示例#

驱动#

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/libata.h> /* 新版kernel不再支持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.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#define LEDDEV_CNT 1
#define LEDDEV_NAME "dtsplatled"
#define LEDOFF 0
#define LEDON 1
struct leddev_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int led0_gpio;
};
struct leddev_dev leddev;
void led0_switch(u8 sta)
{
switch (sta)
{
case LEDON:
gpio_set_value(leddev.led0_gpio, 0);
break;
case LEDOFF:
gpio_set_value(leddev.led0_gpio, 1);
break;
}
}
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = (struct leddev_dev *)&leddev;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
unsigned char databuf[2];
unsigned char led_sta;
ret = copy_from_user(databuf, buf, cnt);
if (ret < 0){
printk("copy from user failed\r\n");
return -EFAULT;
}
led_sta = databuf[0];
switch (led_sta)
{
case LEDON:
led0_switch(LEDON);
break;
case LEDOFF:
led0_switch(LEDOFF);
break;
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/* platfrom函数的probe驱动,匹配后此函数执行 */
static int led_probe (struct platform_device *dev)
{
printk("LED driver and device has beem matched!\r\n");
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);
leddev.minor = MINOR(leddev.devid);
}
printk("Device reg ok, major = %d, minor = %d\r\n", leddev.major, leddev.minor);
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(LEDDEV_NAME);
if (IS_ERR(leddev.class)){
return PTR_ERR(leddev.class);
}
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)){
return PTR_ERR(leddev.device);
}
leddev.nd = of_find_node_by_path("/gpioled");
if (leddev.nd == NULL){
printk("Unable find node\r\n");
return -EINVAL;
}
leddev.led0_gpio = of_get_named_gpio(leddev.nd, "led-gpio", 0);
if (leddev.led0_gpio < 0){
printk("unable find gpio\r\n");
return -EINVAL;
}
gpio_request(leddev.led0_gpio, "led0");
gpio_direction_output(leddev.led0_gpio, 1);
return 0;
}
/* Platform驱动被移除时执行此函数 */
static int led_remove(struct platform_device *dev)
{
gpio_set_value(leddev.led0_gpio, 1);
cdev_del(&leddev.cdev);
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
/* Platform匹配设备树 */
static const struct of_device_id led_of_match[] = {
{.compatible = "custom-gpioled"},
};
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aki");

应用程序#

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "errno.h"
#include "linux/ioctl.h"
#include "sys/select.h"
#include "sys/time.h"
#include "sys/poll.h"
#include "signal.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char databuf[2];
if (argc != 3){
printf("error usage\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0){
printf("unable open file %s\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if (ret < 0){
printf("LED control failed\r\n");
close(fd);
return -1;
}
ret = close(fd);
if (ret < 0){
printf("file %s unable close\r\n", filename);
return -1;
}
return 0;
}

使用Linux原生LED驱动#

Linux原生支持了通过GPIO控制LED的驱动,位于/drivers/leds下。通常LED会作为系统运行指示灯(呼吸灯)或者硬盘工作指示灯进行工作。6.6.52版本Kernel下原生LED驱动支持默认是打开的,仅需要手动修改设备树。设备树的节点编写规则位于/Documentation/devicetree/bindings/leds/leds-gpio.txt。添加以下节点:

dtsleds { /* LED设备 */
compatible = "gpio-leds";
led0 {
label = "red"; /* label属性,一般为LED灯的名字,用颜色区分 */
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
default-state = "on"; /* 默认打开 */
linux,default-trigger = "heartbeat"; /* 作为系统呼吸灯 */
};
};

编译设备树,LED即作为系统呼吸灯运行。

Linux Platform驱动通用框架
https://arkmostima.pages.dev/posts/linuxplatform驱动通用框架/
作者
Aki
发布于
2025-03-28
许可协议
CC BY-NC-SA 4.0