Linux驱动开发之platform总线编程


一,为什么有平台总线


【1】用于平台升级,比如三星的:2410,2440,6410,S5pv210,4412…硬件平台升级的时候,部分模块的控制方式基本上类似的,但是不同平台模块的地址是不一样的。

【2】比如GPIO的控制逻辑:

  • 配置GPIO的输入输出功能------>配置GPxxCON寄存器
  • 给GPIO数据寄存器设置高低电平------->配置GPxxDATA寄存器

不同平台逻辑操作基本是一样的,但是地址不一样

【3】问题:(如果不用平台总线)
当平台升级的时候对于相似的设备驱动,需要编写很多次,这样就会重复编写大量相似代码。

【4】解决:(引入平台总线)
device(中断和地址)和driver(操作逻辑)分离。在升级的时候,只要修改device中的信息即可;实现一个driver代码能够驱动多个平台相似的模块,且修改的代码量很少。


二,平台总线三元素



1.总线对象platform_bus


【1】platform_bus不需要自己创建,系统自动创建

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

【2】匹配方法

  • 优先匹配pdriver中的id_table,里面包含了支持不同平台的名字
  • 直接匹配pdevice和driver中的名字(因为pdriver中没用直接成员是表示名字的,用的是pdriver中继承的父类driver中的成员const char *name)

【3】内核自带的匹配函数

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

	/* match against the id table first */
	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);
}

【4】匹配函数中的原理:
struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv);

在这里插入图片描述


2.platform_device对象


【1】platform_device

struct platform_device {
	const char	* name;                        //用于做匹配
	int		id;                                //一般都直接给-1
	struct device	dev;                       //继承了device父类
	u32		num_resources;					   //资源个数
	struct resource	* resource;                //资源:包括了一个设备的地址和中断
	const struct platform_device_id	*id_entry;
};

【2】注册和注销

int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)

3.platform_driver对象


【1】platform_driver

struct platform_driver {
	int (*probe)(struct platform_device *);         //匹配成功之后被调用的函数
	int (*remove)(struct platform_device *);        //device移除的时候被调用的函数
	struct device_driver driver;                    //继承了driver父类
	const struct platform_device_id *id_table;      //如果driver支持多个平台,在列表中写出来
}; 

【2】注册和注销

int platform_driver_register(struct platform_driver *drv)
void platform_driver_unregister(struct platform_driver *drv)

三,编写代码:一个能在多平台下使用的led驱动



1.platform_device


【1】LED原理图和寄存器表

在这里插入图片描述

【2】定义地址和中断资源

struct resource {
	resource_size_t start;          //地址开始
	resource_size_t end;            //地址结束
	const char *name;               //描述,自定义
	unsigned long flags;           //标志,区分当前资源描述的是中断还是内存;比如IORESOURCE_MEM表示内存资源,IORESOURCE_IRQ表示中断资源
	struct resource *parent, *sibling, *child;    //资源是父类,兄弟,子类
};

#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_IRQ		0x00000400

【3】注册platform_device,代码编写:plat_led_dev.c

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

#define GPJ0_CON_addr   0xE0200240
#define GPJ0_SIZE  24

//一个设备可能有多个资源
struct resource led_res[] = {            //可扩展数组
	[0] = {
		.start = GPJ0_CON_addr,
		.end = GPJ0_CON_addr+ GPJ0_SIZE -1,
		.flags = IORESOURCE_MEM,
	},

	//有些设备也有中断资源,用于说明中断资源的使用,本驱动中没有太多意义
	[1] = {
		.start = 45,                //中断号
		.end = 45,
		.flags = IORESOURCE_IRQ,
	},
};

struct platform_device led_pdev = {
	.name = "s5pv210",     //用于匹配,优先匹配id_table中的
	.id = -1,
	.num_resources = ARRAY_SIZE(led_res),    //资源个数
	.resource = led_res,
};

static int __init plat_led_dev_init(void)
{
	printk("-----%s------\n",__FUNCTION__);
	//注册一个platform_device
	return platform_device_register(&led_pdev);
}

static void __exit plat_led_dev_exit(void)
{
	printk("-----%s------\n",__FUNCTION__);
	platform_device_unregister(&led_pdev);
}

module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);
MODULE_LICENSE("GPL");

2.platform_driver


【1】注册一个platform_driver,实现操作设备的代码

【2】注册完毕同时,如果和pdev匹配成功,就会自动调用probe方法,其中,probe方法的内容为:对硬件进行操作

  • 注册设备号,并注册fops------->为用户提供一个设备标识,同时提供文件操作io接口
  • 创建设备结点
  • 初始化硬件:1.ioremap(地址)------>地址从pdev获取 2.readl/writel();
  • 实现各种io接口:xx_open(); xx_read()…

【3】获取资源的方式

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

参数

  • 参数1:从哪个pdev中获取资源
  • 参数2:资源类型;比如比如IORESOURCE_MEM表示内存资源,IORESOURCE_IRQ表示中断资源
  • 参数3:表示获取同种资源的第几个

【4】代码编写:plat_led_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/ioport.h>

#include <asm/io.h>
#include <asm/uaccess.h>

struct led_dev{                      //描述LED的信息
	unsigned int dev_major;          //主设备号
	struct class *cls;           
	struct device* dev;
	struct resource *res;  //获取到的内存资源
	void * reg_base;    //表示物理地址映射之后的虚拟地址
};
struct led_dev *samsung_led;

ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int val;
	int ret;

	ret = copy_from_user(&val, buf,count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");
		return -EFAULT;
	}
	if(val)  //亮
	{
		writel(readl(samsung_led->reg_base + 4)| (0x3 << 4) ,samsung_led->reg_base + 4);
	}
	else
	{
		writel(readl(samsung_led->reg_base + 4) & ~(0x3 << 4) ,samsung_led->reg_base + 4);
	}
	return count;
}

int led_pdrv_open (struct inode *inode, struct file *filp)
{
	return 0;
}

int led_pdrv_close (struct inode *inode, struct file *filp)
{
	return 0;
}

const struct file_operations led_fops = {
	.open = led_pdrv_open,
	.release = led_pdrv_close,
	.write = led_pdrv_write,	
};

int led_drv_probe(struct platform_device *pdev)
{
	printk("-----%s------\n",__FUNCTION__);
	
	samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
	if(NULL == samsung_led )
	{
		printk(" kzalloc error\n");
		return -ENOMEM;
	}

	samsung_led->dev_major = register_chrdev(0, "led_drv", & led_fops);
	printk("dev_major = %d",samsung_led->dev_major);

	samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
	if(samsung_led->cls == NULL)
	{
		printk("class_create error\n");
		return -EFAULT;
	}
	
	samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major,0), NULL, "led0");
	if(samsung_led->dev == NULL)
	{
		printk("device_create error\n");
		return -EFAULT;
	}

	//获取资源
	//参数1:从哪个pdev中获取资源
	//参数2:资源类型
	//参数3:表示获取同种 资源的第几个:数组下标从0开始
	samsung_led->res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
	
	int irqno = platform_get_irq(pdev, 0);
	printk("---irq = %d---\n",irqno);

	samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));

	//对寄存器进行配置---输出功能
	writel((readl(samsung_led->reg_base) & ~(0xff<<16))|(0x11<<16),samsung_led->reg_base);
	
	return 0;
}

int led_drv_remove(struct platform_device *pdev)
{
	printk("-----%s------\n",__FUNCTION__);
	iounmap(samsung_led->reg_base);
	device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major,0));
	class_destroy(samsung_led->cls);
	unregister_chrdev(samsung_led->dev_major, "led_drv");
	kfree(samsung_led);
	return 0;
}
	
const struct platform_device_id led_id_table[] = {
	{"exynos4412",0x4444},
	{"s5pv210",0x3333},
	{"s3c2410",0x2222},
	{"s3c6410",0x1111},
};

struct platform_driver led_pdrv = {
	.probe = led_drv_probe,
	.remove = led_drv_remove,
	.driver = {
	.name = "samsung_led_drv",    //用于做匹配 //   /sys/bus/platform/driver/samsung_led_drv
	},
	.id_table = led_id_table,
};

static int __init plat_led_pdrv_init(void)
{
	printk("-----%s------\n",__FUNCTION__);
	//注册一个platform_device
	return platform_driver_register(&led_pdrv);
}

static void __exit plat_led_pdrv_exit(void)
{
	printk("-----%s------\n",__FUNCTION__);
	platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");

3.应用程序编写


【1】应用代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
	int fd;
	int on = 0;
	fd = open("/dev/led0",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(-1);
	}
	while(1)
	{
		on = 1;
		write(fd,&on,4);
		sleep(1);

		on = 0;
		write(fd,&on,4);
		sleep(1);	
	}
	
	close(fd);
	return 0;
}

【2】实验结果
在这里插入图片描述
LED3和LED4实现闪烁。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值