I2C驱动-----FM24C02A


前言

I2C核心:主要为总线驱动和设备驱动提供各种API,比如设备探测、注册、注销,设备和驱动匹配等函数。它在I2C架构中处于中间的位置。

I2C总线驱动:I2C总线驱动维护了I2C适配器数据结构(i2c_adapter)和适配器的通信方法数据结构(i2c_algorithm)。所以I2C总线驱动可控制I2C适配器产生start、stop、ACK等。I2C总线驱动在整个架构中是处于最底层的位置,它直接和真实的物理设备相连,同时它也是受CPU控制。

I2C设备驱动:I2C设备驱动主要负责和用户层交互,此处的设备是一个抽象的概念,并非真实的物理设备,它是挂在I2C适配器上,通过I2C适配器与真实的物理设备通信。
在这里插入图片描述
实际上,Linux经过这么多年的发展,已经形成了一套完善的I2C驱动框架,现在编写I2C驱动,我们只需要完成上面所说的I2C设备驱动部分就可以,其他的芯片厂商已经为我们做好了。

根据bus-dev-drv框架模型,我们的主要工作是实现设备文件和驱动文件,也就是上图中的i2c_client和i2c_driver,i2c_client作用是完成设备和适配器的绑定 ,以确定设备驱动需要和哪个适配器下面的真实物理设备通信,i2c_driver的作用就是实现用户层的open、write、read等调用。

Linux编写I2C驱动程序的一般流程为

1、创建i2c_client,并向内核注册这个client,注册的方法有图一中提到的4种方法。
2、创建i2c_driver,并向内核注册driver,一般使用i2c_add_driver注册driver。
3、注册driver时,总线会自动调用match函数,匹配client和driver,如果匹配成功,会调用driver里面的probe函数。
4、probe函数里面完成字符设备驱动那些工作,一个I2C驱动程序基本完成。

一、设备注册 i2c_register_board_info

这里会使用一个重要的结构体i2c_board_info

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	void		*platform_data;
	struct dev_archdata	*archdata;
#ifdef CONFIG_OF
	struct device_node *of_node;
#endif
	int		irq;
};

addr:丛机设备地址右移一位
例如:
static struct i2c_board_info __initdata mxs_i2c_device[] = {
	{ I2C_BOARD_INFO("pcf8563", 0x51)  },
	{ I2C_BOARD_INFO("FM24C02A", 0x50)  },
	{ I2C_BOARD_INFO("DS2460", 0x40)  },	
};

它的作用是描述物理设备信息,主要是name和addr,但内核不会探测这个addr的真实性,这个结构体是你已知真实物理设备的从机地址的情况下,可以直接指定设备信息,然后调用i2c_register_board_info注册设备

int __init i2c_register_board_info(int busnum,
				struct i2c_board_info const *info, 
						 unsigned len)
busnum :使用的是那个i2c总线
例如:
i2c_register_board_info(1, mxs_i2c_device, ARRAY_SIZE(mxs_i2c_device));

device.c

static struct i2c_board_info __initdata mxs_i2c_device[] = {
	//{ I2C_BOARD_INFO("sgtl5000-i2c", 0xa), .flags = I2C_M_TEN }
	//{ I2C_BOARD_INFO("rtc-pcf8563", 0x51)  }
	{ I2C_BOARD_INFO("pcf8563", 0x51)  },
	{ I2C_BOARD_INFO("FM24C02A", 0x50)  },
	{ I2C_BOARD_INFO("DS2460", 0x40)  },
	
};


static void __init i2c_device_init(void)
{
	//i2c_register_board_info(0, mxs_i2c_device, ARRAY_SIZE(mxs_i2c_device));
	i2c_register_board_info(1, mxs_i2c_device, ARRAY_SIZE(mxs_i2c_device));

}

二、驱动注册

驱动实际就是字符设备驱动那一套流程,open、read、write等文件接口以及设备号申请、自动创建设备节点等操作。

只不过设备号申请、自动创建设备节点等操作需要放到probe函数里面去,注销操作则需要放到remove函数里面去。

read和write调用的实现:

下面是标准I2C协议的通信函数:
int i2c_master_send(struct i2c_client *client,const char *buf ,int count)  
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)  
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 

1、添加驱动 i2c_add_driver

在模块初始化函数中调用 i2c_add_driver

static inline int i2c_add_driver(struct i2c_driver *driver)
{
	return i2c_register_driver(THIS_MODULE, driver);
}

非常重要的结构体i2c_driver :


struct i2c_driver {
	unsigned int class;
 
	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this if you can, it will probably
	 * be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);
 
	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);
 
	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);
 
	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);
 
	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
 
	struct device_driver driver;
	const struct i2c_device_id *id_table;
 
	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

其中,成员const struct i2c_device_id *id_table记录了驱动的名字和私有数据,驱动的名字必须和设备的名字一致,否则内核会匹配失败。


struct i2c_device_id {
	char name[I2C_NAME_SIZE];
	kernel_ulong_t driver_data	/* Data private to the driver */
			__attribute__((aligned(sizeof(kernel_ulong_t))));
};

i2c_driver 结构体填充如下:

static const struct i2c_device_id FM24C02A_id[] = 
{
	{ "FM24C02A", 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, FM24C02A_id); /* 进一步初始化 FM24C02A_id */

static struct i2c_driver FM24C02A_driver =
{
	.driver  = {
		.name  = "FM24C02A",
	},
	.probe  = FM24C02A_probe,
	.remove  = FM24C02A_remove,
	.id_table = FM24C02A_id,//这里的名字才是和设备进行匹配的
};

最后,在probe函数里注册常规字符设备驱动,在remove函数里注销字符设备驱动,i2c_driver工作就基本结束了。

2、删除驱动 i2c_del_driver

在模块卸载函数里调用 i2c_del_driver

void i2c_del_driver(struct i2c_driver *driver)
{
	mutex_lock(&core_lock);
	bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_removed_driver);
	mutex_unlock(&core_lock);

	driver_unregister(&driver->driver);
	pr_debug("i2c-core: driver [%s] unregistered\n", driver->driver.name);
}

drivice.c

#include<linux/miscdevice.h>   
#include<linux/init.h>
#include<linux/module.h>
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
#include <linux/slab.h>

#define DEVICE_NAME "FM24C02A"
#define CMD_SET_ROM_ADDR 0x1

static char rom_addr; /* 用于保存 FM24C02A 内部储存器的读/写地址 */
static struct i2c_client *g_client;

static const struct i2c_device_id FM24C02A_id[] = 
{
	{ "FM24C02A", 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, FM24C02A_id); /* 进一步初始化 FM24C02A_id */




static ssize_t FM24C02A_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	int ret = 0,i;
	char data_buf[256];
	
	ret = i2c_master_send(g_client, &rom_addr, 1);  /* 向 FM24C02A 发送要读取数据的起始地址*/
	printk("FM24C02A_read 1=%d\r\n",ret);
	ret = i2c_master_recv(g_client, data_buf, count);  /* 在 FM24C02A 连续读取数据 */
	printk("FM24C02A_read 2=%d\r\n",ret);
	for(i=0;i<ret;i++)
	{
		printk("%d ", data_buf[i]);
	}
	printk("read count=%d\r\n",count);
	
	copy_to_user(buf, data_buf, count);  /* 把读取的数据返回给用户空间  */
	
	return ret;
}

static ssize_t FM24C02A_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	int ret = 0,i;
	char data_buf[256 + 1];
	data_buf[0] = rom_addr;  /* 写入的起始地址  */
	
	copy_from_user(data_buf + 1, buf, count);  /* 在用户空间复制要写入的数据  */
	
	for(i=0;i<(count+1);i++)
	{
		printk("%d ", data_buf[i]);
	}
	printk("write count=%d\r\n",count);
	
	ret = i2c_master_send(g_client, data_buf, count+1);  /* 把要写入的数据发送到 FM24C02A*/
	printk("FM24C02A_write=%d\r\n",ret);
	return ret;
}
static int FM24C02A_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg)
{
	printk("command=%d, arg=%ld\r\n", command, arg);
	if (command == CMD_SET_ROM_ADDR)
	{
		rom_addr = arg; /* FM24C02A内部储存器的读/写地址在arg参数中传入*/
	}
	
	return 0;
}


static struct file_operations FM24C02A_fops={
	.owner = THIS_MODULE,
	.write = FM24C02A_write,
	.read = FM24C02A_read,
	.ioctl = FM24C02A_ioctl,
};


static struct miscdevice FM24C02A_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &FM24C02A_fops,
};


static int FM24C02A_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int err = 0;
	
	printk("FM24C02A device is detected \n");
	
	g_client = client;
	
	err = misc_register(&FM24C02A_miscdev); /* 生成字符设备  */
	if (err) 
	{
		printk("register FM24C02A device faile \n");
		return -1;
	}
	
	return 0;
}

static int  FM24C02A_remove(struct i2c_client *client)
{
	misc_deregister(&FM24C02A_miscdev);
	return 0;
}

static struct i2c_driver FM24C02A_driver =
{
	.driver  = {
		.name  = "FM24C02A",
	},
	.probe  = FM24C02A_probe,
	.remove  = FM24C02A_remove,
	.id_table = FM24C02A_id,
};

static int __init FM24C02A_init(void)
{
	printk("FM24C02A_init\r\n");
	return i2c_add_driver(&FM24C02A_driver);
}

static void __exit FM24C02A_exit(void)
{
	i2c_del_driver(&FM24C02A_driver);
}

module_init(FM24C02A_init);
module_exit(FM24C02A_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("shenrt, ZhiYuan Electronics Co, Ltd.");
MODULE_DESCRIPTION("GPIO DRIVER FOR EasyARM-i.MX28xx");

最后编写测试函数 :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>


#define I2C_DEV_NAME "/dev/FM24C02A"
#define CMD_SET_ROM_ADDR 0X1
#define DATA_LEN 16


int main(int arg,char*args[])
{

	unsigned int len;
	int i,j;
	int fd;
	char tx_buf[DATA_LEN];
	char rx_buf[DATA_LEN];

	memset(tx_buf, 0, sizeof(tx_buf));
	memset(rx_buf, 0, sizeof(rx_buf));
	
	fd = open(I2C_DEV_NAME, O_RDWR); /* 打开 FM24C02A 驱动的设备文件*/
	if(fd < 0) 
	{
		printf("open %s failed\r\n", I2C_DEV_NAME);
		return -1;
	}
	
	/* 设置 DS2460 内存储存器的读/写地址为 0 */
	//ioctl(fd, CMD_SET_ROM_ADDR, 0x0); 
	for (i = 0; i < DATA_LEN; i++)
		tx_buf[i] = i+1;


	j=0;
	ioctl(fd, CMD_SET_ROM_ADDR, 8); 
	len = write(fd, tx_buf, DATA_LEN/2); /* 把数据写入到FM24C02A内存储存器*/
	if (len < 0) 
	{
		printf("write data faile \n");
		return -1;
	}
	
		
	usleep(1000*100);
	
	
	ioctl(fd, CMD_SET_ROM_ADDR, 8); 
	len = read(fd, rx_buf, DATA_LEN/2); /* 把从FM24C02A内存储存器读出数据*/
	if (len < 0)
	{
		printf("read data fail \n");
		return -1;
	}
	
	for(i = 0; i < DATA_LEN; i++)
	{
		printf("%d ", rx_buf[i]);
	}

	close(fd);
	return 0;
	
test_faile:
	printf("test eeprom faile \n");
	close(fd);
	return -1;


}

将设备添加到内核进去重新编译并烧写到开发板后,insmod驱动,再运行app 效果如下:
在这里插入图片描述

总结

在linux系统下编写I2C驱动,目前主要有两种方法
一种是把I2C设备当作一个普通的字符设备来处理(直接在用户层open(“i2c-0”)进行write和read),
另一种是利用linux下I2C驱动体系结构来完成(上面的实验)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值