嵌入式硬件学习(十一)—— platform驱动框架

一、驱动程序分离

1、分离的概念

驱动分离是指将驱动程序中与硬件直接相关的部分 和与硬件无关的部分 分开,使得同一类设备的驱动可以共享通用代码 ,只需针对不同硬件实现特定的部分。

2、分离的作用

  1. 提高代码复用:通用代码可以被多个硬件平台共享
  2. 降低开发难度:开发者只需关注硬件特定的部分
  3. 增强可移植性:更容易将驱动移植到新平台

3、分离的实现方式

a) 设备与驱动分离

Linux设备模型的核心思想之一,通过platform_device和platform_driver实现:

c 复制代码
/* 设备部分 - 描述硬件资源 */
static struct platform_device my_device = {
    .name = "my_device",
    .id = -1,
    .resource = my_resources,
    .num_resources = ARRAY_SIZE(my_resources),
};

/* 驱动部分 - 实现操作逻辑 */
static struct platform_driver my_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",
    },
};

b) 使用设备树(Device Tree)

将硬件描述信息从代码中分离出来,放在DTS文件中:

dts 复制代码
my_device {
    compatible = "vendor,my-device";
    reg = <0x12345678 0x1000>;
    interrupts = <0 45 4>;
};

驱动通过of_match_table匹配设备:

c 复制代码
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    {},
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_device",
        .of_match_table = my_of_match,
    },
    .probe = my_probe,
    .remove = my_remove,
};

二、如何实现驱动程序分层、分离

1、简介

把驱动程序编写所涉及的两个方面抽象出来
device:与硬件相关的概念,称为设备;
driver:与逻辑相关的概念,称为驱动。

可以为这二者分别编写驱动模块,二者各自分开编写便是分离,从而使之可以各自独立变化,以提高代码的复用性 。那么问题来了,既然二者分别是不同的驱动模块,那么driver由如何去找到对应的device呢?

这里需要引入另外一个概念: bus即总线 。也就是说,驱动和设备都各自挂载到一条总线上 ,该总线上可以挂载n个驱动和m个设备,驱动和设备必须具有一种机制,使之能够找到彼此,进而实现对设备的操作。需要注意的是, bus既可以是具体的,也可以是抽象的。platform便是Linux操作系统提供的一根抽象的总线。

2、数据类型

platform总线驱动程序中我们要涉及三个方面的数据类型platform_device、 bus_type和platform_driver

  1. platform_device:这个数据类型是用于描述设备的,而且这里linux 使用了面向对象程序设计思想中的继承概念(is-a)platform_device继承自device ---我们只要记住platform_device将自动拥有来自于device的一切成员。此外platform_device还拥有(has-a)resource ,这个概念是面向对象的组合。由于platform_device拥有一个resource,因此platform_device就可以使用resource所提供的所有成员, resource的作用就是提供一个接口,向驱动程序传递硬件资源信息

  2. platform_driver:该数据类型继承自 device_driver ,同样也自动拥 有来自于device_driver 的所有成员;
    3. bus_type:linux系统为我们用该数据类型创建了一个变量--- platform_bus_type,注意,这是个变量名而非类型名!也就是说我们压根不需要定义一个什么platform_bus_type类型变量,这个就是变量本身。顾名思义,这个东东就是Linux系统提供的那条现成的平台总线

三、驱动编写

1、platform_device编写

  1. device模块:在模块初始化时注册platform_device ,卸载模块时注销即可。
c 复制代码
static int __init led_device_init(void)
{
   return platform_device_register(&led_device);
}

static void __exit led_device_exit(void)
{
   platform_device_unregister(&led_device);
}
  1. 在注册前必须创建一个platform_device变量并初始化
c 复制代码
struct platform_device led_device = 
{
	.name = "Mini2440_leds",
	.id = -1,
	.dev = 
	{
		.release = led_release
	},
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources
};
  1. 成员resource和release的初始化
c 复制代码
void led_release(struct device *dev)
{
	printk("leds has been released\n");
}

static struct resource led_resources[] = 
{
	[0] = 
	{
		.start = 0x56000010,
		.end = 0x56000010 + 8,
		.name = "led1~4",
		.flags = IORESOURCE_IO
	}
};

2、platform_driver编写

  1. 首先是注册和注销
c 复制代码
static int __init led_driver_init(void)
{
	return platform_driver_register(&led_driver);
}

static void __exit led_driver_exit(void)
{
	platform_driver_unregister(&led_driver);
}
  1. 在注册前首先要初始化结构体,注意这里的name必须和device的name一致
c 复制代码
static struct platform_driver led_driver = 
{
	.driver = 
	{
		.name = "Mini2440_leds",
		.owner = THIS_MODULE
	},
	.probe = led_probe,
	.remove = led_remove
};
  1. 在probe函数中取出device中的资源,并注册字符设备
c 复制代码
static  int led_probe(struct platform_device *dev)
{
	int ret ;
	unsigned int GPBCON;
	printk("led_probe\n");
	ret = misc_register(&led_dev);
	if(ret)
	{
		return ret;
	}

	GPBCON = dev->resource[0].start;
	regGPBCON = ioremap(GPBCON, 8);
	regGPBDAT = regGPBCON + 1;

	*regGPBCON &= ~((3 << 10) | (3 << 12) | (3 << 14) | (3 << 16));
	*regGPBCON |= (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16);

	 *regGPBDAT |= (0x0F << 5);

	return 0;
}
  1. 剩下的部分就和之前的字符设备类似了
c 复制代码
unsigned int *regGPBCON;
unsigned int *regGPBDAT;
int led_driver_open(struct inode *p_node, struct file *fp)
{
	printk("open\n");
	return 0;
}

ssize_t led_driver_read(struct file *fp, char __user *user_buffer, size_t n, loff_t * offset)
{
	printk("read\n");
	return 0;
}

void ledOn(unsigned int n)
{
	 *regGPBDAT |= (0x0F << 5);
	if(n < 1 || n > 4)
	{
		return;
	}
	*regGPBDAT &= ~(1 << (n + 4));
}

ssize_t led_driver_write(struct file *fp, const char __user *user_buffer, size_t n, loff_t *offset)
{
	char s[10];
	
	copy_from_user(s, user_buffer, n);
	ledOn(s[0]);

	printk("write\n");
	return n;
}

int led_driver_close(struct inode *p_node, struct file *fp)
{
	printk("close\n");
	return 0;
}

struct file_operations fops =
{
	.owner = THIS_MODULE,
	.release = led_driver_close,
	.open = led_driver_open,
	.read = led_driver_read,
	.write = led_driver_write,
};

static struct miscdevice led_dev = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.fops = &fops,
	.name = "led"
};

3、完整代码

(1)platform_device.c

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

void led_release(struct device *dev)
{
	printk("leds has been released\n");
}

static struct resource led_resources[] = 
{
	[0] = 
	{
		.start = 0x56000010,
		.end = 0x56000010 + 8,
		.name = "led1~4",
		.flags = IORESOURCE_IO
	}
};

struct platform_device led_device = 
{
	.name = "Mini2440_leds",
	.id = -1,
	.dev = 
	{
		.release = led_release
	},
	.resource = led_resources,
	.num_resources = ARRAY_SIZE(led_resources)
};

static int __init led_device_init(void)
{
	return platform_device_register(&led_device);
}

static void __exit led_device_exit(void)
{
	platform_device_unregister(&led_device);
}

module_init(led_device_init);
module_exit(led_device_exit);


MODULE_LICENSE("GPL");

(2)platform_driver.c

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>

unsigned int *regGPBCON;
unsigned int *regGPBDAT;
int led_driver_open(struct inode *p_node, struct file *fp)
{
	printk("open\n");
	return 0;
}

ssize_t led_driver_read(struct file *fp, char __user *user_buffer, size_t n, loff_t * offset)
{
	printk("read\n");
	return 0;
}

void ledOn(unsigned int n)
{
	 *regGPBDAT |= (0x0F << 5);
	if(n < 1 || n > 4)
	{
		return;
	}
	*regGPBDAT &= ~(1 << (n + 4));
}

ssize_t led_driver_write(struct file *fp, const char __user *user_buffer, size_t n, loff_t *offset)
{
	char s[10];
	
	copy_from_user(s, user_buffer, n);
	ledOn(s[0]);

	printk("write\n");
	return n;
}

int led_driver_close(struct inode *p_node, struct file *fp)
{
	printk("close\n");
	return 0;
}

struct file_operations fops =
{
	.owner = THIS_MODULE,
	.release = led_driver_close,
	.open = led_driver_open,
	.read = led_driver_read,
	.write = led_driver_write,
};

static struct miscdevice led_dev = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.fops = &fops,
	.name = "led"
};

static  int led_probe(struct platform_device *dev)
{
	int ret ;
	unsigned int GPBCON;
	printk("led_probe\n");
	ret = misc_register(&led_dev);
	if(ret)
	{
		return ret;
	}

	GPBCON = dev->resource[0].start;
	regGPBCON = ioremap(GPBCON, 8);
	regGPBDAT = regGPBCON + 1;

	*regGPBCON &= ~((3 << 10) | (3 << 12) | (3 << 14) | (3 << 16));
	*regGPBCON |= (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16);

	 *regGPBDAT |= (0x0F << 5);

	return 0;
}

int led_remove(struct platform_device *dev)
{
	printk("led_remove\n");
	misc_deregister(&led_dev);
	return 0;
}

static struct platform_driver led_driver = 
{
	.driver = 
	{
		.name = "Mini2440_leds",
		.owner = THIS_MODULE
	},
	.probe = led_probe,
	.remove = led_remove

};

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

static void __exit led_driver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);


MODULE_LICENSE("GPL");
相关推荐
TDengine (老段)9 分钟前
TDengine 中 TDgpt 的模型评估工具
大数据·数据库·机器学习·ai·时序数据库·tdengine·涛思数据
董莉影30 分钟前
学习嵌入式第二十二天
数据结构·学习·算法·链表
我爱学嵌入式1 小时前
C 语言第 17 天学习笔记:从二级指针到内存布局的进阶指南
c语言·笔记·学习
五月茶1 小时前
MySQL——黑马
数据库·mysql
一号IT男2 小时前
MySql MVCC的原理总结
数据库·mysql
我感觉。2 小时前
旅游mcp配置(1)
数据库
酷飞飞2 小时前
ADC常用库函数(STC8系列)
单片机·嵌入式硬件·51单片机·嵌入式·stc8
人生游戏牛马NPC1号2 小时前
学习 Android (十六) 学习 OpenCV (一)
android·opencv·学习
Badman2 小时前
Redis与DB的数据一致性问题梳理
数据库·redis·后端
荔枝吻2 小时前
【沉浸式解决问题】mysql-connector-python连接数据库:RuntimeError: Failed raising error.
数据库·python·mysql