1. 程式人生 > >amlogic平臺android 系統linux核心中新增i2c裝置實現i2c的讀寫

amlogic平臺android 系統linux核心中新增i2c裝置實現i2c的讀寫

上一篇,我介紹瞭如何在uboot中新增i2c裝置,以及移植i2c的讀寫介面。簡單來說uboot階段使用i2c裝置和平臺關聯性比較大,但不同平臺套路是差不多的。你可以將uboot階段看作是引導android系統起來的另外一個系統。而系統起來後在kenerl中新增i2c裝置就和uboot階段差別非常大,內容要多很多。我仍然不會貼一大段一大段的程式碼上來,這沒什麼意義。我的目的更多的是給沒玩過i2c的小夥伴看完這篇介紹後,能有個方向。
內容主要分兩個方面,第一有關i2c的概念。第二在核心中新增i2c裝置並實現讀寫的方法和流程。
首先來看第一方面,這涉及到幾個點:
    1. 裝置樹概念。
    2. i2c匯流排概念。
    3. i2c驅動
    4. i2c控制器adapter
以上三點自行百度可以找到很詳細的資料,在這裡我只簡單說明下:
1. 裝置樹你可以理解為它是用來專門儲存板子上各個硬體裝置資訊的一個檔案。比如某個裝置GPIO口是多少,狀態是否為開啟,暫存器地址多少等等。你可能需要這這裡新增你的裝置資訊。
2. i2c驅動你可以簡單理解為對於應用層來說,驅動就是應用層溝通底層硬體裝置的橋樑(i2c-核心層),應用層會呼叫驅動層的介面來溝通底層硬體。驅動主要負責拿到上層要對硬體操作的資料,然後提供給i2c控制器對硬體進行讀寫。
3. 匯流排就是負責匹配驅動與裝置樹硬體資訊及adapter資訊。
4. i2c控制器adapter和硬體相關,比如amlogic平臺有5組i2c控制器。它的作用是從驅動中拿到資料後負責將資料寫入硬體中。
我沒有仔細去研究底層程式碼,大概的看了下,總的來說我的理解是裝置樹中包含裝置資訊還有當前裝置所在的adapter。在載入kernel的過程中會被拿出來,通過匯流排匹配到相對應的i2c驅動(匹配的方式是compatible name), 匹配完後,客戶就可以通過i2c驅動中提供的介面,利用匹配的i2c adapter對i2c裝置進行讀寫。

概念簡單介紹完了,接下來就是重點如何在核心中新增i2c裝置並實現讀寫。
通常在kernel中新增i2c裝置的方式一般有兩種:
    方式一:(不推薦) 自己在kernel中寫一個驅動,然後給介面上層去呼叫。如果你打算自己寫個驅動,那麼kernel目錄下的Documentation\i2c中的檔案說明可以幫助你。寫完驅動後你還需要在dts中,需要配置硬體的資訊如下:(參考dts檔案路徑,核心目錄下:arch\arm64\boot\dts\amlogic)
         &i2c_ao {
            status = "okay";            //開啟第ao組i2c。前面也說了amlogic平臺的i2c控制器一共有五組。
            pinctrl-names = "default";
            pinctrl-0 = <&d_i2c_master>;    
            at24 {                      //需要新增的裝置相關資訊
                dev_name = "at24";      //裝置名字
                reg =
<0x50>; //從裝置地址。。等等 pagesize = <8>; ........ }; };
    這個dts的意思是,在i2c d組下,掛載了at24這個裝置,kernel中的at24的驅動,會呼叫到這個dts。

    對於i2c控制器本身資訊也要在新增在dtsi檔案中如下:(參考路徑:arch/arm64/boot/dts/amlogic/mesongxl.dtsi 注意檔名是dtsi我沒有寫錯)
            i2c_ao: 
[email protected]
{ /*I2C-AO*/ compatible = "amlogic, meson-i2c"; dev_name = "i2c-AO"; status = "disabled"; reg = <0x0 0xc8100500 0x0 0x1d>; device_id = <0>; pinctrl-names="default"; pinctrl-0=<&ao_i2c_master>; #address-cells = <1>; #size-cells = <0>; use_pio = <0>; master_i2c_speed = <400000>; clocks = <&clock CLK_81>; clock-names = "clk_i2c"; resets = <&clock GCLK_IDX_I2C>; } //從字面意思也能理解上面的資訊,實在看不懂不必理會,因為這些和硬體相關地址都是固定的。一般原廠會寫好,你只需要寫上面dts中那段,來控制開啟第幾組i2c控制器和上面掛的裝置的裝置資訊即可。
總的來說,如果使用這個方式大概流程如下:
    1. 在裝置樹中開啟你裝置掛載的那個i2c控制器.
    2. 在裝置樹中的那組i2c控制器中新增上裝置相關的資訊。
    3. 自己寫個驅動,比如申請裝置號,建立裝置檔案,建立裝置節點,加入匯流排,在驅動裡面要寫提供給上層呼叫的介面,比如open裝置節點,i2c_read,i2c_write等。
    4. 同樣在系統層要控制某個裝置都是以一個程序的形式(程序的意思就是相關程式碼C應用,可執行程式或者android APP),在這個程序裡面會去呼叫自己在驅動中寫的open、read、write來對裝置進行讀寫操作。
    這個方式不太推薦,特別對於平臺工程師而言,很多平臺工程師對於驅動是隻見過豬跑,沒真正吃過豬肉,平時驅動看的多,但讓真寫一個完整的驅動可能要花很多時間去搞一些細節。所以我更加傾向於第二種方式。
方式二:(推薦)
這種方式是大多數平臺在移植i2c裝置的時候使用的方式,開發效率快。對裝置的操作的呼叫也同樣是在程序裡面,不同於方式一的是直接通過呼叫平臺封裝好的ioctl來進行讀寫,而不需要自己去寫驅動。這個就和平臺相關了,注意不同平臺使用ioctrl進行讀寫只是過程有些差異,但使用ioctrl來讀寫這種方式是個標準。
我會先詳細介紹這個方式的流程然後再對比方式一就豁然開朗了,流程如下:
    1. 在裝置樹dts檔案中新增以下資訊:
    &i2c_ao {               //這一段的目的是開啟第ao組i2c控制器。
                status = "okay";
            };
        在dtsi檔案中新增以下資訊
i2c_ao: [email protected]{ /*I2C-AO*/
                compatible = "amlogic, meson-i2c";
                dev_name = "i2c-AO";
                status = "disabled";
                reg = <0x0 0xc8100500 0x0 0x1d>;
                device_id = <0>;
                pinctrl-names="default";
                pinctrl-0=<&ao_i2c_master>;
                #address-cells = <1>;
                #size-cells = <0>;
                use_pio = <0>;
                master_i2c_speed = <400000>;
                clocks = <&clock CLK_81>;
                clock-names = "clk_i2c";
                resets = <&clock GCLK_IDX_I2C>;
            }
    2. 在操作i2c裝置的程序的程式碼中加入開啟裝置節點程式碼,如下:
int i2c_open()
            {
                //  printf("i2c_open()\n");
                if((i2c_fd = open(DEVICE_NAME, O_RDWR)) == -1) 
                {
                    printf("i2c open %s is fail.\n",DEVICE_NAME);

                    return fail;    
            }else{
                i2c_data.nmsgs=2;
                i2c_data.msgs=(struct i2c_msg*)malloc(i2c_data.nmsgs*sizeof(struct i2c_msg));      
                if(!i2c_data.msgs){
                    printf("i2c_date malloc error \n");
                    close(i2c_fd);
                    exit(1);       
                }
                ret = ioctl(i2c_fd, I2C_TIMEOUT, 2);//set timeout ,2*10ms
            if(ret <0){
                printf("i2c_open ioctl set timeout is fail! \n");
            }
            ret = ioctl(i2c_fd, I2C_RETRIES, 1);//set resend times
            if(ret <0){
                printf("i2c_open ioctl set resend times is fail! \n");
            }
                return i2c_fd;
            }
            }
    3. 然後再呼叫ioctrl進行讀寫。這一步和平臺相關,需要在原始碼中找demo,模仿demo的讀寫介面寫就行了,原始碼找不到去找原廠工程師提供一個成功案例的歷程仿照著寫。我在這裡提供amlogic使用ioctrl進行寫的程式碼:
AAA_u1 i2c_read_byte( AAA_u8 slaveAddr, AAA_u8 subAddr, AAA_u8 len, AAA_u8* dataBuf, AAA_u8 device )
            {
                if(len > 4){
                    printf("dataLength is invalid\n");
                    return fail;
            }
            slaveAddr = slaveAddr >> 1 ;    //注意一般從裝置地址位為7位,若你裝置地址本身是8位,則向右移一位。不過少數平臺是向左移。
            int count = 0;
            i2c_data.nmsgs=2;
            (i2c_data.msgs[0]).len=1;
            (i2c_data.msgs[0]).addr=slaveAddr;   //  slave address

            (i2c_data.msgs[0]).flags=0;  //read ,如果值為1 則是write操作
            (i2c_data.msgs[0]).buf=(unsigned char*)malloc(1);
            (i2c_data.msgs[0]).buf   =(unsigned char*)&subAddr;
            (i2c_data.msgs[1]).len   = len;
            (i2c_data.msgs[1]).addr  = slaveAddr;
            (i2c_data.msgs[1]).flags = I2C_M_RD; //read
            (i2c_data.msgs[1]).buf   = (unsigned char*)malloc(len * sizeof(unsigned char));
            (i2c_data.msgs[1]).buf   = dataBuf;

            if(ioctl(i2c_fd,I2C_RDWR,(unsigned long)&i2c_data)<0){
                printf("ioctl read error. ret = %d \n",ioctl(i2c_fd,I2C_RDWR,(unsigned long)&i2c_data));
                printf("ioctrl error : %s \n",strerror(errno));
                return fail;
            }
            if(DEBUG){
                printf("**slaveAddr=0x%x,subAddr=0x%x**\n",(i2c_data.msgs[0]).addr,(i2c_data.msgs[0]).buf[0]);
                for(count=0;count < (i2c_data.msgs[1]).len;count++)
                printf("***read dataBuf[%d] = 0x%x***\n",count,(i2c_data.msgs[1]).buf[count]);
            }
            return success;
            }
我是最不願意貼一大段程式碼進來的,我自己都看的都噁心,但是在這裡貼進來做個借鑑。注意,不要複製上面這段程式碼去貼進你的讀操作裡去改,沒有任何意義,我可以告訴你在之前的MTK平臺使用ioctrl進行讀操作和現在這個amlogic平臺是有差異的。最快的方式還是去自己平臺上找demo,要麼直接問原廠要。
從上面的讀操作中可以看出,所有的裝置資訊都是放在i2c_data這個物件裡面的,然後呼叫了ioctl(i2c_fd,I2C_RDWR,(unsigned long)&i2c_data)就完成了讀寫。和方式一相比:你不用去寫驅動,驅動中需要各種註冊裝置號,節點太麻煩!只需要開啟裝置所在的那組i2c控制器,在程序程式碼中讀寫i2c前開啟與i2c匹配的裝置節點即可。
對驅動有些瞭解的小夥伴可能會有疑問:如果當前dev/中不止一個裝置節點,那怎麼知道到底使用哪個裝置節點。如果不註冊裝置驅動,哪來的裝置節點。其實是這樣的,還是那句話,平臺會幫你搭建好,一般如果你在dts中打開了第ao組i2c控制器,那麼相對應你會發現/dev中會自動建立一個裝置節點與其相對應比如i2c-0,你再開打第B組i2c控制器,/dev下又會多一個裝置節點i2c-2。
使用這種方式的核心思維在於:你只需要是站在應用的角度去使用它,從需要操作i2c讀寫裝置的程序程式碼中去設定我的從裝置資訊,然後呼叫ioctrl即可實現i2c的讀寫,而完全不用去理會底層做了些什麼。
好了,寫了一大堆,好累... 如果有什麼說的不對的地方,歡迎指正,相互學習。有興趣的小夥伴可以去追一追系統中的原始碼...