5.platfrom平台点亮led
Platform 点亮一个 LED
1. Platform
Platform 是 Linux 为片上外设提供的一种标准驱动模型,其核心思想是将硬件描述与驱动逻辑分离。硬件信息由设备树描述,Linux 根据设备树创建 Platform Device;驱动程序实现 Platform Driver;Platform 总线负责两者的自动匹配,匹配成功后调用 probe() 完成设备初始化。
2. 设备树修改
2.1 在设备树里添加 GPIO 节点
gpioled { #address-cells = <1>; #size-cells = <1>; compatible = "atkalpha,gpioled"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_led>; led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; status = "okay"; };- gpioled
节点名称(Node Name),表示这里定义了一个叫 gpioled 的设备。
在开发板上可以查看是否挂载上:
切换到 device-tree 目录查看: - #address-cells = <1>;
这个属性表示:如果这个节点下面还有子节点,那么子节点的地址用 1 个 cell 表示。 - #size-cells = <1>; 子节点的 size(大小)用 1 个 cell 表示。
- compatible = “atkalpha,gpioled”;
告诉 Linux 我是一个类型为 atkalpha,gpioled 的设备。 - pinctrl-names = “default”;
这个设备有一套默认的引脚配置,default 是这套配置的名字。 - pinctrl-0 = <&pinctrl_led>;
默认使用 pinctrl_led 这组引脚配置,这个配置下方有说明。 - led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
LED 使用 gpio1 控制器的第 3 号引脚。 - status = “okay”;
表示启用这个设备。
如果写:status = “disabled”;,Linux 会忽略这个节点。
2.2 在设备树中添加 pinctrl 引脚配置节点
pinctrl_led: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */ >; };- pinctrl_led:
这个是节点标签,它的作用是给该节点起一个引用名称,方便其他节点引用。 - ledgrp
这个是节点名称,表示它是给 LED 用的。 - MX6UL_PAD_GPIO1_IO03__GPIO1_IO03
含义是把 i.MX6ULL 的 GPIO1_IO03 这个物理引脚配置为 GPIO1_IO03 功能。 - 0x10B0
引脚电气属性配置值,用于设置驱动能力、速度、上下拉等。
3. 驱动代码
3.1 主体框架
以下为主体框架
static int led_probe(struct platform_device *dev) { } static int led_remove(struct platform_device *dev) { return 0; } struct of_device_id led_of_match[] = { {.compatible = ""}, { /* sentinel */ }, }; struct platform_driver led_driver = { .driver = .probe = .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("huangjiajing");整体框架主要由以下四部分组成:
Platform 驱动框架
│
├── probe() 设备匹配成功后执行
├── remove() 驱动卸载或设备移除时执行
├── platform_driver 描述 Platform 驱动
└── 驱动注册与注销
首先是 struct platform_driver led_driver 这个结构体:
struct 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; };里面用到 probe、remove 这两个函数,还有 device_driver driver 这个结构体。
probe():Platform 驱动的入口函数,负责设备初始化和资源申请。remove():Platform 驱动的退出函数,负责资源释放和设备清理。device_driver driver:用于描述驱动信息(如name、of_match_table),供 Linux 完成设备与驱动的匹配。
接下来介绍 device_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; };这里面要用到的是 name 和 of_match_table,用于匹配设备树:
- name 用于指定驱动名称,在没有使用设备树时,Linux 可以根据设备名和驱动名进行匹配。
- of_match_table 用于指定设备树匹配表,Linux 根据设备树节点的 compatible 属性与该匹配表进行匹配。匹配成功后会调用 probe()。
接下来 probe() 和 remove() 的实现逻辑与前一章类似。
3.2 完整代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/string.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/interrupt.h> #include <linux/poll.h> #include <linux/fcntl.h> #include <linux/ide.h> #include <linux/platform_device.h> #define GPIOLED_CNT 1 #define GPIOLED_NAME "dtsplatled" #define LEDOFF 0 #define LEDON 1 /* gpioled设备结构体 */ struct gpioled_dev{ dev_t devid; int major; int minor; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int led_gpio; }; struct gpioled_dev gpioled; /* LED */ static int led_open(struct inode *inode, struct file *filp) { filp->private_data = &gpioled; return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int ret; unsigned char databuf[1]; struct gpioled_dev *dev = filp->private_data; ret = copy_from_user(databuf, buf, count); if(ret < 0) { return -EINVAL; } if(databuf[0] == LEDON) { gpio_set_value(dev->led_gpio, 0); } else if(databuf[0] == LEDOFF) { gpio_set_value(dev->led_gpio, 1); } return 0; } /* 操作集 */ static const struct file_operations led_fops = { .owner = THIS_MODULE, .write = led_write, .open = led_open, .release = led_release, }; static int led_probe(struct platform_device *dev) { ` /* 1,获取设备节点 */ #if 0 gpioled.nd = of_find_node_by_path("/gpioled"); if(gpioled.nd == NULL) { ret = -EINVAL; goto fail_findnode; } #endif gpioled.nd = dev->dev.of_node; /* 2, 获取LED所对应的GPIO */ gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); if(gpioled.led_gpio < 0) { printk("can't find led gpio\r\n"); ret = -EINVAL; goto fail_findnode; } printk("led gpio num = %d\r\n", gpioled.led_gpio); /* 3,申请IO */ ret = gpio_request(gpioled.led_gpio, "led-gpio"); if (ret) { printk("Failed to request the led gpio\r\n"); ret = -EINVAL; goto fail_findnode; } /* 4,使用IO,设置为输出 */ ret = gpio_direction_output(gpioled.led_gpio, 1); if (ret) { goto fail_setoutput; } /* 5,输出底电平,点亮LED灯*/ gpio_set_value(gpioled.led_gpio, 0); return 0; fail_setoutput: gpio_free(gpioled.led_gpio); fail_findnode: return ret; } static int led_remove(struct platform_device *dev) { printk("led remove\r\n"); /* 关灯 */ gpio_set_value(gpioled.led_gpio, 1); /* 注销字符设备驱动 */ cdev_del(&gpioled.cdev); unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); device_destroy(gpioled.class, gpioled.devid); class_destroy(gpioled.class); /* 释放IO */ gpio_free(gpioled.led_gpio); return 0; } struct of_device_id led_of_match[] = { {.compatible = "atkalpha,gpioled"}, { /* sentinel */ }, }; struct platform_driver led_driver = { .driver = { .name = "imx6ull-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("huangjiajing"); 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("huangjiajing");