嵌入式驱动开发详解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
相关推荐
Evan_ZGYF丶5 小时前
【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?
linux·驱动开发·android14·rk3576
sukalot1 天前
window显示驱动开发—视频呈现网络简介
驱动开发
sukalot2 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday2 天前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot2 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot2 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8284 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday4 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发
被遗忘的旋律.4 天前
Linux驱动开发笔记(十)——中断
linux·驱动开发·笔记
路溪非溪4 天前
Linux驱动如何向应用层提供sysfs操作接口
linux·arm开发·驱动开发