1. 程式人生 > >裝置樹中的i2c

裝置樹中的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;
}