博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Linux驱动开发】platform 设备驱动实验
阅读量:202 次
发布时间:2019-02-28

本文共 8477 字,大约阅读时间需要 28 分钟。

一、基础概念

Linux 系统要考虑到驱动的可重用性,所以有了驱动的分离与分层,即platform 设备驱动,也叫平台设备驱动。

驱动分隔:

  • 将主机驱动和设备驱动分隔开来,比如 I2C、SPI等都会采用驱动分隔的方式来简化驱动的开发。
  • 一般主机驱动半导体厂家已经编写,而设备驱动设备器件的厂家已经编写。
  • 我们只需要提供设备信息即可,例如设备连接到哪个 I2C 接口,I2C 的速度等等。
  • 相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。
分隔后驱动框架
驱动分隔框架
 
Linux 总线、驱动和设备模型:
  • 当向系统注册一个驱动的时候,总线就会在右侧设备中查找与之匹配的设备,然后联系起来。
  • 当向系统中注册一个设备的时候,总线就会在左侧驱动中查找与之匹配的驱动,然后联系起来。
 
Linux 总线、驱动和设备模型

 platform 驱动:

Linux系统内核使用 bus_type 结构体表示总线,此结构体定义在文件include/linux/device.h

 

platform 驱动:

Linux
系统内核使用 platform_driver 结构体表示
platform
驱动,此结构体定义在文件 include/linux/platform_device.h
 
注册 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 驱动。
  • 在不支持设备树的Linux版本中,使用 platform_device 结构体表示 platform 设备,描述设备信息,此结构体定义在文件 include/linux/platform_device.h
  • 当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述,Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式。
注册 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/ 目录下会存在该设备文件。

  • 在 leddriver.c 中设置 led_driver(platform_driver 类型)name 字段为“imx6ul-led”。
  • 驱动模块加载完成以后会在 /sys/bus/platform/drivers/ 目录下存在名为“imx6ul-led”这个文件。
  • 如果设备(gpioled)和驱动(imx6ul-led)匹配成功,会输出 led driver and device was matched!

4、开关 LED 灯

./ledApp /dev/dtsplatled 1 //打开 LED 灯 ./ledApp /dev/dtsplatled 0 //关闭 LED 灯

5、卸载驱动

rmmod leddriver.ko

 

转载地址:http://twzi.baihongyu.com/

你可能感兴趣的文章