设备树
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即作为系统呼吸灯运行。