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;
}