IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,,

Linux主机驱动和外设驱动分离思想

外设驱动→API→主机驱动→板级逻辑--具体的i2c设备(camera,ts,eeprom等等)

  • 主机驱动:根据控制器硬件手册,配置SOC的I2C寄存器产生波形,这个不在我的研究范围之内

  • linux应用工程师不需要驱动和硬件的细节.
    linux驱动工程师:不需要考虑硬件!由BSP工程师提供标准的主机驱动,驱动工程师只需要完成“外设驱动”
    内核函数接口:(API)。主机驱动提供给外设驱动的函数接口。

Tip : 主机驱动 由 BSP工程师去完成就行了 ,我们不需要管 ,不需要亲自去设置I2C的寄存器去产生波形,我们要做的就是使用内核中提供的I2C函数去配置声卡这些外部设备就OK!

外设驱动与板级裸机

  • 外设驱动:针对具体的外部器件的代码。
    例如,摄像头以及声卡中i2c用来配置外部设备(声卡和摄像头)→地址和配置的内容都不一样!就是说,不同的 外部设备对应着不同的板级驱动,就是说,它指的是设备相关的代码,通常建立在内核提供的函数的基础上,这些函数由主机驱动实现毋庸赘言!

  • 板级逻辑:描述主机和外部设备是怎么连接的,描述设备与SOC是如何连接的部分代码(使用那组IIC等信息).

所以说 最主要的东西就是,..... 就是 ... 就是 ... 就是 ..... 如何使用内核提供的接口来熟练操作IIC

对于3.0版本的KERNEL来说,I2C涉及到的API函数

注册i2c设备:i2c_board_info
驱动注册函数和结构体:i2c_del_driver/i2c_add_driver,i2c_driver(描述I2C驱动的结构体)
读写函数和结构体:i2c_transfer(传输数据的函数,收/发),i2c_msg(放置传输数据的结构体)


设备-i2c设备注册以及设备注册之后的查询方法

查询i2c设备地址:ls /sys/bus/i2c/devices/

​ 怎么和原理图以及外部设备对应:3-0038→I2C_3_SCL(addr:datasheet中查0x38,注意Linux内核中使用的 I2C地址是7位地址,即前7位,(不包括读写标志位)而datasheet中一般给出的是8位的I2C地址,包括读写标志位)
查询i2c设备名称:cat /sys/bus/i2c/devices/3-0038/name

[root@iTOP-4412]# ls sys/bus/i2c/devices/
0-003a  1-0066  3-005d  5-0068  7-0048  i2c-1   i2c-4   i2c-7
1-0006  3-0038  5-0018  7-0038  i2c-0   i2c-3   i2c-5

3-0038 3表示第三组IIC ,, 0038表示该设备的前7位地址为0038


********************************华丽的分割线************************

实验 ,通过I2C总线读取触摸屏的X,Y轴坐标

硬件资源

讯为ITOP4412 SCP开发板

4.3寸LCD屏,主控TSC2007

软件版本

内核版本: KERNEL3.0

编译器版本: gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67)

1. 在make menuconfig中取消已有的驱动程序,以便于我们编写自己的

menuconfig中去掉触摸主控TSC2007的驱动

│ Symbol: TOUCHSCREEN_TSC2007 [=n]                                                                                                     │  
  │ Type  : tristate                                                                                                                     │  
  │ Prompt: TSC2007 based touchscreens                                                                                                   │  
  │   Defined at drivers/input/touchscreen/Kconfig:692                                                                                   │  
  │   Depends on: !S390 && INPUT [=y] && INPUT_TOUCHSCREEN [=y] && I2C [=y]                                                              │  
  │   Location:                                                                                                                          │  
  │     -> Device Drivers                                                                                                                │  
  │       -> Input device support                                                                                                        │  
  │         -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])                                                        │  
  │           -> Touchscreens (INPUT_TOUCHSCREEN [=y])   
2. 添加设备,平台文件的方式,注册我们的I2C设备

添加i2c设备:i2c_devs7[]中添加

/* I2C7 */
static struct i2c_board_info i2c_devs7[] __initdata = {

/*********略*****/
		//这个已经取消配置勾选了,不会生效
        /* add by cym 20130417 for TSC2007 TouchScreen */
#ifdef CONFIG_TOUCHSCREEN_TSC2007
        {
                I2C_BOARD_INFO("tsc2007", 0x48),
                .type           = "tsc2007",
                .platform_data  = &tsc2007_info,
                .irq = IRQ_EINT(0),
         },
#endif
		//添加我们自定义的平台设备信息
        //CONFIG_TOUCHSCREEN_TSC2007,然后添加下面这个
		//如果地址冲突,那么是无法注册进去的
        {
                I2C_BOARD_INFO("tsc2007", 0x48),
         },
        /* end add */
#endif

/*********略*****/

};

编译=>烧写内核,然后 查看设备信息是否生效

cat /sys/bus/i2c/devices/7-0048/name结果是tsc2007

3.驱动-i2c驱动注册和卸载。i2c设备驱动初始化完成-进入probe函数。
  • 用到的内核API : i2c_del_driver/i2c_add_driver,i2c_driver
  • module_init和late_initcall的区别:module_init先运行,late_initcall后运行
static const struct i2c_device_id i2c_test_id[] = {
//第一个参数,设备名,和.name一样,第二个参数是自定义硬件版本,用的很少
//I2C提供了一种机制,用于区分不同的硬件版本,很少用的 
//像下面这样,不用管那个参数,写0就行了
	{ I2C_DEVICE_NAME, 0 },
	{ }
};

static struct i2c_driver i2c_test_driver = {
	.probe		= i2c_test_probe,
	.remove		= __devexit_p(i2c_test_remove),
	//用于区分不同的硬件版本,这个用到很少,
	.id_table	= i2c_test_id,
	.driver	= {
		.name	= I2C_DEVICE_NAME,
		.owner	= THIS_MODULE,
	},
};

大多数情况下IIC都是在内核中完成工作的,向触摸屏的话通过输入子系统与用户空间进行交互,向声卡的话,一般通过中断触发IIC进行调整音量等操作

4.驱动-i2c数据的传输
  • 用到的内核API: i2c_transfer,i2c_msg
	struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
	#define I2C_M_RD		0x0001	/* read data, from slave to master */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

要完成i2c的读,必须要先写再读!写的时候,你要通知从机,你要读哪个寄存器!,I2C协议的,,,基本原则

5.Liux-i2c利用杂项驱动完成应用层对i2c的读和写

直接看后面源码就好了...


内核中关于i2c_msg的说明

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 * @addr: Slave address, either seven or ten bits.  When this is a ten
 *	bit address, I2C_M_TEN must be set in @flags and the adapter
 *	must support I2C_FUNC_10BIT_ADDR.
 * @flags: I2C_M_RD is handled by all adapters.  No other flags may be
 *	provided unless the adapter exported the relevant I2C_FUNC_*
 *	flags through i2c_check_functionality().
 * @len: Number of data bytes in @buf being read from or written to the
 *	I2C slave address.  For read transactions where I2C_M_RECV_LEN
 *	is set, the caller guarantees that this buffer can hold up to
 *	32 bytes in addition to the initial length byte sent by the
 *	slave (plus, if used, the SMBus PEC); and this value will be
 *	incremented by the number of block data bytes received.
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they
 * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
 */
struct i2c_msg {
	__u16 addr;	/* slave address	,在平台文件中已经注册了,直接使用平台文件的注册信息就行		*/
	__u16 flags;  //标志位   下面这些宏 都是标志 
	//用于选择使用8位地址还是10位地址,不选择 就是8位地址
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
	//表示读
#define I2C_M_RD		0x0001	/* read data, from slave to master */
	//其他标志位 以下   用的少 
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
//长度 ,I2C的传输的数据长度,给i2c_transfer函数使用
	__u16 len;		/* msg length				*/
//传输数据用的buf  ,,  将要传输的值赋给buf就ok
	__u8 *buf;		/* pointer to msg data			*/
};

******************五彩缤纷的分割线*******************

外设驱动_TSC2007

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/regulator/consumer.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ft5x0x_touch.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>

#define I2C_DEVICE_NAME "tsc2007"

//便于传递probe中获得的client结构体
static struct i2c_client * this_client;

static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int __devexit i2c_test_remove(struct i2c_client *client);
static int __init i2c_test_init(void);
static void __exit i2c_test_exit(void);
static struct i2c_driver i2c_test_driver;

/*
TSC2007的操作步骤
发送地址,写的方式
发送一条设置命令
停止
发送地址,读的方式
读取一个数据 
停止
*/
//测试 读 不是我们设置的val的默认值 那么 就是 Okay的了 应该 

static int i2c_tes_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) {
	u8 buf1[4] = { 0 };
	u8 buf2[4] = { 0 };//读的时候,读到的值存储在这里
	//这个结构体中包含一组读的时候用的,和一组写的时候用的
	//这个结构体,读的时候需要两组(先写地址后读,so需要两组),写的时候只需要一组
	struct i2c_msg msgs[] = {
		{
			//可以打印出来 看看 ,这个是设备的地址,从设备注册的平台信息中获得的
			.addr	= client->addr,	//0x38
			.flags	= 0,	//写  0是写标志 
			//长度是1 ,就是说读/写 一次 ,8位数据 ,
			.len	= 1,	//要写的数据的长度
			.buf	= buf1,
		},
		{
			.addr	= client->addr,//设备地址 7位的应该是 
			.flags	= I2C_M_RD, //R/W标志位
			.len	= 1,// 读取的长度 ,,1个字节 (8位一般)
			.buf	= buf2,//读取到的值存储到这个buf中
		},
	};
	int ret;
	//这个addr的参数,不是设备地址 ,设备地址是这个client->addr, 
	//这个addr就是要写入的数据
	buf1[0] = addr;//要写的内容 先写I2C内部寄存器的地址,告诉设备我们要读哪个寄存器
	//buf1[1]的话 ,那就是要写的数据了 ,
	//i2c_transfer数据传输函数 
	//client->adapter从匹配的平台设备信息中读取出使用的那组I2C接口
	//msgs
	//先写入I2C设备内部寄存器地址(buf1),然后读取数据到buf2
	//最后一个参数 ,表示msg这样的结构体数组,有几个成员
	//就是说msg这样的结构体有几个 ,我们设置了两个!  
	//
	ret = i2c_transfer(client->adapter, msgs, 2);
	if (ret < 0) {
		pr_err("read reg (0x%02x) error, %d\n", addr, ret);
	} else {
		*pdata = buf2[0];
	}
	return ret;
}

static int i2c_tes_read_fw_reg(struct i2c_client *client,unsigned char *val)
{
	int ret;
	//设定一个默认值,如果出错,那么val为默认值0xff
	*val = 0xff;
	ret = i2c_tes_read_reg(client,0xcf, val);
	return ret;
}

int tsc2007_open(struct inode * inode_point, struct file * file_ponit){
	printk("%s\n",__FUNCTION__);
	return 0;
}

int tsc2007_release(struct inode * inode_point, struct file * file_ponit){
	printk("%s\n",__FUNCTION__);
	return 0;
}

//从buf传入一个数据进去(指定读取I2C设备内部的内容/内部寄存器地址)
//然后根据buf传入的这个数据,读取I2C设备内部相应的位置,将数据读取出来,再存入buf
ssize_t tsc2007_read(struct file * filp_ponit, char __user * buf, size_t size, loff_t * loff){
	int retval;
	u8 reg_data;
	
	//通过this_client这个全局变量,将probe中得到的client传递给read函数
	//这个结构体中包含一组读的时候用的,和一组写的时候用的
	//这个结构体,读的时候需要两组(先写地址后读,so需要两组),写的时候只需要一组
	struct i2c_msg msgs[] = {
		{
			//可以打印出来 看看 ,这个是设备的地址,从设备注册的平台信息中获得的
			.addr	= this_client->addr,	//0x38
			.flags	= 0,	//写  0是写标志 
			//长度是1 ,就是说读/写 一次 ,8位数据 ,
			.len	= 1,	//要写的数据的长度
			.buf	= &reg_data,
		},
		{
			.addr	= this_client->addr,//设备地址 7位的应该是 
			.flags	= I2C_M_RD, //R/W标志位
			.len	= 1,// 读取的长度 ,,1个字节 (8位一般)
			.buf	= &reg_data,//读取到的值存储到这个buf中
		},
	};
	
	retval = copy_from_user(&reg_data,buf,1);
		if(retval<0)
			return -EFAULT;
	//msgs
	//先写入I2C设备内部寄存器地址(buf1),然后读取数据到buf2
	//最后一个参数 ,表示msg这样的结构体数组,有几个成员
	//就是说msg这样的结构体有几个 ,我们设置了两个!  
	//
	retval = i2c_transfer(this_client->adapter, msgs, 2);
	if (retval < 0) {
		pr_err("read retval (0x%02x) error, %d\n", reg_data, retval);
		return retval;
	} 
	retval = copy_to_user(buf,&reg_data,1);
	return retval;
}

/*读的流程*/
//写I2C设备地址,写设备内部寄存器的地址,读该位置的数据
//除了写设备地址,要写一次(位置),读一次(数据)
/*写的流程*/
//写I2C设备地址,写设备内部寄存器的地址,向该位置写数据
//除了写设备地址,要写一次(位置),再写一次(数据)
//所以写函数,要写入两次数据
ssize_t tsc2007_write(struct file * filp_ponit, const char __user * buf, size_t size, loff_t * loff){
	int retval;
	u8 buffer[2];
	//从用户控件拷贝来两个数据,一次,
	//第一个数据用来指定写入I2C设备的内部地址
	//第二个数据用来指定向第一个参数指定的地址写入的内容

	//结构体的另一种赋值方法
	struct i2c_msg msgs[1];
		msgs[0].addr	= this_client->addr;//设备地址 7位的应该是 
		msgs[0].flags	= 0;  				//写标志
		msgs[0].len	= 2;					// 要写入的数据的长度
		msgs[0].buf = buffer;
	
	retval = copy_from_user(buffer,buffer,2);
		if(retval<0)
			return -EFAULT;
	
	//1个 msgs 第三个参数的说明
	//transfer应该会按照msgs结构体的内容去按顺序传输数据
	retval = i2c_transfer(this_client->adapter, msgs, 1);
	if (retval < 0) {
		pr_err("read retval (0x%02x) error, %d\n", buffer[0], retval);
	} 

	return retval;
}

static struct file_operations tsc2007_ops = {
	.owner 	= THIS_MODULE,
	.open 	= tsc2007_open,
	.release= tsc2007_release,
	.write  = tsc2007_write, 
	.read   = tsc2007_read,
};

static struct miscdevice tsc2007_dev = {
	.minor	= MISC_DYNAMIC_MINOR,
	.fops	= &tsc2007_ops,
	.name	= "i2c_tsc2007",
};


static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	unsigned char val;
	printk("==%s:\n", __FUNCTION__);
	
	i2c_tes_read_fw_reg(client,&val);
	
	this_client = client ;
	
	misc_register(&tsc2007_dev);
	
	return 0;
}

static int __devexit i2c_test_remove(struct i2c_client *client)
{
	//移除设备的时候,将client清空(赋值NULL),移除的干净
	//clear i2c data ... it will be NULL.
	i2c_set_clientdata(client, NULL);
	printk("==%s:\n", __FUNCTION__);
	return 0;
}

static const struct i2c_device_id i2c_test_id[] = {
	{ I2C_DEVICE_NAME, 0 },
	{ }
};


static int __init i2c_test_init(void)
{
	//print fuction name
	printk(KERN_EMERG "==%s\n",__FUNCTION__);
	//register I2C driver
	return i2c_add_driver(&i2c_test_driver);
}
static void __exit i2c_test_exit(void)
{
	printk("==%s:\n", __FUNCTION__);
	//unload i2c driver 
	i2c_del_driver(&i2c_test_driver);
}

static struct i2c_driver i2c_test_driver = {
	.probe		= i2c_test_probe,
	.remove		= __devexit_p(i2c_test_remove),
	.id_table	= i2c_test_id,
	.driver	= {
		.name	= I2C_DEVICE_NAME,
		.owner	= THIS_MODULE,
	},
};

//load module ,in the end.
late_initcall(i2c_test_init);
module_exit(i2c_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("rather_dog");
MODULE_DESCRIPTION("I2C_TEST");


应用程序

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

//表示要读取的x轴还是y轴的数据
#define TSC2007_X_POSITION 0xcf
#define TSC2007_Y_POSITION 0xdf

/*
	读取触摸屏的x和y坐标,并打印出来,不停的循环打印
*/
int main(int argc,char **argv){
	int fd,retval;
	unsigned int x_val,y_val;
	//字符串指针常量,不需要申请空间
	//驱动的节点
	const char *i2c_device = "/dev/i2c_tsc2007";
	unsigned char buffer[1];
	
	printf("open %s!\n",i2c_device);
	if((fd = open(i2c_device,O_RDWR|O_NDELAY))<0)
		printf("APP open %s failed",i2c_device);
	else{
		printf("APP open %s success!\n",i2c_device);
	}
	
	while(1){
		buffer[0] = TSC2007_X_POSITION;
		read(fd,buffer,1);
		x_val = buffer[0];
		buffer[0] = TSC2007_Y_POSITION;
		read(fd,buffer,1);
		y_val = buffer[0];
		printf("x= %d\ty= %d\n",x_val,y_val);
		sleep(1);
	}
	
	close(fd);
	return 0;
}

posted on 2020-04-13 14:49  freedo  阅读(1551)  评论(0编辑  收藏  举报

导航