基于platform总线实现gpio的操作

一、摘要:
linux内核的驱动的实现是分成驱动、总线、设备模型,采用面向对象的编程思维。为了减低成本、功耗,大多数嵌入式微处理器的SOC上面已经集成i2c、spi等总线,但是并不是所有设备都符合这些总线。所以,linux内核为了维护驱动是高内聚、低耦合并且符合驱动、总线、设备模型,而开发出一个platform虚拟总线。
二、原理:
1、明确硬件的基本信息或者资源,分配并初始化struct platform_device *plat_dev变量。
2、根据struct platform_device *plat_dev变量传递过来的硬件资源进行初始化处理(映射、申请、根据具体功能配置相应的寄存器),明确获取信号的流程以及提交信号到用户空间的过程。根据前面的步骤初始化struct platform_driver *plat_drv变量,最后把plat_drv变量注册进内核。
3、plat_dev和plat_drv的关联,platform总线会维护着该总线的驱动和设备这两条双向链表,当有platform_device变量注册是,会遍历驱动链表的驱动与之匹配(具体的匹配函数是platform总线的match指向的匹配函数)。相反,如果注册platform_driver变量,就会遍历设备链表的每一个设备与该注册的驱动进行匹配。每当匹配成功后,会进行调用platform_driver变量的probe指向的探测函数。如果匹配失败,就会先加入链表,等待合适的设备/驱动注册后再匹配。

三、源码:
1、注册platform_device的源码:

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

/**********指定需要用到的GPIO的基地址************/
#define GPIO0 0xfffff700
#define GPIO1 0xfffff701
/**********自定义的结构体,为了保存硬件信息************/
struct _key_info{
	char *name;
	unsigned long gpio_base;
	unsigned long gpio_num;

};
/**********指明需要用到的硬件资源************/
struct resource res[] = {
	[0] = {
		.start = 10,
		.end = 10,
		.flags = IORESOURCE_IRQ,
	},
	[1] = {
		.start = 12,
		.end = 12,
		.flags = IORESOURCE_IRQ,
	}
};
/**********初始化需要的硬件信息************/
struct _key_info key_info[] = {
	[0] = {
		.gpio_base = GPIO0,
		.gpio_num = 10,
		.name = "GPIO0-B2",
	},
	[1] = {
		.gpio_base = GPIO1,
		.gpio_num = 12,
		.name = "GPIO1-B4"
	},

};

/**********定义、初始化platform_device变量************/
struct platform_device key_dev = {
	//该设备名最好和驱动的名字一致,这是设备和驱动匹配是否成功的最后保障。
	.name = "rk3399_key_demo",	
	.resource = res,
	.num_resources = 2,
	.dev = {
		//该指针可以指向传递给驱动的私有数据,所以用此来指定保存其它硬件信息的自定义结构体变量
		.platform_data = key_info, 
	}
};

int __init rk3399_key_dev_init(void)
{
	//加载设备模块时,注册platform_device变量
	platform_device_register(&key_dev);
	return 0;
}

void __exit rk3399_key_dev_exit(void)
{
	//卸载模块时,注销platform_device变量
	platform_device_unregister(&key_dev);
}

module_init(rk3399_key_dev_init);
module_exit(rk3399_key_dev_exit);
MODULE_LICENSE("GPL");

注:硬件信息,需要根据自已的硬件环境来定,上述的硬件信息和rk3399的平台信息不对应,主要是想提供一个框架。

2、注册platform_driver变量:

/*
*步骤:
*1、使用字符设备来创建一个设备文件,可以使用户空间通过该设备文件读取键值。
*2、使用到中断,当硬件有数据的时候,会产生中断通知CPU。避免了CPU不断轮询键值,提高CPU工作效率。
*3、使用定时器进行消抖,每当一个中断到来的时候,设置定时器超时处理函数延迟10ms执行。
*4、使用等待队列使进程没有新的键值读取的时候,进入睡眠状态,让出CPU。
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/irq.h>

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

/***********自定义需要的结构体和定义所需的变量*************/
#define DRV_NAME "rk3399_key_demo"

//struct input_dev *in_dev = NULL;
struct timer_list dev_time;
struct cdev *c_dev;

struct class *dev_class;
struct device *dev_device;

wait_queue_head_t wait_que;


dev_t dev_num;
int dev_minor = 0;
int dev_major = 0;
int key_value;
int key_control_flag = 0;

struct _key_info{
	char *name;
	unsigned long gpio_base;
	unsigned long gpio_num;

};

struct _gpio_reg{
	void *gpiocon;
	void *gpiopub;
	void *gpiodat;
}gpio_reg[2];

/***********下面的四个函数是进程对设备的具体实现**************/
int rk3399_key_open(struct inode *inode, struct file *filp)
{
	return 0;
}

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

ssize_t rk3399_key_read(struct file *filp, char __user *buf, size_t count, loff_t *loff)
{
	int r_size = count;
	if(r_size > sizeof(int)){
		r_size = sizeof(int);
	}

	/*******当条件不满足/为0时,进入睡眠等待*********/
	wait_event_interruptible(wait_que, key_control_flag);

	/*******条件满足时,要更改条件状态,以防重复读取某个值*********/
	key_control_flag = 0;

	/*********把键值上报给进程***********/
	copy_to_user(buf, &key_value, r_size);
	
	return r_size;
}

ssize_t rk3399_key_write(struct file *filp, const char __user *buf, size_t count, loff_t *loff)
{
	return count;
}


/************对gpio进行映射,并设置*******************/
void gpio_key_init(unsigned long addr, unsigned long gpio_num, int num)
{
	/*********gpio addr ioremap*********/
	gpio_reg[num].gpiocon = ioremap(addr, 24); //rk3399 64位/8字节,需要映射3个寄存器
	gpio_reg[num].gpiopub = gpio_reg[num].gpiocon + 8;
	gpio_reg[num].gpiodat = gpio_reg[num].gpiocon + 16;

	/**********gpio set****************/
	/*下面可以通过控制register的值来设置,gpiocon为输入设置寄存器,gpiopub为上/下拉设置*/
	//...
}


/*************定时器超时处理函数*****************/
void rk3399_key_time_func(unsigned long data)
{
	struct _key_info *pdata = (struct _key_info *)data;
	unsigned long pintdata;
	switch(pdata->gpio_num){
		case 10: 
			pintdata = *(unsigned long *)(gpio_reg[0].gpiodat);
			if(pintdata == 0){ //按下
				key_value = 0x01;
			}else{//松开
				key_value = 0x02;
			}
			break;
		case 12:
			pintdata = *(unsigned long *)(gpio_reg[1].gpiodat);
			if(pintdata == 0){ //按下
				key_value = 0x03;
			}else{//松开
				key_value = 0x04;
			}
			break;
		default :
			return;
	}

	/****在唤醒等待队列前,设置条件为真******/
	key_control_flag = 1;

	/*******唤醒在等待队列上睡眠的某一个进程********/
	wake_up_interruptible(&wait_que);
}


/*******中断处理函数*********/
irqreturn_t dev_irq_isr(int irq_num,void *data)
{
	/*******每发生一个中断,都把定时器延迟10ms********/
	dev_time.data = (unsigned long)data;
	mod_timer(&dev_time, jiffies + 1 * HZ / 100);

	return IRQ_HANDLED;
}

/********文件操作函数集合**********/
struct file_operations fop = {
		.owner = THIS_MODULE,
		.open = rk3399_key_open,
		.write = rk3399_key_write,
		.read = rk3399_key_read,
		.release = rk3399_key_release,
};	


int rk3399_key_probe(struct platform_device *dev)
{
	int ret = -1, i;
	struct resource *res = dev->resource;
	struct _key_info *pdata = dev->dev.platform_data;

	/*********根据不同情况,可以动态或者静态申请设备号**************/
	if(!dev_major){
		alloc_chrdev_region(&dev_num, dev_minor, 1, DRV_NAME);
		dev_major = MAJOR(dev_num);
		dev_minor = MINOR(dev_num);
	}else{
		dev_num = MKDEV(dev_major, dev_minor);
		ret = register_chrdev_region(dev_num, 1, DRV_NAME);
		if(ret < 0){
			printk("register chrdev region fail.\n");
			return ret;
		}
	}

	/*************申请字符设备的空间、初始化以及向内核添加该字符设备**************/
	c_dev = cdev_alloc();
	if(IS_ERR(c_dev)){
		printk("cdev_alloc fail: %ld,\n",PTR_ERR(c_dev));
		goto cdev_err;
	}
	cdev_init(c_dev, &fop);
	cdev_add(c_dev, dev_num, 1);

	/*************为该模块申请一个类***************/
	dev_class = class_create(THIS_MODULE, DRV_NAME);
	if(IS_ERR(dev_class)){
		printk("class_create fail.\n");
		goto class_create_fail;
	}

	/*************在该类上,根据设备号申请设备***************/
	dev_device = device_create(dev_class, NULL, dev_num, NULL, DRV_NAME);
	if(IS_ERR(dev_device)){
		printk("device_create fail.\n");
		goto device_create_fail;
	}

	/*************初始化等待队列头***************/
	init_waitqueue_head(&wait_que);

	/*************初始化定时器,并且指定定时器超时处理函数***************/
	init_timer(&dev_time);
	dev_time.function = rk3399_key_time_func;
	

	/*************gpio的初始化,以及中断的申请***************/
	for(i = 0; i < dev->num_resources; i++){
		gpio_key_init(pdata[i].gpio_base, pdata[i].gpio_num, i);
		ret = request_irq(res[i].start, dev_irq_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
							pdata[i].name, &pdata[i]);
		if(ret < 0){
			printk("request_irq fail.\n");
			goto request_irq_fail;
		}
	}


	return 0;

request_irq_fail:
	del_timer(&dev_time);
	for(--i; i >= 0; i--){
		iounmap(gpio_reg[i].gpiocon);
		free_irq(res[i].start, &pdata[i]);
	}
	device_destroy(dev_class, dev_num);
device_create_fail:
	class_destroy(dev_class);
class_create_fail:
	cdev_del(c_dev);

cdev_err:
	unregister_chrdev_region(dev_num, 1);
	return ret;
	
}


/*********卸载模块时,需要释放资源************/
int rk3399_key_remove(struct platform_device *dev)
{
	struct resource *res = dev->resource;
	struct _key_info *pdata = dev->dev.platform_data;
	int i;
	del_timer(&dev_time);
	for(i = 0; i < dev->num_resources; i++){
		iounmap(gpio_reg[i].gpiocon);
		free_irq(res[i].start, &pdata[i]);
	}
	
	device_destroy(dev_class, dev_num);
	class_destroy(dev_class);
	cdev_del(c_dev);
	unregister_chrdev_region(dev_num, 1);
	return 0;
}


struct platform_driver rk3399_key_drv = {
	.probe = rk3399_key_probe,
	.remove = rk3399_key_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = DRV_NAME,
	}
};

int __init rk3399_key_drv_init(void)
{
	platform_driver_register(&rk3399_key_drv);
	return 0;
}

void __exit rk3399_key_drv_exit(void)
{


	platform_driver_unregister(&rk3399_key_drv);
}

module_init(rk3399_key_drv_init);
module_exit(rk3399_key_drv_exit);
MODULE_LICENSE("GPL");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值