裝置樹中的i2c
i2c節點一般表示i2c控制器, 它會被轉換為platform_device, 全志H3的匯流排裝置驅動位於busses/i2c-mv64xxx.c ,該檔案有對應的platform_driver;在匯流排驅動器程式碼中的platform_driver的probe函式中會呼叫i2c_add_numbered_adapter,來增加一個i2c介面卡。
i2c_adap_s3c_init s3c24xx_i2c_probe i2c_add_numbered_adapter // drivers/i2c/i2c-core-base.c __i2c_add_numbered_adapter i2c_register_adapter of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c i2c板級資訊在裝置樹中描述 for_each_available_child_of_node(bus, node) { //在i2c節點下遍歷所有子節點 client = of_i2c_register_device(adap, node); client = i2c_new_device(adap, &info); // 裝置樹中的i2c子節點被轉換為i2c_client
由呼叫流程可以得知,i2c節點在of_i2c_register_devices函式中遍歷其下的子節點,轉換成一個個i2c_client。由此可以得出結論在i2c匯流排驅動程式中,才會處理I2C的裝置樹節點,找到處理程式碼, 通過i2c_new_device(adap, &info)建立i2c裝置。
void of_i2c_register_devices(struct i2c_adapter *adap) { struct device_node *bus, *node; struct i2c_client *client; /* Only register child devices if the adapter has a node pointer set */ if (!adap->dev.of_node) return; dev_dbg(&adap->dev, "of_i2c: walking child nodes\n"); bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus"); if (!bus) bus = of_node_get(adap->dev.of_node); for_each_available_child_of_node(bus, node) { if (of_node_test_and_set_flag(node, OF_POPULATED)) continue; client = of_i2c_register_device(adap, node); if (IS_ERR(client)) { dev_err(&adap->dev, "Failed to create I2C device for %pOF\n", node); of_node_clear_flag(node, OF_POPULATED); } } of_node_put(bus); }
由以上分析可以初步得出裝置樹是如何處理解析i2c節點。在本文我們通過一個例項來實現一個i2c裝置驅動。在nanopi上掛接一個光線感測器GY30的i2c裝置於i2c介面卡0上。由資料手冊得出,當光線感測器的add腳接低電平是i2c地址為0x23,為高時地址為0x5c。
SDA ======>PA12 資料線
SCL ======>PA11 時鐘線
- i2c-tools使用
進行I2C相關程式開發時,很多時候我們需要確認硬體是否正常連線,裝置是否正常工作,裝置的地址是多少等等,這裡我們就需要使用一個用於測試I2C匯流排的工具——i2c tools,
i2c-tools編譯,安裝步驟
wget https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/i2c-tools-4.0.tar.gz //下載i2c-tools-4.0
tar -xzvf i2c-tools-4.0.tar.gz //解壓
Makefile中的CC該為使用的交叉編譯器
Make
編譯好後,掛載NFS,出現錯誤,提示缺少共享庫
[email protected]:/mnt/i2c-tools-4.0/tools# ./i2cget
./i2cget: error while loading shared libraries: libi2c.so.0: cannot open shared object file: No such file or directory
將i2c-tools-4.0/lib/的共享庫 拷貝到開發板 lib 目錄下
cp -rfd *.so.* /lib
然後使用i2cdetect檢測,出現了4個i2c介面卡
[email protected]:/mnt/i2c-tools-4.0/tools# ./i2cdetect -l
i2c-3 i2c DesignWare HDMI I2C adapter
i2c-1 i2c mv64xxx_i2c adapter I2C adapter
i2c-2 i2c mv64xxx_i2c adapter I2C adapter
i2c-0 i2c mv64xxx_i2c adapter
現在在介面卡0上接了光線感測器,使用i2cdetect -y指定介面卡編號就可以檢測到該i2c裝置的地址,由命令也可以得出裝置地址為0x23
[email protected]:~# i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
- 感測器操作流程
普遍採用的工作方式為 Continuously H-Resolution Mode,即連續高解析度模式。通過i2c介面設定若干命令就可以從gy30感測器得到光照強度資訊。
1 初始化 寫暫存器0x01 上電
2 設定 0x11 設定成高精度模式 Continuously H-Resolution Mode,即連續高解析度模式。
3 讀資料 unsigned char Buf[3] data=Buf[0]; data=(data<<8)+Buf[1] tmp=data/1.2
- 使用使用者空間直接訪問i2c介面的光線感測器
在核心文件Documentation/i2c/instantiating-devices中給出了編寫i2c裝置驅動程式的方法。
Method 1:
Method 1a: Declare the I2C devices by bus number
Method 1b: Declare the I2C devices via devicetree
Method 1c: Declare the I2C devices via ACPI
Method 2: Instantiate the devices explicitly
Method 3: Probe an I2C bus for certain devices
Method 4: Instantiate from user-space
其中方法1使用最普遍,1a這種方法在以前的文章中闡述過,也就是把裝置平臺資訊放在i2c_board_info,然後通過i2c_register_board_info。1b方法大同小異,只是把i2c裝置資訊放在i2c裝置節點的子節點中,然後在驅動載入時去解析。這裡我選擇方法4通過使用者層建立一個i2c裝置。
直接通過使用者空間就可以操作裝置。在核心配置中需要使能I2C device interface
程式碼很簡單,首先開啟裝置檔案然後設定i2c裝置的地址,然後按照gy30資料手冊中操作流程,連續寫0x01和0x11,就可以讀出光照資訊
#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#define I2C_ADDR 0x23
int main(void)
{
int fd;
char buf[3];
char val,value;
float flight;
fd=open("/dev/i2c-0",O_RDWR);
if(fd<0)
{
printf("開啟檔案錯誤:%s\r\n",strerror(errno)); return 1;
}
if(ioctl( fd,I2C_SLAVE,I2C_ADDR)<0 )
{
printf("ioctl 錯誤 : %s\r\n",strerror(errno));return 1;
}
val=0x01;
if(write(fd,&val,1)<0)
{
printf("上電失敗\r\n");
}
val=0x11;
if(write(fd,&val,1)<0)
{
printf("開啟高解析度模式2\r\n");
}
usleep(200000);
while(1)
{
if(read(fd,&buf,3)){
flight=(buf[0]*256+buf[1])*0.5/1.2;
printf("光照度: %6.2flx\r\n",flight);
}
else{
printf("讀取錯誤\r\n");
}
sleep(5);
}
}
- 基於裝置樹的驅動程式
在裝置樹檔案sun8i-h3-nanopi-m1.dts引用i2c0節點,並新增一個[email protected]子節點,指定reg屬性(裝置地址)和compatible 屬性值
132 &i2c0 {
133 status = "okay";
134 [email protected]{
135 compatible = "gy,bh1750fvi";
136 reg = <0x23>;
137 };
138 };
編譯裝置樹,sun8i-h3-nanopi-m1.dtb拷貝到啟動卡的第一個分割槽,重啟開發板進入檔案系統,就會增加一個i2c裝置0-0023和這個裝置在裝置樹中相關資源
[email protected]:~# ls /sys/bus/i2c/devices/
0-0023 0-0068 2-003c i2c-0 i2c-1 i2c-2 i2c-3
[email protected]:~# cat /sys/bus/i2c/devices/0-0023/of_node/compatible
gy,[email protected]:~#
給[email protected]編寫驅動程式
- 構造一個platform_driver,其中的of_match_table欄位需要與 [email protected]節點的compatible屬性值一致,當匹配時則呼叫platform_driver的probe函式
static const struct of_device_id ids[]=
{
{.compatible="gy,bh1750fvi"},
{}
};
/* 1. 分配/設定i2c_driver */
static struct i2c_driver gy_sensor_driver = {
.driver = {
.name = "wu",
.owner = THIS_MODULE,
.of_match_table=ids,
},
.probe = gy_sensor_probe,
.remove = gy_sensor_remove,
};
static int gy_sensor_drv_init(void)
{
/* 2. 註冊i2c_driver */
i2c_add_driver(&gy_sensor_driver);
return 0;
}
- 在i2c_driver的probe函式中得到在匯流排驅動程式中解析得到的i2c_client,併為該光線感測器註冊一個字元裝置
static int gy_sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
gy_sensor_client = client;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "gy_sensor", &gy_sensor_fops);
class = class_create(THIS_MODULE, "gy_sensor");
device_create(class, NULL, MKDEV(major, 0), NULL, "gy_sensor"); /* /dev/gy_sensor */
return 0;
}
- 填充字元裝置中的file_operations結構體 ,在驅動的open函式中,初始化光線感測器 ,在驅動的read函式中,讀出感測器的兩個位元組,怎麼轉換成光強值由應用程式來負責。
int gy_sensor_open (struct inode *inode, struct file *file)
{
printk("open gy_sensor\n");
gy_sensor_write_reg(0x01); //power up
gy_sensor_write_reg(0x11);
return 0;
}
static ssize_t gy_sensor_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
unsigned char addr=0, data[2];
gy_sensor_read_reg(data);
copy_to_user(buf, data, 2);
return 1;
}
- 構造i2c_msg通過這個client呼叫i2c_tansfer來讀寫
static int gy_sensor_write_reg(unsigned char addr)
{
int ret = -1;
struct i2c_msg msgs;
printk("gy_sensor_client -> addr=%d\n",gy_sensor_client -> addr);
msgs.addr = gy_sensor_client -> addr;////GY30_ADDR,直接封裝於i2c_msg
msgs.buf = &addr;
msgs.len = 1; //長度1 byte
msgs.flags = 0; //表示寫
ret = i2c_transfer(gy_sensor_client ->adapter, &msgs, 1);//這裡都封裝好了,本來根據i2c協議寫資料需要先寫入器件寫地址,然後才能讀
if (ret < 0)
{
printk("i2c_transfer write err\n");
return -1;
}
return 0;
}
static int gy_sensor_read_reg(unsigned char *buf)
{
int ret = -1;
struct i2c_msg msg;
msg.addr = gy_sensor_client -> addr;//GY30_ADDR
msg.buf = buf;
msg.len = 2; //長度1 byte
msg.flags = I2C_M_RD; //表示讀
ret = i2c_transfer(gy_sensor_client ->adapter, &msg, 1);//這裡都封裝好了,本來根據i2c協議讀資料需要先寫入讀地址,然後才能讀
if (ret < 0)
{
printk("i2c_transfer write err\n");
return -1;
}
return 0;
}
- 在platform_driver的remove函式中,登出該字元裝置
static int gy_sensor_remove(struct i2c_client *client)
{
//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(class, MKDEV(major, 0));
class_destroy(class);
unregister_chrdev(major, "gy_sensor");
return 0;
}
編寫測試程式來讀寫光線感測器的光照強度
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
char val;
unsigned char buf[3];
float flight;
fd = open("/dev/gy_sensor", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/gy_sensor\n");
return -1;
}
usleep(200000);
while(1)
{
if(read(fd,&buf,3)){
flight=(buf[0]*256+buf[1])*0.5/1.2;
printf("light: %6.2flx\r\n",flight);
}
else{
printf("read err!\r\n");
}
sleep(4);
}
return 0;
}