本文共 8477 字,大约阅读时间需要 28 分钟。
Linux 系统要考虑到驱动的可重用性,所以有了驱动的分离与分层,即platform 设备驱动,也叫平台设备驱动。
驱动分隔:
platform 驱动:
Linux系统内核使用 bus_type 结构体表示总线,此结构体定义在文件include/linux/device.h
platform 驱动:
注册 platform 驱动 | int platform_driver_register (struct platform_driver *driver) | driver :要注册的 platform 驱动 返回值: 负数,失败; 0 ,成功 |
卸载 platform 驱动 | void platform_driver_unregister(struct platform_driver *drv) | drv :要卸载的 platform 驱动 返回值: 无 |
platform 设备
注册 platform 设备 | int platform_device_register(struct platform_device *pdev) | pdev :要注册的 platform 设备 返回值: 负数,失败; 0 ,成功 |
注销 platform 设备 | void platform_device_unregister(struct platform_device *pdev) | pdev :要注销的 platform 设备 返回值: 无 |
驱动流程:
1、定义设备号长度、设备名字2、定义 leddev 设备结构体3、LED打开/关闭 函数 —— gpio_set_value4、打开设备函数 led_open5、向设备写数据函数(led_write)6、定义设备操作函数(file_operations)7、flatform驱动的probe函数(led_probe),当驱动与设备匹配以后此函数就会执行 (1)设置设备号(alloc_chrdev_region) (2)注册设备(cdev_init、cdev_add) (3)创建类(class_create) (4)创建设备(device_create) (5)初始化IO(of_find_node_by_path、of_get_named_gpio)8、platform驱动的remove函数(led_remove),移除platform驱动的时候此函数会执行 (1)关闭LED(gpio_set_value) (2)删除cdev(cdev_del) (3)注销设备号(unregister_chrdev_region) (4)删除设备(device_destroy) (5)删除类(class_destroy)9、定义匹配列表(of_device_id)10、定义platform驱动结构体(platform_driver)11、驱动模块加载函数(leddriver_init)—— platform_driver_register12、驱动模块卸载函数(leddriver_exit)—— platform_driver_unregister13、module_init 和 module_exit 指定驱动入口和出口函数14、LICENSE 和作者信息
leddriver.c
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /***************************************************************文件名 : leddriver.c描述 : 设备树下的platform驱动***************************************************************/#define LEDDEV_CNT 1 /* 设备号长度 */#define LEDDEV_NAME "dtsplatled" /* 设备名字 */#define LEDOFF 0#define LEDON 1/* leddev设备结构体 */struct leddev_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ struct device_node *node; /* LED设备节点 */ int led0; /* LED灯GPIO标号 */};struct leddev_dev leddev; /* led设备 *//* * @description : LED打开/关闭 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED * @return : 无 */void led0_switch(u8 sta){ if (sta == LEDON ) gpio_set_value(leddev.led0, 0); else if (sta == LEDOFF) gpio_set_value(leddev.led0, 1); }/* * @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[2]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue < 0) { printk("kernel write failed!\r\n"); return -EFAULT; } ledstat = databuf[0]; if (ledstat == LEDON) { led0_switch(LEDON); } else if (ledstat == LEDOFF) { led0_switch(LEDOFF); } 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){ printk("led driver and device was matched!\r\n"); /* 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_init(&leddev.cdev, &led_fops); cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT); /* 3、创建类 */ leddev.class = class_create(THIS_MODULE, LEDDEV_NAME); if (IS_ERR(leddev.class)) { return PTR_ERR(leddev.class); } /* 4、创建设备 */ leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME); if (IS_ERR(leddev.device)) { return PTR_ERR(leddev.device); } /* 5、初始化IO */ leddev.node = of_find_node_by_path("/gpioled"); if (leddev.node == NULL){ printk("gpioled node nost find!\r\n"); return -EINVAL; } leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0); if (leddev.led0 < 0) { printk("can't get led-gpio\r\n"); return -EINVAL; } gpio_request(leddev.led0, "led0"); gpio_direction_output(leddev.led0, 1); /* led0 IO设置为输出,默认高电平 */ return 0;}/* * @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行 * @param - dev : platform设备 * @return : 0,成功;其他负值,失败 */static int led_remove(struct platform_device *dev){ gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭LED */ cdev_del(&leddev.cdev); /* 删除cdev */ unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */ device_destroy(leddev.class, leddev.devid); class_destroy(leddev.class); return 0;}/* 匹配列表 */static const struct of_device_id led_of_match[] = { { .compatible = "iot-gpioled" }, { /* Sentinel */ }};/* platform驱动结构体 */static struct platform_driver led_driver = { .driver = { .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */ .of_match_table = led_of_match, /* 设备树匹配表 */ }, .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("pjw");
ledApp.c
#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描述 : 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;}
Makefile
KERNELDIR := /home/pjw/linux/kernel/iot_kernel/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)obj-m := leddriver.obuild: kernel_moduleskernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
1、将 leddriver.ko 和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录 2、主板终端进入到目录 lib/modules/4.1.15 中,输入如下命令加载 leddriver.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令modprobe leddriver.ko //加载驱动模块
3、如果设备树中添加了 gpioled 节点,在 /sys/bus/platform/devices/ 目录下会存在该设备文件。
4、开关 LED 灯
./ledApp /dev/dtsplatled 1 //打开 LED 灯 ./ledApp /dev/dtsplatled 0 //关闭 LED 灯
5、卸载驱动
rmmod leddriver.ko
转载地址:http://twzi.baihongyu.com/