嵌入式驱动开发详解9(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 的一个具体实例

c 复制代码
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 中

c 复制代码
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 的驱动模型:

c 复制代码
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 中

c 复制代码
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 属性值来匹配驱动。

c 复制代码
	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子系统,如果想要更简单的操作,可以直接用系统自带的驱动,把设备树写成对应的格式然后使能相应的驱动即可,这里不做赘述:

c 复制代码
#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
相关推荐
小仇学长8 分钟前
Linux内核编程(二十一)USB应用及驱动开发
linux·驱动开发·usb
跳河轻生的鱼4 小时前
海思Linux-DEMO(1)-sample_venc(h265,h264)视频流文件的获取
linux·驱动开发·学习
lishing613 小时前
Linux驱动开发(17):输入子系统–电阻触摸驱动实验
linux·运维·驱动开发
Moshow郑锴1 天前
什么是TDD测试驱动开发(Test Driven Development)?
驱动开发·tdd
千千道2 天前
linux的线程同步(条件变量和锁)
linux·arm开发·驱动开发·物联网·arm
智者知已应修善业2 天前
【74HC192减法24/20/72进制】2022-5-17
驱动开发·经验分享·笔记·硬件工程
逝灮2 天前
【蓝桥杯——物联网设计与开发】Part2:OLED
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·oled
Mr zhua3 天前
(四)配置有线网口、SSH登陆、文件传输以及运行交叉编译程序测试
驱动开发·ssh
7yewh3 天前
LVGL 移植到 Arduino IDE(适用SP32 Arduino RP系列)
arm开发·驱动开发·嵌入式硬件·mcu·物联网·ui