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 = ®_data,
},
{
.addr = this_client->addr,//設備地址 7位的應該是
.flags = I2C_M_RD, //R/W標誌位
.len = 1,// 讀取的長度 ,,1個位元組 (8位一般)
.buf = ®_data,//讀取到的值存儲到這個buf中
},
};
retval = copy_from_user(®_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,®_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;
}