Tiny210 I2C總線

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、主設備向從設備發送數據(即寫數據)

  1. 主設備發送起始信號;
  2. 主設備發送一個8位地址信號(這裏就是我們平常說的設備I2C地址),地址信號第8位爲低電平,表示寫信號;
  3. 主設備會在發送一個Word address(這裏的地址就是我們平常用到寫數據進去的寄存器地址);
  4. 發送數據;
  5. 發送停止信號

 

2.4.2、主設備讀從設備數據(即讀數據)

   讀數據比寫數據要複雜,在讀數據前,首先要告訴從設備哪個寄存器是需要讀的,所以讀數據前,先會有一個寫數據過程。

  1. 主設備發送起始信號;
  2. 主設備發送一個8位地址信號(這裏就是我們平常說的設備I2C地址),地址信號第8位爲低電平,表示寫信號;
  3. 發送內部寄存器地址;
  4. 重複起始信號,重新發送設備地址,此時第8位變成高電平,表示讀信號;
  5. 讀取數據;
  6. 主設備發送停止信號。

 

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		  = &gtp_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主要完成下面工作:

  1. 查找bus總線是否註冊了driver
  2. 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進行設備與驅動綁定。
  3. __driver_attach中調用driver_probe_device進入不同設備函數中的probe進行匹配。
  4. 在sys下面創建對應的driver節點
  5. 3.4s3c24xx_i2c_algorithm通訊函數
  6. 不同平臺的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;
}
  1. 、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);

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章