一、linux驱动开发-8.1-platform设备驱动

本文详细介绍了Linux平台驱动模型,包括驱动的分隔与分离、驱动的分层原理。重点讲解了platform总线、platform驱动和platform设备的结构及功能,特别是匹配机制和驱动注册、注销的过程。同时,通过示例展示了如何编写和注册platform驱动,以及如何描述和注册platform设备。
摘要由CSDN通过智能技术生成

一、驱动的分离与分层

1.1、驱动的分隔与分离

       通过驱动的分隔,也就是将主机驱动和设备驱动分隔开来,通过总线就行匹配,当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配
的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。

1.2、驱动的分层

       驱动分层类似网络的7层模型,不同的层负责不同的内容。 

二、platform平台驱动模型

       对于总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。

2.1、platform总线

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

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;    //总线属性
	const struct attribute_group **dev_groups;    //设备属性
	const struct attribute_group **drv_groups;    //驱动属性

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

       match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

       platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.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_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。驱动和设备匹配有四种方法:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

①、 OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是
否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后probe 函数就会执行。

②、ACPI 匹配方式

③、id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型

④、如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

       对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

2.2、platform驱动

       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 函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数!!一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。

       id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,
platform_device_id 结构体内容如下

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

       device_driver 结构体定义在 include/linux/device.h

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};

       of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

       compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

       在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
       当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。

/*
@driver:要注册的platform驱动
@return:0,成功
        <0,失败
*/
int platform_driver_register (struct platform_driver *driver)

       还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动

void platform_driver_unregister(struct platform_driver *drv)

platform驱动框架示例:

struct xxx_dev {

    struct cdev cdev;
}

struct xxx_dev xxxdev;

static int xxx_open()
{
    return 0;
}

static ssize_t xxx_write()
{
    return 0;
}

static struct file_operations xxx_fops = {
    .owner = THIS_MODULE,
    .open  = xxx_open,
    .write = xxx_write,
};

//驱动和设备匹配后此函数就会执行
static int xxx_probe(struct platform_device *dev)
{
    cdev_init(&xxxdev.cdev, &xxx_fops);    //注册字符设备

    return 0;
}

//关闭platform驱动的时候此函数就会执行
static int xxx_remove(struct platform_device *dev)
{
    cdev_del(&xxxdev.cdev);    //删除cdev

    return 0;
}

//匹配表
//最后一个匹配项必须为空
static const struct of_device_id xxx_of_match[] = {
    {.compatible = "xxx-gpio" },
    { /* Sentinel */ }
};


/*
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {
    .driver = {
        .name = "xxx",
        .of_match_table = xxx_of_match,
    },
    .probe = xxx_probe,
    .remove = xxx_remove,
};

/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
    return platform_driver_register(&xxx_driver);
}

/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
    platform_driver_unregister(&xxx_driver);
}

module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk");

2.3、platform设备

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

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 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name字段也要设置为“xxx-gpio”。
       num_resources 表示资源数量,一般为第 28 行 resource 资源的大小。
       resource 表示资源,也就是设备信息,比如外设寄存器等

       在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中

/*
@pdev:要注册的platform设备
@return:0,成功
        <0,失败
*/
int platform_device_register(struct platform_device *pdev)

       如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备

void platform_device_unregister(struct platform_device *pdev)

三、程序编写

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#define LEDDEV_CNT     1     
#define LEDDEV_NAME    "platformled"

#define	LED_ON      1
#define LED_OFF     0

//映射后的虚拟地址
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

//设备结构体
struct leddev_dev{
	dev_t devid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	int major;
	int minor;
};

struct leddev_dev leddev;  

static void led_switch(uint8_t sta)
{
	uint32_t val;
	if (sta == LED_ON)
	{
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);		/* bit3清零,打开LED */
		writel(val, GPIO1_DR);
	} else if (sta == LED_OFF) {
		val = readl(GPIO1_DR);
		val |= (1 << 3);		/* bit3置1,关闭LED */
		writel(val, GPIO1_DR);
	}
}

static int leddev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &leddev;
	printk("leddev open!\n");
	return 0;
}

static ssize_t leddev_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{	
	printk("leddev read\n");
	return 0;
}

static ssize_t leddev_write(struct file *filp, const char __user *buf, size_t count, loff_t *oppos)
{
	int ret;
	uint8_t data_buf[1];
	ret = copy_from_user(data_buf, buf, count);
	if (ret < 0)
	{
		printk("write data fail\n");
		return -EFAULT;
	}

	led_switch(data_buf[0]);

	return 0;
}

static int leddev_release(struct inode *inode, struct file *filp)
{
	printk("led release\n");
	return 0;
}

static const struct file_operations leddev_fops = {
	.owner = THIS_MODULE,
	.open  = leddev_open,
	.read  = leddev_read,
	.write = leddev_write,
	.release = leddev_release,
};

/*
*platform驱动的probe函数
*驱动与设备匹配成功以后此函数就会执行
*/

static int leddev_probe(struct platform_device *dev)
{
	int i = 0;
    unsigned int val = 0;
	int ressize[5];
	struct resource *ledsource[5];

	printk("led driver and device has match\r\n");

	/* 获取资源 */
	for (i = 0; i < 5; i++)
	{
		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
		if (!ledsource[i])
		{
			dev_err(&dev->dev, "NO MEM resource for always on\r\n");
			return -ENXIO;
		}
		ressize[i] = resource_size(ledsource[i]);
	} 

	CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);	
	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);	
	GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
	GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
	
	/* 2、初始化 */
	val = readl(CCM_CCGR1);
	val &= ~(3 << 26);	/* 先清除以前的配置bit26,27 */
	val |= 3 << 26;		/* bit26,27置1 */
	writel(val, CCM_CCGR1);

	writel(0x05, SW_MUX_GPIO1_IO03);	/* 设置复用 */
	writel(0X10B0, SW_PAD_GPIO1_IO03);	/* 设置电气属性 */

	val = readl(GPIO1_GDIR);
	val |= 1 << 3;		/* bit3置1,设置为输出 */
	writel(val, GPIO1_GDIR);

	val = readl(GPIO1_DR);
	val &= ~(1 << 3);		/* bit3清零,打开LED */
	writel(val, GPIO1_DR);

	//分配设备号
	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);
		leddev.minor = MINOR(leddev.devid);
	}
	printk("leddev major=%d, minor= %d\r\n", leddev.major, leddev.minor);
	//初始化cdev
	leddev.cdev.owner = THIS_MODULE;
	cdev_init(&leddev.cdev, &leddev_fops);
	//添加cdev
	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
	//创建类
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	//创建设备
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	
	return 0;
}

static int leddev_remove(struct platform_device *dev)
{
    led_switch(LED_OFF);

	iounmap(CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	//删除设备的类
	device_destroy(leddev.class, leddev.devid);
	//删除类
	class_destroy(leddev.class);
	//删除设备
	cdev_del(&leddev.cdev);
	//注销设备号
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
}

/*
*platform平台驱动结构体
*/
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",
    },
    .probe = leddev_probe,
    .remove = leddev_remove,
};

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

static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
		
	printk("newchrdev exit\n");
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZK");

 

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>

//寄存器物理地址
#define CCM_CCGR1_BASE			(0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE	(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE	(0X020E02F4)
#define	GPIO1_DR_BASE			(0X0209C000)
#define GPIO1_GDIR_BASE			(0X0209C004)

#define REGISTER_LENGTH			4

static void led_release(struct device *dev)
{
	printk("led device released\r\n");
}

/* 资源 */
static struct resource led_resources[] = {
	[0] = {
		.start = CCM_CCGR1_BASE,
		.end   = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = SW_MUX_GPIO1_IO03_BASE,
		.end   = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[2] = {
		.start = SW_PAD_GPIO1_IO03_BASE,
		.end   = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[3] = {
		.start = GPIO1_DR_BASE,
		.end   = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[4] = {
		.start = GPIO1_GDIR_BASE,
		.end   = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
};

/* platform 设备结构体 */
static struct platform_device leddevice = {
	.name = "imx6ul-led",
	.id = -1,
	.dev = {
		.release = &led_release,
	},
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,
};

static int __init leddevice_init(void)
{
	printk("leddevice init\r\n");

	return platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void)
{
	printk("newchrdev exit\n");

	platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZK");

 四、运行测试

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值