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.然後就會在menuconfig的Device Drivers選項中,看到GPIO Support的選項
3.選擇I2C Support,進去後就會看到有I2C Hardware Bus support中有了GPIO_based
bitbanging I2C的選項了
設定完之後,就把Linux kernel的I2C & GPIO support給enable了,在build kernel就可以把這部分給編譯進去使用。
4.整個i2c-gpio driver的架構
i2c-core.c à
i2c-algo-bit.c à i2c-gpio.c à gpiolib.c
i2c的standard function都定義在i2c-core.c的檔案中,其中用來傳輸的是i2c_master_send,宜路往下追會發現根據設定的algo不同,最後會呼叫連結到master_xfer的function來做傳送,以此次的例子對應到的是在i2c-algo-bit.c之中的bit_xfer()來傳送data,在這之中定義了getsda()、getscl()、setsda()和setscl(),來控制I2C的SCL和SDA的動作,完成I2C的data傳送。
然後往下還可以發現,採用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,這次是根據所使用的MCU的gpio控制function來定義,而不是用Linux本身寫好的。
以此次的case來說,因為GPIO並不是open-drain的設計,GPIO設定output 0,i2c為high訊號;GPIO設定input,i2c為low訊號。因此在sda_is_open_drain & scl_is_open_drain這兩個部分都要設定為0。
5. Linux i2c gpio driver設定
把I2C platform_device add到linux
#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來執行,其中slave的addr為0x70。
#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;
}
沒有留言:
張貼留言