1、i2c總線簡介
I2C BUS(Inter IC BUS)是NXP推出的芯片間串行傳輸總線,它以2根連線實現了完善的雙向數據傳送,可以很方便地構成朵機系統和外圍器件擴展系統。I2C總線的2根線(串行數據SDA,串行時鐘SCL)連接到總線上的任何一個器件,每個器件都應有一個唯一的地址,而且都可以作爲一個發送器或者一個接受器。此外,器件在執行數據傳輸時也可以被看作是主機或從機。
發送器:本次傳送中發送數據(不包括地址和命令)到總線的器件。
接受器:本次傳送中從總線接受數據(不包括地址和命令)的器件。
主機: 初始化發送、產生時鐘信號和終止發送的器件,它可以是發送器或接受器。主機通常是微控制器。
從機: 被主機尋址的器件,它可以是發送器或接收器。
SDA和SCL都是雙向線路。連接到總線的器件的輸出級必須是漏級開路或集電極開路,都通過一個電流源或上拉電阻連接到正的電源電壓,這樣才能實現“線與”功能。當總線空閒時,這2條線路都是高電平。
在標準模式下,總線數據傳輸的速度爲0-100kbit/s,在高速模式下,可以達到0-400kbit/s。總線速率與總線上拉電阻的關係:總線速率越高,總線上拉電阻要越小。100kbit/s總線速率,通常使用5.1k的上拉電阻。
2、數據傳輸格式
2.1、空閒狀態
I2C總線總線的SDA和SCL兩條信號線同時處於高電平時,規定爲總線的空閒狀態。此時各個器件的輸出級場效應管均處在截止狀態,即釋放總線,由兩條信號線各自的上拉電阻把電平拉高。
2.2、起始信號和停止信號
起始信號(重複起始信號):在SCL爲高電平時,SDA從高電平向低電平切換。
停止信號:在SCL爲高電平時,SDA由低電平向高電平切換。
起始信號和停止信號一般由主機產生。起始信號作爲一次傳輸的開始,在起始信號後,總線被認爲處於忙的狀態。停止信號作爲一次傳送的結束,在停止信號的某時段後,總線被認爲再次處於空閒狀態。重複傳送信號即作爲上次傳送的結束,也作爲下次傳送的開始。
2.3、ACK
發送器每發送一個字節,就在時鐘脈衝9期間釋放數據線,由接收器反饋一個應答信號。 應答信號爲低電平時,規定爲有效應答位(ACK簡稱應答位),表示接收器已經成功地接收了該字節;應答信號爲高電平時,規定爲非應答位(NACK),一般表示接收器接收該字節沒有成功。 對於反饋有效應答位ACK的要求是,接收器在第9個時鐘脈衝之前的低電平期間將SDA線拉低,並且確保在該時鐘的高電平期間爲穩定的低電平。 如果接收器是主控器,則在它收到最後一個字節後,發送一個NACK信號,以通知被控發送器結束數據發送,並釋放SDA線,以便主控接收器發送一個停止信號P。
2.4、數據傳輸
2.4.1、主設備向從設備發送數據(即寫數據)
- 主設備發送起始信號;
- 主設備發送一個8位地址信號(這裏就是我們平常說的設備I2C地址),地址信號第8位爲低電平,表示寫信號;
- 主設備會在發送一個Word address(這裏的地址就是我們平常用到寫數據進去的寄存器地址);
- 發送數據;
- 發送停止信號
2.4.2、主設備讀從設備數據(即讀數據)
讀數據比寫數據要複雜,在讀數據前,首先要告訴從設備哪個寄存器是需要讀的,所以讀數據前,先會有一個寫數據過程。
- 主設備發送起始信號;
- 主設備發送一個8位地址信號(這裏就是我們平常說的設備I2C地址),地址信號第8位爲低電平,表示寫信號;
- 發送內部寄存器地址;
- 重複起始信號,重新發送設備地址,此時第8位變成高電平,表示讀信號;
- 讀取數據;
- 主設備發送停止信號。
3、代碼分析
Linux的I2C體系結構分爲3個組成部分:
I2C核心:I2C核心提供了I2C總線驅動和設備驅動的註冊,註銷方法,I2C通信方法(”algorithm”)上層的,與具體適配器無關的代碼以及探 測設備,檢測設備地址的上層代碼等。
I2C總線驅動:I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部。
I2C設備驅動:I2C設備驅動(也稱爲客戶驅動)是對I2C硬件體系結構中設備端的實現,設備一般掛接在受CPU控制的I2C適配器上,通過 I2C適配器與CPU交換數據。
具體框架如下所示:
I2c利用platform-bus總線進行註冊的,device信息在mach-mini210.c中填充,i2c-s3c2410.c通過name匹配,註冊device,並且註冊i2c_adapter通過__i2c_board_list全局鏈表匹配版級設備,版級設備在mach-mini210.c中填充,設備驅動通過設備name跟版級設備匹配,具體流程如下圖所示:
3.1、device部分
I2c總線跟其他總線類似,device部分在mini210_machine_init函數中註冊,同時會將io資源進行註冊,mini210_machine_init開機會運行,mini210_machine_int函數在linux-3.0.8/arch/arm/mach-s5pv210/mini210-lcds.c中定義,如下所示:
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
static struct platform_device *mini210_devices[] __initdata = {
………
&s3c_device_i2c0,
&s3c_device_i2c1,
&s3c_device_i2c2,
………
}
static void __init mini210_machine_init(void)
{
arm_pm_restart = smdkc110_pm_restart;
s3c_pm_init();
mini210_wifi_init();
#ifdef CONFIG_DM9000
mini210_dm9000_init();
#endif
/* Disable USB PHY for soft reboot */
if (__raw_readl(S5P_RST_STAT) & (1 << 3)) {
__raw_writel(0, S5P_USB_PHY_CONTROL);
}
platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));
設備的版級信息也是在mini210_machine_int函數進行註冊:
static struct i2c_board_info mini210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08", 0x50), }, /* Samsung S524AD0XD1 */
#ifdef CONFIG_SND_SOC_WM8580
{ I2C_BOARD_INFO("wm8580", 0x1b), },
#endif
#ifdef CONFIG_SND_SOC_WM8960_MINI210
{
I2C_BOARD_INFO("wm8960", 0x1a),
.platform_data = &wm8960_pdata,
},
#endif
#ifdef CONFIG_SENSORS_MMA7660
{
I2C_BOARD_INFO("mma7660", 0x4c),
.platform_data = &mma7660_pdata,
},
#endif
};
static void __init mini210_machine_init(void)
{
s3c_i2c2_set_platdata(&i2c2_data);
i2c_register_board_info(0, mini210_i2c_devs0,
ARRAY_SIZE(mini210_i2c_devs0));
i2c_register_board_info(1, mini210_i2c_devs1,
ARRAY_SIZE(mini210_i2c_devs1));
i2c_register_board_info(2, mini210_i2c_devs2,
ARRAY_SIZE(mini210_i2c_devs2));
#if defined(CONFIG_TOUCHSCREEN_1WIRE)
onewire_i2c_bdi.irq = gpio_to_irq(S5PV210_GPH1(6));
i2c_register_board_info(2, &onewire_i2c_bdi, 1);
#endif
#ifdef CONFIG_TOUCHSCREEN_EGALAX
i2c_register_board_info(5, i2c_devs5, ARRAY_SIZE(i2c_devs5));
#endif
i2c_register_board_info位註冊版級設備,函數中填充好版級信息,然後搜索全局鏈表__i2c_board_list,如果不存在相應版級信息,將新的版級信息註冊到__i2c_board_list鏈表中:
int __init i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
3.2 i2c-core部分
I2c-core是I2C核心提供了I2C總線驅動和設備驅動的註冊,註銷方法,I2C通信方法(”algorithm”)上層的,與具體適配器無關的代碼以及探測設備,檢測設備地址的上層代碼等。Tiny210該部分代碼在i2c-s3c2410.c中。
s3c24xx_i2c_probe中會指定i2c通信函數s3c24xx_i2c_algorithm,,初始化平臺相關的硬件設備,然後註冊一個i2c-adapter,這裏先看i2c-adapter是如何註冊的。
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
}
/* Sanity checks */
if (unlikely(adap->name[0] == '\0')) {
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) {
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
}
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
//設置 adap->dev.kobj.name 爲 i2c-0 ,它將出現在 sysfs 中
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//設置所屬的總線
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
//將 adap->dev註冊到i2c bus總線
res = device_register(&adap->dev);
if (res)
goto out_list;
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif
/* create pre-declared device nodes */
//掃描 __i2c_board_list 鏈表裏的設備信息,自動創建 client ,並註冊到 i2c_bus_type
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* Notify drivers */
mutex_lock(&core_lock);
//遍歷 i2c_bus_type的driver 鏈表,取出每一個driver 調用 i2c_do_add_adapter
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock);
return 0;
out_list:
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
return res;
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
//遍歷__i2c_board_list鏈表,取出每一個devinfo
list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一個設備都註冊一個
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
//遍歷__i2c_board_list鏈表,取出每一個devinfo
list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一個設備都註冊一個
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
i2c_scan_static_board_info會掃描__i2c_board_list 鏈表中i2c_register_board_info填充的版級信息,然後作爲device設備註冊成i2c bus設備。
3.3 i2c_add_driver註冊driver
i2c_add_driver函數是i2c-core暴露出來提供給設備驅動註冊i2c driver的,傳入的參數是一個i2c_driver類型結構體:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this, it will be removed in a
* near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
/*探測函數,匹配成功之後執行,會將匹配到的i2c_client對象傳入,
完成申請資源,初始化,提供接口等工作*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
/*移除函數,設備消失時會調用,驅動模塊被rmmod時也會先被調用,
完成和probe相反的操作*/
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
//電源管理相關的接口
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
//設備驅動類,用於匹配設備樹的of_match_table域在這裏
struct device_driver driver;
//用於使用平臺文件或模塊編寫設備信息時進行匹配使用,相當於platform_driver中的id_table
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
//用於將所有i2c_driver聯繫到一起的節點
struct list_head clients;
};
在設備驅動中,不需要每個變量都定義,只需定義部分變量,下面是goodix TP驅動中註冊driver實例:
#ifdef GTP_CONFIG_OF
static const struct of_device_id goodix_match_table[] = {
{.compatible = "goodix,gt9xx",},
{ },
};
#endif
static const struct i2c_device_id goodix_ts_id[] = {
{ GTP_I2C_NAME, 0 },
{ }
};
static struct i2c_driver goodix_ts_driver = {
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.id_table = goodix_ts_id,
.driver = {
.name = GTP_I2C_NAME,
.owner = THIS_MODULE,
#ifdef GTP_CONFIG_OF
.of_match_table = goodix_match_table,
#endif
#if !defined(CONFIG_FB) && defined(CONFIG_PM)
.pm = >p_pm_ops,
#endif
},
};
ret = i2c_add_driver(&goodix_ts_driver);
上面註冊的driver name跟device註冊的name是相同的,i2c_add_driver函數會調用到i2c-core.c中的i2c_register_driver函數:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
/* Drivers should switch to dev_pm_ops instead. */
if (driver->suspend)
pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
driver->driver.name);
if (driver->resume)
pr_warn("i2c-core: driver [%s] using legacy resume method\n",
driver->driver.name);
pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
i2c_register_driver中會調用driver_register去註冊driver,然後通過i2c_for_each_dev(driver, __process_new_driver);將i2c_driver與i2c_adapter關聯起來。
driver_register主要完成下面工作:
- 查找bus總線是否註冊了driver
- bus_add_driver中調用driver_attach,通過__driver_attach函數中的driver_match_device調用bus中的match函數,也是i2c_device_match函數,通過比較i2c_driver->id_table->name和client->name,如果相同,則匹配上,匹配上之後,運行driver_register調用driver_probe_device進行設備與驅動綁定。
- __driver_attach中調用driver_probe_device進入不同設備函數中的probe進行匹配。
- 在sys下面創建對應的driver節點
- 3.4s3c24xx_i2c_algorithm通訊函數
- 不同平臺的i2c函數都會有自己的通訊函數,tiny210開發板的通訊函數是s3c24xx_i2c_algorithm,linux會將通訊函數進行封裝,提供給驅動使用都是i2c_transfer函數。
i2c_transferàadap->algo->master_xfer(adap, msgs, num)à s3c24xx_i2c_algorithm(s3c24xx_i2c_probe中有填充)
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
//使能clk
clk_enable(i2c->clk);
for (retry = 0; retry < adap->retries; retry++) {
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
if (ret != -EAGAIN) {
clk_disable(i2c->clk);
return ret;
}
dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);
udelay(100);
}
//關閉clk
clk_disable(i2c->clk);
return -EREMOTEIO;
}
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
if (i2c->suspended)
return -EIO;
//設置開始傳輸寄存器
ret = s3c24xx_i2c_set_master(i2c);
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
//使能傳輸中斷
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs); //數據傳輸
spin_unlock_irq(&i2c->lock);
//超時等待
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;
/* having these next two as dev_err() makes life very
* noisy when doing an i2cdetect */
if (timeout == 0)
dev_dbg(i2c->dev, "timeout\n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
/* ensure the stop has been through the bus */
udelay(10);
out:
return ret;
}
- 、s3c24xx_i2c_set_master
writel(iicstat & ~S3C2410_IICSTAT_TXRXEN, i2c->regs + S3C2410_IICSTAT); // 1<<4 I2C-bus data output enable/ disable bit
if (!(readl(i2c->regs + S3C2410_IICSTAT) & S3C2410_IICSTAT_BUSBUSY)// 判斷總線是否忙
2、s3c24xx_i2c_enable_irq(i2c)
readl(i2c->regs + S3C2410_IICCON) //1<<5 I2C-Bus Tx/Rx interrupt enable/ disable bit.
writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON); //第5位置1
3、s3c24xx_i2c_message_start(i2c, msgs)
s3c24xx_i2c_enable_ack(i2c); // 0xE180_0000寫入1<<7位,使能ack位
writel(stat, i2c->regs + S3C2410_IICSTAT); // 2<<6 Master receive mode 3<<6 Master transmit mode
writeb(addr, i2c->regs + S3C2410_IICDS);
/* I2C-Bus busy signal status bit.
0 = read) Not busy (If read) write) STOP signal generation
1 = read) Busy (If read) write) START signal generation. The data in I2 CDS is transferred automatically just after the start signal
*/
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT); //1<<4 i2c-bus data output enable
3.5硬件寄存器設置
在上面s3c24xx_i2c_probe和s3c24xx_i2c_doxfer都會有一些寄存器設置,下面來看看具體含義。
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
struct s3c2410_platform_i2c *pdata;
unsigned int freq;
/* get the plafrom data */
pdata = i2c->dev->platform_data;
/* inititalise the gpio */
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));
/* write slave address */
//write 0xE1800008 Specifies the I2C-Bus Interface0 address register P894
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
//write 0xE1800000; data: 1<<5 | 1<<7 Specifies the I2C-Bus Interface0 control register
writel(iicon, i2c->regs + S3C2410_IICCON);
/* we need to work out the divisors for the clock... */
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
writel(0, i2c->regs + S3C2410_IICCON);
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}
/* todo - check that the i2c lines aren't being dragged anywhere */
dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
//Specifies the I2C-Bus Interface0 multi-master line control register
writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
return 0;
}
1、writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
指定i2c總線0寄存器地址
2、writel(iicon, i2c->regs + S3C2410_IICCON);
0xE1800000寫1<<7,1<<5
3、writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);