驱动里调用I2c和Gpio,驱动里调用驱动

一. 简介

  • 来源:应项目需求,需将3个I2c和6个GPIO封装成一个驱动供上层应用层调用,遂有了此文。
  • 开发板:Halley2
  • Linux版本:3.10.14
  • 开发环境:Manhattan(基于君正官方提供的开发环境,很好用)。
  • 经验:说实话,这是我第一个“正经”开发的驱动。

二. 编写驱动前的准备

  • 先明确需求,3个I2c的设备地址和片上内存都为一个字节,6个Gpio分别为5输入1输出
  • 留给上层接口的选择,刚开始在read/write上琢磨,但是总觉得这样操作好啰嗦,后来受应用层操作I2c的方式的影响,就索性自定义一个结构体用ioctl来传参了,虽操作也是有点啰嗦,但能接受,Bingo。
  • 网上搜索相关资料,了解到驱动里使用I2c的一些基操,见本文最后的参考文章。

三. 头文件的编写

  • 留给上层使用的结构体,因需求的原因,一切从简,只支持单字节读写,自己可扩展
  • 一些关于器件的宏定义,由ioctl函数参数的第二个参数指定
struct sakway_msg_t{
	#define SAKWAY_I2C_MSG_WRITE	0
	#define SAKWAY_I2C_MSG_READ		1
	int 			rw_flag;			// 读写标志位,可由上面宏来替代
	short			chip_addr;			// 片上内存地址(默认单字节)
	unsigned char 	wmsg;				// 写入的内容
	unsigned char 	index;				// 指定获取GPIO输入状态的GPIO下标,如下表
	unsigned char *	rmsg;				// 读取的内容
};
/*	input
 *	index 		0		1		2		3		4
 *	gpio		PA04	PA12	PC24	PC25	PD02
 */
#define SAKWAY_CMD_DRV_TEMP			0	// 温度传感器	--	0x92
#define SAKWAY_CMD_DRV_MCUG			1	// 单片机G传感器	--	0x34
#define SAKWAY_CMD_DRV_MCUF			2	// 单片机F传感器	--	0x38

#define SAKWAY_CMD_GPIO_GET			3	// 获取5个Gpio的输入状态,由结构体里的rmag来得值
#define SAKWAY_CMD_GPIO_SET			4	// 设置1个Gpio的输出状态,由结构体里的wmsg来传值

四. 源文件的编写

  • 先是相关结构体的封装
#define I2C_TEMP_ADDR			0x48	// 内部温度传感器的设备地址
#define I2C_MCUG_ADDR			0x34	// GD单片机的I2c设备地址
#define I2C_MCUF_ADDR			0x38	// FM单片机的I2c设备地址

typedef struct sakway_t SAKWAY;

struct sakway_hw_t{
	unsigned int io_in[5];			// 输入GPIO
	unsigned int io_out;			// 输出GPIO
	unsigned int io[0];
	int i2c_id;						// 指定要操作的I2c器件
	#define SAKWAY_I2C_ID_CPLD			0u
	#define SAKWAY_I2C_ID_MCUG			1u
	#define SAKWAY_I2C_ID_MCUF			2u
	struct i2c_client *i2c_clit[3];		// 三个I2c设备
	struct i2c_adapter *i2c_adper;		// 适配器
	struct i2c_driver *i2c_drv;			// 这个没用上,可删
};

struct sakway_t{
	SAKWAY *this;
	struct sakway_hw_t *hw;	
	struct mutex lock;					// 锁
	unsigned int majoy_id;
};
  • I2c和Gpio的初始化,及读写分配
// I2c写操作
static int __sakway_write_reg(SAKWAY* this, short reg_addr,unsigned char reg_value){
	char write_buf[4];
	int status;
	struct i2c_msg msg = 
	{
		this->hw->i2c_clit[this->hw->i2c_id]->addr, 0, 2, write_buf
	};
	
	memset(write_buf, 0, 4);
	
	write_buf[0] = reg_addr&0x00ff;
	write_buf[1] = reg_value;
	msg.len = 2;
	
	mutex_lock(&(this->lock));
	status = i2c_transfer(this->hw->i2c_adper, &msg, 1);
	mutex_unlock(&(this->lock));
	return (status == 1)? 0 : -1;
}
// I2c读操作
static int __sakway_read_reg(SAKWAY* this, short reg_addr,unsigned char *reg_value){
	char buf[4];
	int status;
	struct i2c_msg msg[2] = {
		{
			this->hw->i2c_clit[this->hw->i2c_id]->addr, 0, 2, buf
		},
		{
			this->hw->i2c_clit[this->hw->i2c_id]->addr, I2C_M_RD, 1, reg_value
		}
	};
	memset(buf, 0, 4);
	buf[0] = reg_addr&0x00ff;
	msg[0].len = 1;
	mutex_lock(&(this->lock));
	status = i2c_transfer(this->hw->i2c_adper, &msg, 2);
	mutex_unlock(&(this->lock));
	return (status == 2)? 0 : -1;
}

// 将所需的I2c的描述和设备地址填入下列
static struct i2c_board_info __initdata sakway_board_info[] = {
	{
		I2C_BOARD_INFO("TEMP-i2c", I2C_TEMP_ADDR),
		.irq = -1,
	},
	{
		I2C_BOARD_INFO("MCUG-i2c", I2C_MCUG_ADDR),
		.irq = -1,
	},
	{
		I2C_BOARD_INFO("MCUF-i2c", I2C_MCUF_ADDR),
		.irq = -1,
	},
	{},
};
// 分配I2c设备和GPIO,设备构造
static int __sakway_hw_init(SAKWAY* this){
	int ret , _i;
	struct i2c_client* client;
	struct i2c_adapter* adapter;
	
	printk(DRV_NAME "\t init sakway adapter. \n");
	adapter = i2c_get_adapter(0);
	if (!adapter) {
		ret = -ENXIO;
		printk(DRV_NAME "\terror: %d : init i2c adapter failed.\n", ret);
		return ret;
	}
	strlcpy(adapter->name, "sakway-i2c",sizeof(adapter->name));
	printk(DRV_NAME "\tadd %s adapter successful.\n", adapter->name);
	printk(DRV_NAME "\tinit i2c client. \n");
	for(_i = 0; _i < ARRAY_SIZE(this->hw->i2c_clit); _i ++){
		
		client = i2c_new_device(adapter, &sakway_board_info[_i]);
		printk(DRV_NAME "\tclient address is %d bits\n", client->flags & I2C_CLIENT_TEN ? 10 : 7);
		if (!client) {
			ret = -ENXIO;
			printk(DRV_NAME "\terror: %d : init i2c client failed.\n", ret);
			return ret;
		}
		printk(DRV_NAME "\tadd %s client successful.\n", client->name);
		printk(DRV_NAME "\tinfo:\n");
		printk(DRV_NAME "\tclient addr: %x\n", client->addr);
		printk(DRV_NAME "\tclient name: %s\n", client->name);
		printk(DRV_NAME "\tadapter name: %s\n", client->adapter->name);
		
		this->hw->i2c_clit[_i] = client;	
	}
	this->hw->i2c_adper = adapter;
	this->hw->io_in[0] = 32*0 + 4;		// PA04
	this->hw->io_in[1] = 32*0 + 12;		// PA12
	this->hw->io_in[2] = 32*2 + 24;		// PC24
	this->hw->io_in[3] = 32*2 + 25;		// PC25
	this->hw->io_in[4] = 32*3 + 2;		// PD02
	
	this->hw->io_out = 32*3 + 1;		// PD01
	return 0;
}
// 设备析构
static int
__sakway_remove(SAKWAY *this){
	int _i ;
	printk(DRV_NAME "\tRelease I2c adapter.\n");
	i2c_put_adapter(this->hw->i2c_adper);
	printk(DRV_NAME "\tRelease I2c client.\n");
	for (_i = 0; _i < ARRAY_SIZE(Sakway->hw->i2c_clit); _i ++) {
		i2c_unregister_device(this->hw->i2c_clit[_i]);
	}
	return 0;
}
  • 相关文件操作接口函数的实现
// 初始化该设备进行的内存分配
static SAKWAY* sakway_new_dev(void){
	SAKWAY* dev = (SAKWAY*)kmalloc(sizeof(SAKWAY),GFP_ATOMIC);
	dev->hw = (struct sakway_hw_t*)kmalloc(sizeof(struct sakway_hw_t),GFP_ATOMIC);
	return dev;
}

// 没用上
static size_t 
sakway_driver_write(struct file* filp, 
								 const char __user* buffer, 
								 size_t size, loff_t* f_pos) 
{
    return 0;
}
// 没用上
static size_t 
sakway_driver_read(struct file* filp, 
								const char __user* buffer, 
								size_t size, loff_t* f_pos) 
{
    return 0;
}
// ioctl 的实现
static int	
sakway_driver_ioctl( struct file* filp, 
								unsigned int cmd, 
								unsigned long arg)
{
	struct sakway_msg_t _msg,* _m;
	switch(cmd){
		case SAKWAY_CMD_DRV_TEMP:
		case SAKWAY_CMD_DRV_MCUG:
		case SAKWAY_CMD_DRV_MCUF:				// I2c设备
			if( cmd < ARRAY_SIZE(Sakway->hw->i2c_clit) ){
				Sakway->hw->i2c_id = cmd;		// 指明操作那个I2c设备
			}
			_m = (struct sakway_msg_t *)arg;	// 类型转换,获取内容
			copy_from_user(&_msg, _m, sizeof(struct sakway_msg_t));	// 拷贝到内核
			if(_msg.rw_flag == SAKWAY_I2C_MSG_READ){	// 读
				__sakway_read_reg(Sakway, _msg.chip_addr, _msg.rmsg);
			}
			else{										// 写
				__sakway_write_reg(Sakway, _msg.chip_addr, _msg.wmsg);
			}
		break;
		case SAKWAY_CMD_GPIO_GET:			// gpio状态的获取
			_m = (struct sakway_msg_t *)arg;
			copy_from_user(&_msg, _m, sizeof(struct sakway_msg_t));
			if(_msg.index < ARRAY_SIZE(Sakway->hw->io_in)){		// 防止越界
				mutex_lock(&(Sakway->lock));
				*(_msg.rmsg) = gpio_get_value(Sakway->hw->io_in[_msg.index]);	// 指明哪个Gpio口
				mutex_unlock(&(Sakway->lock));
			}
		break;
		case SAKWAY_CMD_GPIO_SET:			// 设置gpio口的状态
			_m = (struct sakway_msg_t *)arg;
			copy_from_user(&_msg, _m, sizeof(struct sakway_msg_t));
			mutex_lock(&(Sakway->lock));
			gpio_set_value(Sakway->hw->io_out, _msg.wmsg);
			mutex_unlock(&(Sakway->lock));
		break;
		default:
			// pr_info(DRV_NAME "ioctl cmd failed \n");
		break;
	}
	return 0;
}
// 打开
static int 
sakway_driver_open(struct inode* node, struct file* filp) 
{
	return 0;
}

static struct file_operations sakway_fops = 
{
        .owner              =   THIS_MODULE,
        .open               =   sakway_driver_open,
        .read               =   sakway_driver_read,
        .write              =   sakway_driver_write,
        .unlocked_ioctl     =   sakway_driver_ioctl,
};

static struct miscdevice sakway_miscdev = 
{
        .name               =   DRV_NAME,
        .fops               =   &sakway_fops,
        .nodename           =   DRV_NAME,
};
  • 模块的构造和析构
static const char *Sakway_led_lable[]={		// 给gpio的描述
	"PA04","PA12","PC24", "PC25", "PD02","PD01"
};

static void	__init sakway_driver_init(void)
{
	int ret,i;

	printk(DRV_NAME "\tApply mem for Sakway.\n");
	Sakway = sakway_new_dev();	// 分配内存空间
    if (!Sakway) {
        ret = -ENOMEM;
        printk(DRV_NAME "\Sakway new device failed!\n");
        kfree(Sakway);
		return ret;
    }
	ret = misc_register(&sakway_miscdev);	// 设备的注册
	if (ret < 0 ) {
		printk(DRV_NAME "\tRegister Char Driver Failed. ret = %d\n", ret);
		kfree(Sakway);
		return ret;
	}
	printk(DRV_NAME "\tgenerated a driver nod : /dev/Sakway \n");
	printk(DRV_NAME "\tInit hardware...\n");
	ret = __sakway_hw_init(Sakway);			// I2c的初始化
	if (ret < 0) {
		kfree(Sakway);
		return ret;
	}
	for (i= 0; i < ARRAY_SIZE(Sakway->hw->io_in); i ++) {	// 输入gpio的初始化
		ret = gpio_request(Sakway->hw->io_in[i], Sakway_led_lable[i]);	//申请输入gpio
		if (ret < 0) {
			kfree(Sakway);
			misc_deregister(&sakway_miscdev);
			printk(DRV_NAME "\trequest gpio %d for Sakway failed, ret = %d\n", Sakway->hw->io_in[i], ret);
			return ret;
		} else {
		    printk(DRV_NAME "\trequest gpio %d for  Sakway set succussful, ret = %d\n", Sakway->hw->io_in[i], ret);
		}
		gpio_direction_input(Sakway->hw->io_in[i]);		// 配置为输入
	}
	ret = gpio_request(Sakway->hw->io_out, "PD01");		// 申请输出gpio
	if (ret < 0) {
		kfree(Sakway);
		misc_deregister(&sakway_miscdev);
		printk(DRV_NAME "\trequest gpio %d for Sakway failed, ret = %d\n", Sakway->hw->io_in[i], ret);
		return ret;
	} else {
		printk(DRV_NAME "\trequest gpio %d for  Sakway set succussful, ret = %d\n", Sakway->hw->io_in[i], ret);
	}
	gpio_direction_output(Sakway->hw->io_out, 0);	// 配置为输出
		
	printk(DRV_NAME "\tInit mutex...\n");
    mutex_init(&(Sakway->lock));					// 初始化锁
	printk(DRV_NAME "\tInit module...\n");
	
}
module_init(sakway_driver_init);

static void __exit sakway_driver_exit(void)
{
	int i;
	
	printk(DRV_NAME "\tRelease GPIOs.\n");
	for (i = 0; i < ARRAY_SIZE(Sakway->hw->io_in); i ++) 
		gpio_free(Sakway->hw->io_in[i]);		// 释放gpio
	gpio_free(Sakway->hw->io_out);
	printk(DRV_NAME "\tRelease Driver nod.\n");
	misc_deregister(&sakway_miscdev);
	printk(DRV_NAME "\tRelease the Sakway space.\n");
	__sakway_remove(Sakway);
	kfree(Sakway);
}
module_exit(sakway_driver_exit);

MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");

五. 编译驱动并加载到开发板上

  • 直接放入Halley2的内核源码中
  • 例在kernel/drivers/misc/下新建一个文件夹sakway,将驱动源文件、头文件放入该文件夹中,并在当前目录创建Makefile文件,写入以下信息并保存:
obj-y += sakway.o
  • 再在上层目录下的Makefile文件中添加以下一行信息并保存:
obj-y += sakway/
  • 重新编译内核并烧写进开发板即可,上电运行后即可在系统中的/dev下看到Sakway设备。

  • 在外单独编译该驱动,先确保主机上有相关编译工具链(这里是mips-linux-gnu-)和halley2的kernel源码,新建并打开文件夹下的Makefile文件,并记得更改kernel路径成自己主机上Halley2的kernel的路径

  • obj-m += sakway.o
    
    CC=mips-linux-gnu-gcc
    MODULE_NAME =sakway
    AR=mips-linux-gnu-ar
    DEBFLAGS = -O2
    obj-m := $(MODULE_NAME).o
    
    # 该kernel目录文件夹路径,得更改成与主机上的halley2的kernel目录
    KERNELDIR ?= /path/to/kernel/	
    PWD := $(shell pwd)
    modules:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
    clean:
    	rm $(MODULE_NAME).mod.* $(MODULE_NAME).ko $(MODULE_NAME).o  Module.symvers modules.order .$(MODULE_NAME).*
    
  • 在主机上使用make编译该驱动,不出意外的话会在当前文件夹下生成sakway.ko文件

  • 然后在将该ko文件放入开发板的系统里,使用insmod 来加载该ko文件。

  • 加载成功后,会在/dev/出现Sakway设备。

六. 使用小demo

#include "sakway.h"
int main(int argc, char **argv){
    int fd;
    unsigned char buf;
    struct sakway_msg_t msg = {
        SAKWAY_I2C_MSG_READ,	// 配置为读
        0x00,					// 片上地址0x00
        0,0,					// wmsg为0,index为0
        &buf					// 写
    };
    fd = open("/dev/Sakway", O_RDWR);
    if(fd < 0){
        printf("open error ... \n");
        exit(0);
    }
    ioctl(fd, SAKWAY_CMD_DRV_TEMP, &msg);	// 读取温度传感器
    printf("temp read : %02x \n", buf);		// 打印获取的值
    
    ioctl(fd, SAKWAY_CMD_DRV_MCUG, &msg);	// 读取MCUG
    printf("MCUG read : %02x \n", buf);		// 打印获取的值
    
    ioctl(fd, SAKWAY_CMD_DRV_MCUF, &msg);	// 读取MCUF
    printf("MCUF read : %02x \n", buf);		// 打印获取的值
    
    msg.index = 3;							// 获取PC25的状态
    ioctl(fd, SAKWAY_CMD_GPIO_GET, &msg);
    printf("PC25 read: %02x \n", buf);
    
    msg.wmsg = 1;
    ioctl(fd, SAKWAY_CMD_GPIO_SET, &msg);	// 设置PD01为高电平
    close(fd);
    return 0;
}

参考链接

参考文章

你可能感兴趣的:(Linux驱动)