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