2012年7月18日 星期三

I2C簡介(一)

  • I2C簡介
I2C 全名是Inter IC,原先是Phillips發展的一個two-wire 的serial bus protocol。他是一種半雙工同步多組設備匯流排。只需要兩條線:串列資料線(SDA)與串列時脈線(SCL or SCK)。I2C 提供兩種定址模式與三種傳輸模式。定址方面分成10-bit(允許連接1024個裝置)與 7-bit (允許連接128個裝置)。傳輸方面則有10kbps 的低速模式、100kbps 的標準模式、及 400kbps 的快速模式。
I2C連接的裝置都是Open Drain的接腳,也就是說導通的時候電位是low,而不導通時為high。I2C通常用在嵌入式系統與不同的裝置溝通。在PC,I2C通常用來與不同的sensor 連結例如取得系統風扇的轉速、CPU的溫度等等。
  • I2C in Linux
I2C 在kernel 2.x的時候開始支援。在Linux Kernel中,I2C的code拆成四個部份: I2C core、I2C bus drivers、I2C algorithm drivers 和 I2C chip drivers。

舉一個例子,一個PMU(power management unit)利用I2C與SOC連結。要使PMU能夠正確運作我們必須先撰寫I2C adapter(controller)的driver,讓I2C bus可以正常運作。I2C adapter(controller)可能是外接的chip也可能是在SOC裡頭例如Tegra3擁有五組I2C controller。再來,我們必須撰寫PMU的driver,註冊進正確的I2C bus中。透過adapter(controller)與device溝通。
這一篇的介紹是在於如何撰寫下圖的i2c client driver來控制(R/W)i2c device。
一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。
  • 撰寫I2C Device Driver
在這裡我們直接以TI的PMU tps6591為例子說明。原始碼一樣可以透過下面 reference中抓取。
我們首先先看看tps6591 board-specific code的地方。我們看cardhu_regulator_init()。
//arch/arm/mach-tegra/board-cardhu-power.c
static struct tps6591x_platform_data tps_platform = {
        //pmu的irq起始編號
 .irq_base = TPS6591X_IRQ_BASE,
        //gpio的起始編號
 .gpio_base = TPS6591X_GPIO_BASE,
 .dev_slp_en = true,
 .slp_keepon = &tps_slp_keepon,
 .use_power_off = true,
};

static struct i2c_board_info __initdata cardhu_regulators[] = {
    {
        //設定pmu的i2c device number
        I2C_BOARD_INFO("tps6591x", 0x2D),
        .irq    = INT_EXTERNAL_PMU,
        .platform_data = &tps_platform,
    },
};

int __init cardhu_regulator_init(void){
    ...
    /**註冊i2c device進list: __i2c_board_list
      *bus number=4  
      *device info=cardhu_regulators
      *#device=1 
    */
    i2c_register_board_info(4, cardhu_regulators, 1);
    ...
}
接著在tps6591的driver中,我們從tps6591x_init()看起。
//driver/mfd/tps6591x.c

static int __devinit tps6591x_i2c_probe(struct i2c_client *client,
     const struct i2c_device_id *id)
{
 struct tps6591x_platform_data *pdata = client->dev.platform_data;
 struct tps6591x *tps6591x;
 int ret;

 if (!pdata) {
  dev_err(&client->dev, "tps6591x requires platform data\n");
  return -ENOTSUPP;
 }

 ret = i2c_smbus_read_byte_data(client, TPS6591X_VERNUM);
 if (ret < 0) {
  dev_err(&client->dev, "Silicon version number read"
    " failed: %d\n", ret);
  return -EIO;
 }

 dev_info(&client->dev, "VERNUM is %02x\n", ret);

 
        //same as dev_set_drvdata(&dev->dev, data);
 i2c_set_clientdata(client, tps6591x);

 mutex_init(&tps6591x->lock);

 ...

 tps6591x_i2c_client = client;

 return 0;

err_add_devs:
 if (client->irq)
  free_irq(client->irq, tps6591x);
err_irq_init:
 kfree(tps6591x);
 return ret;
}

static struct i2c_driver tps6591x_driver = {
 .driver = {
  .name = "tps6591x",
  .owner = THIS_MODULE,
 },
 .probe  = tps6591x_i2c_probe,
 .remove  = __devexit_p(tps6591x_i2c_remove),
#ifdef CONFIG_PM
 .suspend = tps6591x_i2c_suspend,
 .resume  = tps6591x_i2c_resume,
#endif
 .id_table = tps6591x_id_table,
};

static int __init tps6591x_init(void)
{
        /**註冊I2C device driver
         *將這個driver與bus做配對並呼叫probe
        */
 return i2c_add_driver(&tps6591x_driver);
}
  • R/W VIA I2C
如何從I2C device讀取資料呢?我們以 tps6591x_read為例:
static inline int __tps6591x_read(struct i2c_client *client,
      int reg, uint8_t *val)
{
 int ret;
        //read data from address:reg
 ret = i2c_smbus_read_byte_data(client, reg);
 if (ret < 0) {
  dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
  return ret;
 }

 *val = (uint8_t)ret;

 return 0;
}


讀取資料呢?我們以 tps6591x_write為例:
static inline int __tps6591x_write(struct i2c_client *client,
     int reg, uint8_t val)
{
 int ret;
        //write value:val to address:reg
 ret = i2c_smbus_write_byte_data(client, reg, val);
 if (ret < 0) {
  dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
    val, reg);
  return ret;
 }

 return 0;
}

其他的 read/write可以參考driver/i2c/i2c-core.c
  • Reference
  1. Introduction to I2C
  2. Linux I2C driver: what's new-style !?
  3. I2C总线使用方法继续研究
  4. nVidia Tegra Kernel Source

沒有留言: