Linux设备驱动开发(一)—— platform总线、设备和驱动

platform总线、设备和驱动


一个现实的Linux设备和驱动通常都需要挂载在一种总线上,对于本身依附于PCIUSB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统中,SoC系统中集成的独立的外设控制器,挂接在SoC内存空间的外设却不依附于此类总线。因此,Linux发明了一种虚拟的总线,称为platform总线(虚拟总线),相应的设备称为platform_device,驱动称为platform_driver
注意: 所谓的platform_device不是与字符设备、块设备、网络设备相提并论的概念,而是Linux提供的一种附加手段

platform总线的优点

  • 可以通过platform总线,可以遍历所有的platform总线设备;platform本质其实也是kset、kobject,具有kobject的特性
  • 实现设备与驱动的分离,通过platform总线,设备与驱动是分开注册的,通过platform总线的probe来随时检测与设备匹配的驱动,如匹配上即进行这个设备的驱动注册;
  • 由于上面这个优势,一个驱动可以供同类的几个设备使用;

platform总线以及platform总线设备驱动的实现流程

  • platform总线注册
  • platform_device注册
  • platform_driver注册
  • 设备与驱动的匹配
  • 驱动的注册

platform总线三大要素


1. paltform_bus:不需要自己创建

platform总线的注册:platform总线的注册是linux内核工程师已经设注册好的;

重点看一下.match = platform_match函数;platform_driver和platform_device就是通过这个函数来匹配的

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中的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);
 
    /* 首先匹配id_table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
    /*其次,直接匹配driver和device的名字 */
    return (strcmp(pdev->name, drv->name) == 0);
}

**注:**在platform_driver中如果有id_table的话,就优先匹配id_table中的,没有在匹配device和driver中的名字。

2. platform_device

struct platform_device {
	const char *name;    //名字用作于匹配
	int id;              //一般直接写-1
	struct device dev;   //继承了device父类
	u32 num_resource;    //资源的数量
	struct resouce *resouce;  //资源:办函了一个设备的地址和中断
}; 

注册和注销(了解即可):

int platform_device_register(struct platform_device *pdev);
int platform_device_unregister(struct platform_device_ *pdev);

使用的platform_device_register这个函数把设备注册到内核中;内核会自动查找platform_device链表以及platform_driver链表,当match以后字自动执行platform_driver的probe函数;

platform_device的注册过程:

  1. 设置好platform_device结构体(对于led驱动来说关键是name、dev->platform_data两个元素)
  2. 初始化好dev->platform_data结构体,这里主要涉及到led驱动所要用到的gpio,

这里我们可以看到linux内核platform驱动框架的设计思想:

  • 首先设备和驱动是分开的,同类设备有共性的部分,不同的部分,不同的部分在初始化的即被设置好;共性的部分内核工程师以及设置好;
  • 然后在通过一个匹配函数如果内核链表的设备与驱动链表的驱动匹配,则会自动安装驱动,否则不会安装驱动;

platform_devicce中有一个重要的结构resource资源结构,该结构中包含了设备的硬件资源和中断资源等,在platform_device中加入。
resource结构的原型:

struct resource {
    resource_size_t start;  //资源起始地址
    resource_size_t end;    //资源结束地址
    const char *name;     
    unsigned long flags;    //资源类型
    struct resource *parent, *sibling, *child;
};

在实际的应用中,有可能有多个设备要注册,因此可以添加多个设备的硬件资源

static struct resource led_resource[] = {
    [0] = {
        .start = 0x56000050,
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 5,
        .end   = 5,
        .flags = IORESOURCE_IRQ,
    },

};
//有的设备中需要用到中断资源
	[2] = {
		.start = 67,
		.end = 67,
		.flags = IORESOURCE_IRQ 
	},

platform_device.c

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

#define GPIO_REG_BASE 0x11400000

#define GPF3_CON GPIO_REG_BASE + 0x01E0
#define GPF3_SIZE 24


#define GPF1_CON GPIO_REG_BASE + 0x0C20
#define GPF1_SIZE 24

//一个设备可能有多个资源
struct resource led_res[] = {
	[0] = {
		.start = GPF3_CON,
		.end = GPF3_CON + GPF3_SIZE -1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = GPF1_CON,
		.end = GPF1_CON + GPF3_SIZE -1,
		.flags = IORESOURCE_MEM,
	},
	
	//有的设备可能有中断资源
	[2] = {
		.start = 67,
		.end = 67,
		.flags = IORESOURCE_IRQ,
	},
};

//platform_device
struct platform_device led_pdev = {
	.name = "exynos4412_led",     //用于匹配
	.id = -1,
	.num_resources - ARRAY_SIZE(led_res),
	.resource = led_res,
};

static int __init plat_led_dev_init(void)
{
	//注册一个platform_device
	return platform_device_register(&led_pdev);
}

static void __exit plat_led_dev_exit(void)
{
	platform_device_unregister(&led_pdev);
}

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

3. platform_driver

struct platform_driver{
	int (*probe)(struct platform_device *);      //匹配成功之后,调用的函数probe
	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;                 //继承了driver父类
	const struct platform_device_id *id_table;   //如果driver支持多个平台,可以在列表中写出来
}

注册和注销:

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

在id_table中写出支持的设备

const struct platform_device_id led_id_table[] = {
		{"exynos4412_led", 0x4444},
		{"s5pv210_led", 0x2222},
		{"s3c2410_led", 0x3333},
		{"s3c6410_led", 0x3333},
};	

platform_driver.c

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

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


//设计一个全局的设备对象
struct led_dev{
	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)
{
	printk("-----%s------------\n", __FUNCTION__);
	return 0;

}
int led_pdrv_close(struct inode *inode, struct file *filp)
{
	printk("-----%s------------\n", __FUNCTION__);
	return 0;
}





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

};


int led_pdrv_probe(struct platform_device *pdev)
{
	printk("-----%s------------\n", __FUNCTION__);
	int ret;

	samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
	if(samsung_led == NULL)
	{
		printk("kzalloc errorn\n");
		return -ENOMEM;
	}

	/*
		a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口
		b, 创建设备节点
		c, 初始化硬件
					ioremap(地址);  //地址从pdev需要获取
					readl/writle();
		d,实现各种io接口: xxx_open, xxx_read, ..

	*/

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

	samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");

	samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0),
							NULL, "led0");
	
	//获取资源
	// 参数1: 从哪个pdev中获取资源
	// 参数2:  资源类型
	// 参数3: 表示获取同种资源的第几个
	
	samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	
	int irqno = platform_get_irq(pdev, 0);
	// 等同于platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	printk("--------irqno = %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_pdrv_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_led", 0x4444},
		{"s5pv210_led", 0x2222},
		{"s3c2410_led", 0x3333},
		{"s3c6410_led", 0x3333},
};	

struct platform_driver led_pdrv = {
	.probe = led_pdrv_probe,
	.remove = led_pdrv_remove,
	.driver = {
			.name = "samsung_led_drv",
				//可以用于做匹配
				// /sys/bus/platform/drivers/samsung_led_drv
	},
	.id_table = led_id_table,
};


static int __init plat_led_pdrv_init(void)
{
	printk("-----%s------------\n", __FUNCTION__);
	//注册一个pdrv
	
	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");


  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值