2014/05/20

[Linux] Build Linux i2c-gpio module 標準Linux i2c interface[bit-banging]

I2C是在IC之間,常被使用的data bus,而在目前的MCU之中,很多時候都會用GPIO來做模擬,也稱作bit-banging,而且Linux kernel也有support這個部分的driver,以下為研究的小心得:

詳細的I2C protocol可以參考

這次的I2C detail information:
7 bits address / not open-drain / 100kHz


實作流程
1.增加"ARCH_REQUIRE_GPIOLIB"選項來support I2C & GPIO
\arch\mips\Kconfig
config  MACH_XXXX <---根據板子MUC chip及指令集
        select ARCH_REQUIRE_GPIOLIB   <--- 增加此選項,讓硬體support GPIOLIB

2.然後就會在menuconfigDevice Drivers選項中,看到GPIO Support的選項

3.選擇I2C Support,進去後就會看到有I2C Hardware Bus support中有了GPIO_based bitbanging I2C的選項了
設定完之後,就把Linux kernelI2C & GPIO supportenable了,在build kernel就可以把這部分給編譯進去使用。

4.整個i2c-gpio driver的架構
i2c-core.c à i2c-algo-bit.c à i2c-gpio.c à gpiolib.c
i2cstandard function都定義在i2c-core.c的檔案中,其中用來傳輸的是i2c_master_send,宜路往下追會發現根據設定的algo不同,最後會呼叫連結到master_xferfunction來做傳送,以此次的例子對應到的是在i2c-algo-bit.c之中的bit_xfer()來傳送data,在這之中定義了getsda()getscl()setsda()setscl(),來控制I2CSCLSDA的動作,完成I2Cdata傳送。
然後往下還可以發現,採用i2c-gpio.c來實作時,一開始i2c-gpio dreiver註冊的時候,就有i2c-g[op_probe,定義了getsda()getscl()setsda()setscl()這四個function的連結對應到i2c_gpio_setsda_dir()i2c_gpio_setscl_dir() [這部分會根據一開始設定的sda_is_open_drain & scl_is_open_drain的結果連結到不同的function定義]。最後再往下trace,就會看到i2c_gpio_setsda_dir()i2c_gpio_setscl_dir()定義在gpiolib.c之中。
附註:在gpiolib.c之中的function,這次是根據所使用的MCUgpio控制function來定義,而不是用Linux本身寫好的。

以此次的case來說,因為GPIO並不是open-drain的設計,GPIO設定output 0i2chigh訊號;GPIO設定inputi2clow訊號。因此在sda_is_open_drain & scl_is_open_drain這兩個部分都要設定為0

5. Linux i2c gpio driver設定
I2C platform_device addlinux

#if defined(CONFIG_I2C_GPIO)
static struct i2c_gpio_platform_data foo_i2c_data = {
    .sda_pin = CHIP_I2C_SDA,
    .sda_is_open_drain = 0,
    .scl_pin = CHIP_I2C_SCL,
    .scl_is_open_drain = 0,
    .udelay = 5, /* ~100 kHz */
};

static struct platform_device foo_i2c_device = {
    .name = "i2c-gpio",
    .id = -1,
    .dev.platform_data = &foo_i2c_data,
};
#endif

static struct platform_device *chip_platform_devices[] __initdata = {
#ifdef CONFIG_SERIAL_8250
    &chip_uart,
#endif
#if defined(CONFIG_I2C_GPIO)
    &foo_i2c_device,
#endif
    &chip_usb_ehci_device_1,
    &chip_usb_ehci_device_2,
#ifdef CONFIG_CHIP_HAS_PCI_EP
    &chip_pci_ep_device
#endif

};

int chip_platform_init(void)
{
    int ret;

#ifdef CONFIG_SERIAL_8250
    chip_uart_data[0].uartclk = chip_uart_freq;
#endif

    ret = platform_add_devices(chip_platform_devices,
    ARRAY_SIZE(chip_platform_devices));

    if (ret < 0) {
        printk("%s: failed %d\n", __func__, ret);
        return ret;
    }

    return 0;
}

開機後應該可以看到i2c device的訊息

接著試著來使用i2c_master_send來傳送data,寫一個kernel module來執行,其中slaveaddr0x70

#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#include <linux/proc_fs.h> /* Needed for the proc_entry */
#include <linux/uaccess.h> /* Needed for copy_from_user */
#include <linux/delay.h>

#if defined(CONFIG_I2C_GPIO)
#include <linux/i2c.h>
struct i2c_adapter *adap;
struct i2c_client *client;
struct i2c_board_info info;
unsigned char I2C_SLAVE_cmd[5];
#endif

int foo_i2c_init_module(void)
{
#if defined(CONFIG_I2C_GPIO)
    adap = i2c_get_adapter(0);
    if (!adap) {
        printk(KERN_INFO "can't get i2c_get_adapter\n");
    }

    memset(&info, 0, sizeof(info));
    strlcpy(info.type, "I2C_SLAVE", sizeof(info.type));
    info.addr = 0x70;

    /* Create the i2c client */
    client = i2c_new_device(adap, &info);
    if (client == NULL)
        printk(KERN_INFO "can't open i2c_new_device\n");

    I2C_SLAVE_cmd[0]=0xC0;
    I2C_SLAVE_cmd[1]=0x01;
    I2C_SLAVE_cmd[2]=0x00;
    I2C_SLAVE_cmd[3]=0x03;
    I2C_SLAVE_cmd[4]=0xE8;

    i2c_master_send(client, I2C_SLAVE_cmd, 5);
    mdelay(30);
#endif

    return 0;
}

最後i2c的結果
就可以看到i2c的訊號了,有任何問題歡迎討論!

沒有留言:

張貼留言