当前位置: 首页 > article >正文

嵌入式驱动开发详解9(platform驱动)

文章目录

  • 前言
  • platform简介
  • 总线
  • 驱动
  • 设备
  • 设备树下的platform驱动
    • 在设备树中创建设备节点
    • 编写 platform 驱动
  • 后续
  • 参考文献

前言

Linux 系统要考虑到驱动的可重用性,提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们最常打交道的 platform 设备驱动,也叫做平台设备驱动。

platform简介

在实际的驱动开发中,一般 I2C 主机控制器驱动已经由 半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信 息即可,比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上,I2C 的速度是多少等等。相当 于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获 取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动, 设备只负责设备,想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和 设备(device)模型,也就是常说的驱动分离。
在这里插入图片描述

总线

Linux 系统内核使用 bus_type 结构体表示总线,此结构体定义在文件 include/linux/device.h,该结构体有一个match 函数,此函数 就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱 动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有 两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。platform 总线是 bus_type 的一个具体实例

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

其中 platform_match 就是匹配函数,此函数提供了四种不同的匹配方式。

驱动

platform_driver 结构体表示 platform 驱动,此结构体定义在文件 include/linux/platform_device.h 中

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 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类, 然后在此基础上又添加了一些特有的成员变量,device_driver 结构体定义在 include/linux/device.h,其中有一个of_match_table 就是采用设备树的时候驱动使用的匹配表。
id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的第三种方法(不使用设备树)。
当定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用 platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,驱 动 卸 载 函 数 中 通 过 platform_driver_unregister 函 数 卸 载 platform 驱 动。
下面展示一下 platform 的驱动模型:

static const struct file_operations gpioled_fops = {
	.owner	= THIS_MODULE,
	.open	= led_open,
	.write	= led_write,
};
static int led_probe(struct platform_device *dev)
{
	printk("led driver and device matched\r\n");
	return 0;
}
static int led_remove(struct platform_device *dev)
{
	printk("led driver and device removed\r\n");
	return 0;
}
static struct of_device_id led_of_match[] = {
    { .compatible = "hbb-gpioled", },
	{ /* sentinel */ }
};
static struct platform_driver led_dirver ={
    .driver = {
        .name = "led_dirver",   /* platform的驱动名称,用于与非设备树的外设匹配 */
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_dirver);
}
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_dirver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hubinbin");

设备

platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树 的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。当然了,你如果 一定要用 platform_device 来描述设备信息的话也是可以的。platform_device 结构体定义在文件 include/linux/platform_device.h 中

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设 备就无法匹配到对应的驱动。
num_resources 表示资源数量,
resource 表示资源,也就是设备信息,比如外设寄存器等,该结构体中含有以下内容:start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name 表示资源名字 ,flags 表示资源类型。
在以前不支持设备树的 Linux 版本中,用户需要编写 platform_device 变量来描述设备信息, 然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform 设备。
当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述, Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式,设备树到 platform_device 的具体过程就不去详细的追究了,感兴趣的可以自行百度。

设备树下的platform驱动

在设备树中创建设备节点

肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动。

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

编写 platform 驱动

在使用设备树的时候 platform 驱动会通过 of_match_table 来 保存兼容性值,也就是表明此驱动兼容哪些设备。所以of_match_table 将会尤为重要。

下面展示的是一个 led 的platform驱动代码,该代码用到了gpio子系统,如果想要更简单的操作,可以直接用系统自带的驱动,把设备树写成对应的格式然后使能相应的驱动即可,这里不做赘述:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>  //copy
#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/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>  //信号量  互斥体
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_CNT 1
#define LED_NAME "dtsplatform_led"
#define LED_ON 1
#define LED_OFF 0

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;


static int led_open(struct inode *inode, struct file *file)
{
	file->private_data = &gpioled;
	return 0;
}

static ssize_t led_write(struct file *file, const char __user *buf,
				size_t count, loff_t *off)
{
	unsigned char status;
	int ret;
	struct gpioled_dev *dev = file->private_data;
	ret = copy_from_user(&status,buf,count);
	if(ret < 0){
		printk("kernel write failed!!!");
		return -1;
	}
	printk("device write%d\r\n",status);
	if(status == LED_ON){
		gpio_set_value(dev->led_gpio, 0);
	}else if(status == LED_OFF){
		gpio_set_value(dev->led_gpio, 1);
	}
	return 0;
}

static const struct file_operations gpioled_fops = {
	.owner	= THIS_MODULE,
	.open	= led_open,
	.write	= led_write,
};


static int led_probe(struct platform_device *dev)
{
    int ret;
    printk("led driver and device matched\r\n");
	gpioled.nd = of_find_node_by_path("/gpioled");
	if(gpioled.nd == NULL){
		printk("gpioled node cant not find!!\r\n");
		ret = -1;
		goto fail_node;
	}else{
		printk("gpioled node found!!\r\n");
	}
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpio",0);
	if(gpioled.led_gpio < 0){
		printk("cant not get led-gpio\r\n");
		ret = -1;
		goto fail_node;
	}
	printk("led-gpio-num=%d\r\n",gpioled.led_gpio);

	ret = gpio_direction_output(gpioled.led_gpio,1);
	if(ret < 0){
		printk("can`t set gpio!!!\r\n");
	}

	if(gpioled.major){
		gpioled.devid = MKDEV(gpioled.major,gpioled.minor);
		ret = register_chrdev_region(gpioled.devid, LED_CNT, LED_NAME);
	}else{
		ret = alloc_chrdev_region(&gpioled.devid,0,LED_CNT,LED_NAME);
		gpioled.major = MAJOR(gpioled.devid);
		gpioled.minor = MINOR(gpioled.devid);
		printk("alloc_chrdev_region major=%d minor=%d\r\n",gpioled.major, gpioled.minor);
	}
	if (ret < 0) {
		printk("Could not register\r\n");
		goto fail_devid;
	}
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	ret = cdev_add(&gpioled.cdev,gpioled.devid,LED_CNT);
	if(ret < 0){
		printk("Could not cdev\r\n");
		goto fail_cdev;
	}
	gpioled.class = class_create(THIS_MODULE,LED_NAME);
	if(IS_ERR(gpioled.class)){
		ret = PTR_ERR(gpioled.class);
		goto fail_class;
	}
	gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,LED_NAME);
	if(IS_ERR(gpioled.device)){
		ret = PTR_ERR(gpioled.device);
		goto fail_device;
	}

	return 0;
fail_device:
	class_destroy(gpioled.class);
fail_class:
	cdev_del(&gpioled.cdev);
fail_cdev:
	unregister_chrdev_region(gpioled.devid,LED_CNT);
fail_devid:
fail_node:
	return ret;
}

static int led_remove(struct platform_device *dev)
{
    printk("led driver and device removed\r\n");
	gpio_set_value(gpioled.led_gpio,1);
	printk("gpioled_exit\r\n");
	cdev_del(&gpioled.cdev);
	unregister_chrdev_region(gpioled.devid,LED_CNT);

	device_destroy(gpioled.class,gpioled.devid);
	class_destroy(gpioled.class);
    return 0;
}


static struct of_device_id led_of_match[] = {
    { .compatible = "hbb-gpioled", },
	{ /* sentinel */ }
};


static struct platform_driver led_dirver ={
    .driver = {
        .name = "led_dirver",   /* platform的驱动名称,用于与非设备树的外设匹配 */
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};

static int __init leddriver_init(void)
{
    return platform_driver_register(&led_dirver);
}

static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_dirver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hubinbin");

后续

以上便是笔者对platform驱动框架的理解,相比之前初学更深入的理解了这几个结构体之间的关系,也更加深刻理解了有无设备树的区别,目前对驱动的理解就只停留在这个层次,后期如果有需要更深入的研究还会继续更新本专栏。

参考文献

  1. 个人专栏系列文章
  2. 正点原子嵌入式驱动开发指南
  3. 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

http://www.kler.cn/a/465196.html

相关文章:

  • Python 实时获取Linux服务器信息
  • 《Swift 结构体》
  • aws(学习笔记第二十二课) 复杂的lambda应用程序(python zip打包)
  • 【Pytorch报错】AttributeError: cannot assign module before Module.__init__() call
  • Linux硬盘分区 --- 挂载分区mount、卸载分区umount、永久挂载
  • RK3588+FPGA全国产异步LED显示屏控制卡/屏幕拼接解决方案
  • 实践:事件循环
  • STM32入门教程(CAN通信篇)
  • (leetcode算法题)​122. 买卖股票的最佳时机 II​ 和 123. 买卖股票的最佳时机 III
  • PostgreSQL-01-入门篇-简介
  • Redis数据库——数据结构类型
  • 基于16QAM的载波同步和定时同步性能仿真,采用四倍采样,包括Costas环和gardner环
  • tiny RISCV项目学习
  • 系统设计——大文件传输方案设计
  • Springboot 下载附件
  • 靶场搭建问题(技巧)总结
  • DEWA功能介绍
  • Redis 中 Lua 脚本的使用详解
  • 络安全警钟:通过Microsoft Teams和AnyDesk传播的DarkGate恶意软件
  • JavaScript 的 requestAnimationFrame
  • 如果Adobe 退出中国后怎么办
  • 安全框架:Apache Shiro
  • Springboot数据层开发 — 整合jdbcTemplate、mybatis
  • Word格式修改
  • Nginx知识详解(理论+实战更易懂)
  • PDF预览插件