2012年7月18日 星期三

I2C簡介(二)

前一篇簡單說明了如果寫I2C device的driver。這篇主要要介紹I2C adapter(controller的Driver)。在這裡我們直接以Tegra3的I2C Controller為例來說明。要注意的是,一個板子可能會有很多controller,而一個controller可能會有多條bus。每一個bus會對應一個adapter。

  • Board-Specific Code
Tegra3的board-specific code在arch/arm/mach-tegra/device.c 及arch/arm/mach-tegra/board-cardhu.c中。從source code我們可以看出Tegra 3有五組I2C controller。我們先看device.c的部份,在這裡主要是填寫platform_device的資料如下:
//arch/arm/mach-tegra/device.c
static struct resource i2c_resource1[] = {
 [0] = {
  .start = INT_I2C,
  .end = INT_I2C,
  .flags = IORESOURCE_IRQ,
 },
 [1] = {
  .start = TEGRA_I2C_BASE,
  .end = TEGRA_I2C_BASE + TEGRA_I2C_SIZE-1,
  .flags = IORESOURCE_MEM,
 },
};
                               .
                               .
                               .

static struct resource i2c_resource5[] = {
 [0] = {
  .start  = INT_I2C5,
  .end    = INT_I2C5,
  .flags  = IORESOURCE_IRQ,
 },
 [1] = {
  .start = TEGRA_I2C5_BASE,
  .end = TEGRA_I2C5_BASE + TEGRA_I2C5_SIZE-1,
  .flags = IORESOURCE_MEM,
 },
};
#endif

static struct tegra_i2c_platform_data tegra_i2c1_platform_data = {
 .bus_clk_rate   = { 400000 },
};
                               .
                               .
                               .

static struct tegra_i2c_platform_data tegra_dvc_platform_data = {
 .bus_clk_rate   = { 400000 },
};

struct platform_device tegra_i2c_device1 = {
 .name  = "tegra-i2c",
 .id  = 0,
 .resource = i2c_resource1,
 .num_resources = ARRAY_SIZE(i2c_resource1),
 .dev = {
  .platform_data = &tegra_i2c1_platform_data,
 },
};
                               .
                               .
                               .

struct platform_device tegra_i2c_device5 = {
 .name  = "tegra-i2c",
 .id  = 4,
 .resource = i2c_resource5,
 .num_resources = ARRAY_SIZE(i2c_resource5),
 .dev = {
  .platform_data = 0,
 },
};
接著我們繼續看board-cardhu.c。不知道為什麼,在這邊又把platform_data蓋掉@@。
static struct tegra_i2c_platform_data cardhu_i2c1_platform_data = {
 .adapter_nr = 0,
        //設定這個controller有幾個bus
 .bus_count = 1,
 .bus_clk_rate = { 100000, 0 },
 .scl_gpio  = {TEGRA_GPIO_PC4, 0},
 .sda_gpio  = {TEGRA_GPIO_PC5, 0},
 .arb_recovery = arb_lost_recovery,
};

                               .
                               .
                               .

static struct tegra_i2c_platform_data cardhu_i2c5_platform_data = {
 .adapter_nr = 4,
 .bus_count = 1,
 .bus_clk_rate = { 400000, 0 },
 .scl_gpio  = {TEGRA_GPIO_PZ6, 0},
 .sda_gpio  = {TEGRA_GPIO_PZ7, 0},
 .arb_recovery = arb_lost_recovery,
};

static struct i2c_board_info __initdata cardhu_codec_wm8903_info = {
 I2C_BOARD_INFO("wm8903", 0x1a),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
 .platform_data = &cardhu_wm8903_pdata,
};

static struct i2c_board_info __initdata cardhu_codec_aic326x_info = {
 I2C_BOARD_INFO("aic3262-codec", 0x18),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
};

static struct i2c_board_info __initdata cardhu_codec_max98095_info = {
 I2C_BOARD_INFO("max98095", 0x10),
 .irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_CDC_IRQ),
 .platform_data = &cardhu_max98095_pdata,
};

static void cardhu_i2c_init(void)
{
 tegra_i2c_device1.dev.platform_data = &cardhu_i2c1_platform_data;
                        .
                               .
                               .

 tegra_i2c_device5.dev.platform_data = &cardhu_i2c5_platform_data;

 platform_device_register(&tegra_i2c_device5);
                        .
                               .
                               .

 platform_device_register(&tegra_i2c_device1);

        
}

  •  I2C Controller Driver

看完了platform_device的宣告後,我們理所當然的去看platform_driver的地方。實作的位置在driver/i2c/busses/i2c-tegra.c。這裡我們從tegra_i2c_init_driver()看起。
static int tegra_i2c_probe(struct platform_device *pdev)
{
 struct tegra_i2c_dev *i2c_dev;
 struct tegra_i2c_platform_data *plat = pdev->dev.platform_data;
 struct resource *res;
 struct resource *iomem;
 struct clk *div_clk;
 struct clk *fast_clk = NULL;
 const unsigned int *prop;
 void *base;
 int irq;
 int nbus;
 int i = 0;
 int ret = 0;

        //取得定義在device.c的platform_device 的resource且flag為IORESOURCE_MEM 的資料
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
        //檢查從res->start~res->start+ resource_size(res)是否可用
        iomem = request_mem_region(res->start, resource_size(res), pdev->name);
 
        //mapping到kernel虛擬空間
 base = ioremap(iomem->start, resource_size(iomem));
 
 div_clk = clk_get(&pdev->dev, "i2c-div");
 
 fast_clk = clk_get(&pdev->dev, "i2c-fast");
 
        i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev) +
     (nbus-1) * sizeof(struct tegra_i2c_bus), GFP_KERNEL);
 

 i2c_dev->base = base;
 i2c_dev->div_clk = div_clk;
 i2c_dev->fast_clk = fast_clk;
 i2c_dev->iomem = iomem;
 i2c_dev->irq = irq;
 i2c_dev->cont_id = pdev->id;
 i2c_dev->dev = &pdev->dev;
 i2c_dev->is_clkon_always = plat->is_clkon_always;

 i2c_dev->last_bus_clk_rate = 100000; /* default clock rate */
 i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0];
        i2c_dev->is_high_speed_enable = plat->is_high_speed_enable;
 i2c_dev->last_bus_clk_rate = plat->bus_clk_rate[0] ?: 100000;
 i2c_dev->msgs = NULL;
 i2c_dev->msgs_num = 0;
 rt_mutex_init(&i2c_dev->dev_lock);
 spin_lock_init(&i2c_dev->fifo_lock);

 i2c_dev->slave_addr = plat->slave_addr;
 i2c_dev->hs_master_code = plat->hs_master_code;
 i2c_dev->is_dvc = plat->is_dvc;
 i2c_dev->arb_recovery = plat->arb_recovery;
 init_completion(&i2c_dev->msg_complete);

 if (irq == INT_I2C || irq == INT_I2C2 || irq == INT_I2C3)
  i2c_dev->is_slave = true;

 platform_set_drvdata(pdev, i2c_dev);

 if (i2c_dev->is_clkon_always)
  tegra_i2c_clock_enable(i2c_dev);

 ret = tegra_i2c_init(i2c_dev);
 if (ret) {
  dev_err(&pdev->dev, "Failed to initialize i2c controller");
  goto err_free;
 }

 

 for (i = 0; i < nbus; i++) {
  struct tegra_i2c_bus *i2c_bus = &i2c_dev->busses[i];

  i2c_bus->dev = i2c_dev;
  i2c_bus->mux = plat->bus_mux[i];
  i2c_bus->mux_len = plat->bus_mux_len[i];
  i2c_bus->bus_clk_rate = plat->bus_clk_rate[i] ?: 100000;

  i2c_bus->scl_gpio = plat->scl_gpio[i];
  i2c_bus->sda_gpio = plat->sda_gpio[i];

  i2c_bus->adapter.dev.of_node = pdev->dev.of_node;
  i2c_bus->adapter.algo = &tegra_i2c_algo;
  i2c_set_adapdata(&i2c_bus->adapter, i2c_bus);
  i2c_bus->adapter.owner = THIS_MODULE;
  i2c_bus->adapter.class = I2C_CLASS_HWMON;
  strlcpy(i2c_bus->adapter.name, "Tegra I2C adapter",
   sizeof(i2c_bus->adapter.name));
  i2c_bus->adapter.dev.parent = &pdev->dev;
  i2c_bus->adapter.nr = plat->adapter_nr + i;

                //!!將此bus註冊進kernel中
  ret = i2c_add_numbered_adapter(&i2c_bus->adapter);
                //註冊此bus的adapter
  of_i2c_register_devices(&i2c_bus->adapter);

  i2c_dev->bus_count++;
 }

 return 0;
        ...
}

static struct platform_driver tegra_i2c_driver = {
 .probe   = tegra_i2c_probe,
 .remove  = tegra_i2c_remove,
 .driver  = {
  .name  = "tegra-i2c",
  .owner = THIS_MODULE,
  .of_match_table = tegra_i2c_of_match,
  .pm    = TEGRA_I2C_DEV_PM_OPS,
 },
};

static int __init tegra_i2c_init_driver(void)
{
        //註冊tegra_i2c_driver,跳到tegra_i2c_probe繼續看
 return platform_driver_register(&tegra_i2c_driver);
}
subsys_initcall(tegra_i2c_init_driver);
  • Reference
  1. Introduction to I2C
  2. Linux I2C driver: what's new-style !?
  3. I2C总线使用方法继续研究
  4. nVidia Tegra Kernel Source

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