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 在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。
在這裡我們直接以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);
}
如何從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
- Introduction to I2C
- Linux I2C driver: what's new-style !?
- I2C总线使用方法继续研究
- nVidia Tegra Kernel Source
沒有留言:
張貼留言