IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 正点原子linux阿尔法开发板使用——platform平台总线模型 -> 正文阅读

[系统运维]正点原子linux阿尔法开发板使用——platform平台总线模型

Linux驱动分离与分层

目的:为了提高软件的重用,跨平台性能!!!

控制器驱动和设备驱动分离!!!

将驱动分离:主机控制器驱动和设备驱动,主机控制器驱动一般是半导体厂家写的。在linux驱动框架下编写具体的设备驱动。

二、驱动-总线-设备

驱动和设备。
驱动就是具体的设备驱动。
设备:是设备属性,包括地址范围,如果是IIC器件地址,速度。

总线主要完成总线下设备和驱动之间的匹配。

驱动

驱动数据类型为device_driver,驱动程序向内核注册驱动采用driver_register。
向总线注册驱动的时候,会检查当前总线下的所有设备,有没有与此驱动匹配的设备,如果有的话就执行驱动里面的probe函数。使用driver_register注册驱动。

设备

设备数据类型为device,通过device_register向内核注册设备。
驱动与设备匹配以后驱动的probe函数就会执行,probe函数就是驱动编写人员去编写的!!!!

三、platform平台驱动模型

1、方便开发,linux提出了驱动分离与分层。
2、进一步引出了驱动-总线-设备驱动模型,或者框架。
3、对于SOC内部的RTC,timer等等不好归结为具体的总线,为此linux内核提出了一个虚拟总线:platform总线,platform设备和platform驱动。

四、platform总线注册

注册的内容就是:
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平台而言,platform_match函数就是负责驱动和设备的匹配。

1、无设备树的时候,此时需要驱动开发人员编写设备注册文件,使用platform_device_register函数注册设备。

2,有设备树,修改设备树的设备节点即可。当设备与platform的驱动匹配以后,就会执行platform_driver->probe函数。

使用platform_driver_register向内核注册platform驱动。
platform_driver_register
-> __platform_driver_register (platform_driver)
-> 设置driver的probe为platform_drv_probe, //如果platform_driver的
// probe函数有效的话。
-> driver_register
->执行device_drive->probe,对于platform总线,也就是platform_drv_probe函数。而platform_drv_probe函数会执行platform_driver下的probe函数。

结论:向内核注册platform驱动的时候,如果驱动和设备匹配成功,最终会执行platform_driver的probe函数。

五、platform匹配过程

根据前面的分析,驱动和设备匹配是通过bus->match函数,platform总线下的match函数就是:platform_match。
platform_match
-> of_driver_match_device,设备树
-> acpi_driver_match_device ACPI类型的
-> platform_match_id 根据platform_driver-> id_table
-> strcmp(pdev->name, drv->name) //最终的就是比较字符串,就是platform_device->name,和platform_driver->driver->name。无设备树情况下使用。

1、有设备树的时候:
of_driver_match_device
-> of_match_device(drv->of_match_table, dev) //of_match_table非常重要,
类型为of_device_id。
//compatible属性

实验程序编写

无设备树

两部分:platform_driver、platform_device。以点灯为例。
1、编写向platform总线注册设备。编写驱动需要寄存器地址信息,地址信息使用设备信息,定义在platform_device里面,因此需要在驱动里面获取设备中的信息,或者叫资源。使用函数platform_get_resource()

有设备树

有设备树的时候设备device是由设备树描述的,因此不需要向总线注册设备,而是直接修改设备树。只需要修改设备树,然后编写driver驱动。

驱动和设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体。

platform提供了很多API函数去获取设备相关信息的。

2022.6.10 左神手撕代码 P57

设备加载

cd /sys/bus/
cd platform/

在这里插入图片描述
在这里插入图片描述

驱动加载

在这里插入图片描述
在这里插入图片描述
后期驱动不需要修改 只需要改设备。

无设备树的情况下:

设备代码:

......
#include <linux/irq.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



void leddevice_release(struct device *dev)
{

	printk("leddevice release\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,
	},

};

static struct platform_device leddevice = {

	.name = "imx6ull-led",
	.id = -1,//表示设备无此ID
	.dev = {
		.release = leddevice_release,
	},
	.num_resources = ARRAY_SIZE(led_resources),

	.resource = led_resources,

};

static int __init leddevice_init(void)
{
	return	platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void){
	platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");

驱动代码:

....
#include <linux/platform_device.h>

#define LEDDEV_CNT		1			/* 设备号长度 	*/
#define LEDDEV_NAME		"platled"	/* 设备名字 	*/
#define LEDOFF 			0
#define LEDON 			1

/* 寄存器名 */
static void __iomem *IMX6U_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;

/* leddev 设备结构体 */
struct leddev_dev{
		dev_t devid; /* 设备号 */
		struct cdev cdev; /* cdev */
		struct class *class; /* 类 */
		struct device *device; /* 设备 */
		int major; /* 主设备号 */ 
};

struct leddev_dev leddev; 	/* led设备 */
/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led0_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON){
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF){
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @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[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		return -EFAULT;
	}
	ledstat = databuf[0];		/* 获取状态值 */
	if(ledstat == LEDON) {
		led0_switch(LEDON);		/* 打开LED灯 */
	}else if(ledstat == LEDOFF) {
		led0_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

static int led_probe(struct platform_device *dev)
{
	//printk("led driver probe\r\n");
	int i = 0;

	//int ret = 0;
	int ressize[5];
	u32 val = 0;

	struct resource *ledsource[5];
	printk("led driver and device has matched!\r\n");
	/*获取资源*/
	for (i = 0; i < 5; i++)
	{
		ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
		if (ledsource[i] == NULL) 
		{
			dev_err(&dev->dev, "No MEM resource for always on\n");
			return -ENXIO;
		}
		ressize[i] = resource_size(ledsource[i]);	//
	}

	/*内存映射*/
	IMX6U_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]);

	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);				/* 清除以前的设置 */
	val |= (3 << 26);				/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
	writel(5, SW_MUX_GPIO1_IO03);
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);			/* 清除以前的设置 */
	val |= (1 << 3);			/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 默认关闭LED1 */
	val = readl(GPIO1_DR);
	val |= (1 << 3) ;	
	writel(val, GPIO1_DR);
	
	/* 注册字符设备驱动 */
	/*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 */
	leddev.cdev.owner = THIS_MODULE;
	cdev_init(&leddev.cdev, &led_fops);
	/* 3、添加一个cdev */
	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
	/* 4、创建类 */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		return PTR_ERR(leddev.class);
	}
	/* 5、创建设备 */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		return PTR_ERR(leddev.device);
	}
	return 0;
}

static int led_remove(struct platform_device *dev)
{
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	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 struct platform_driver  led_driver = {
	.driver = {
		.name = "imx6ull-led",  //驱动名字和设备名字匹配
	},
	.probe = led_probe,
	.remove = led_remove,
};

static int __init leddriver_init(void)
{
	return	platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");

应用层代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 	0
#define LEDON 	1


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;
}

有设备树情况下:

有设备树的情况下,设备是由设备树描述的,不需要向总线注册设备,而是直接修改设备树。
只需要修改设备树,然后编写驱动。
设备树配置!!!
在这里插入图片描述

左手撕 P2022.6.11

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
驱动和设备匹配成功后,设备信息就会从设备树节点转成platform_device结构体。

platform 提供很多获取资源的函数。

代码分解

头文件包含

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.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_NAME "gpioled"
#define   LED_COUNT 1 

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

struct gpioled_dev {
	struct cdev cdev;
	struct class *class;/*类:为了自动创建节点*/
	struct device *device;/*设备:为了自动创建节点*/
	dev_t devid; //设备号
	int major;   //主设备号
	int minor;   //次设备号
	struct device_node *nd; //设备节点
	int led_gpio;
};

struct gpioled_dev gpioled; //led设备

驱动函数

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

static ssize_t gpioled_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
	struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
	int retvalue = 0;
	unsigned char databuf[1];
	retvalue = copy_from_user(databuf,buf,cnt);
	if (retvalue < 0)
	{
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	if (databuf[0] == LEDON)
	{
		gpio_set_value(dev->led_gpio,0);
	}
	else if(databuf[0] == LEDOFF){
		gpio_set_value(dev->led_gpio,1);
	}
	
	return 0;
}

static int gpioled_release(struct inode *inode,struct file *filp)
{
	//struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
	return 0;
}

static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.write = gpioled_write,
	.open = gpioled_open,
	.release = gpioled_release,
};

led_probe(字符设备创建流程)


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

static int led_probe(struct platform_device *dev)
{
	int ret = 0; 
	/*注册字符设备*/
	gpioled.major = 0;
	if (gpioled.major){	
		gpioled.devid = MKDEV(gpioled.major,0);//设备号
		ret = register_chrdev_region(gpioled.devid,LED_COUNT,LED_NAME);
	}	else{
		ret = alloc_chrdev_region(&gpioled.devid,0,LED_COUNT,LED_NAME);
		gpioled.major = MAJOR(gpioled.devid);
		gpioled.minor = MINOR(gpioled.devid);
	}printk("led major = %d led minor = %d \r\n",gpioled.major,gpioled.minor);
	if (ret < 0){
		goto faile_devid;
	}
	/*2、添加字符设备*/
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev,&led_fops);
	ret = cdev_add(&gpioled.cdev,gpioled.devid,LED_COUNT);
	if(ret<0){
		goto fail_cdev;
	}
	/*3、创建设备类*/
	gpioled.class = class_create(THIS_MODULE,LED_NAME);
	if(IS_ERR(gpioled.class)){
		ret = PTR_ERR(gpioled.class);
		printk("can't class_create \r\n");
		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);
		printk("can't device_create \r\n");
		goto fail_device;
	}
	/*4、获取设备属性内容*/
	//通过platform
	gpioled.nd = dev->dev.of_node;
	/*获取LED所对应的GPIO*/
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-goios",0);
	if (gpioled.led_gpio < 0)
	{
		printk("can't find gpio \r\n");
		goto fail_findnode;
	}
	printk("led gpio number = %d\r\n",gpioled.led_gpio);
	/*申请的GPIO*/
	ret = gpio_request(gpioled.led_gpio,"led-gpio");
	if (ret)
	{
		printk("gpio_request errno \r\n");
		ret = -EINVAL;
	}
	/*使用IO*/
	ret = gpio_direction_output(gpioled.led_gpio,1);
	if (ret)
	{
		goto fail_setoutput;
	}
	
	gpio_set_value(gpioled.led_gpio,0);

	printk("led_probe\r\n");
	return 0;

fail_setoutput:
	gpio_free(gpioled.led_gpio);
fail_findnode:
	device_destroy(gpioled.class,gpioled.devid);
fail_device:
	class_destroy(gpioled.class);
fail_class:
	cdev_del(&gpioled.cdev);
fail_cdev:
	unregister_chrdev_region(gpioled.devid,LED_COUNT);
faile_devid:
	return ret;

}


led_remove函数

static int led_remove(struct platform_device *dev)
{
	/* 关灯 */
	gpio_set_value(gpioled.led_gpio,1);
	/*删除设备*/
	cdev_del(&gpioled.cdev);
	/*释放设备号*/
	unregister_chrdev_region(gpioled.devid,LED_COUNT);

	/*摧毁设备*/
	device_destroy(gpioled.class,gpioled.devid);

	/*释放类*/
	class_destroy(gpioled.class);

	/*释放IO*/
	gpio_free(gpioled.led_gpio);

	return 0;
}

platform_driver 函数


static struct platform_driver  led_driver = {
	.driver = {
		.name = "imx6ull-led",  //无设备树和设备进行匹配,驱动名字
		.of_match_table = led_of_match,//设备树匹配表
	},
	.probe = led_probe,
	.remove = led_remove,
};



模块加载与卸载

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

static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}
//模块加载函数
module_init(leddriver_init);

//模块卸载
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("qhy");
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-14 22:57:06  更:2022-06-14 22:57:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:45:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码