十九、IIC驅動框架介紹及驅動代碼解析


一、IIC驅動框架簡介

1、IIC物理總線

  • SCL:時鍾線,數據收發同步。
  • SDL:數據線,具體數據

支持一主多從,各設備地址獨立,標准模式傳輸速率為100kbit/s,快速模式為400kbit/s

2、常見IIC設備

  • EEPROM
  • 觸摸芯片
  • 溫濕度傳感器
  • mpu6050

3、框架圖

  • I2C核心

    提供I2C總線驅動和設備驅動的注冊方法、注銷方法、I2C通信硬件無關代碼。

  • I2C總線驅動

    主要包含I2C硬件體系結構中適配器(IIC控制器)的控制,用於I2C讀寫時序。

    主要數據結構:I2C_adapter、 I2C_algorithm.

  • I2C設備驅動

    通過I2C適配器與CPU交換數據。

    主要數據結構:i2c_driver和i2c_client.

  • I2C適配器

    i2c adapter是軟件上抽象出來的i2c總線控制器接口,物理上一條i2c總線可以掛接多個硬件設備(slave)一個CPU可以掛接多條i2c總線(想象一下PCI總線),i2c總線控制器就是CPU訪問I2C總線的硬件接口,也就是你說的那幾個寄存器  

    簡單來說,你的開發板上有幾個I2C接口,就有幾個adapter , 也就是有幾條I2C bus , I2C CLIENT 對應的就是你的外圍I2C 設備,有幾個就有幾個CLIENT , 把這些設備插入開發板, 對應其中的一條BUS, 那么相應的就對應了其中的一個ADAPTER , 接下來的就是                 CLIENT 與 ADAPTER 勾搭成對了, 后面就是做該做的事了

二、I2C驅動文件分析

1、i2c-dev.c

(1)初始化
static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
    //1、注冊字符設備
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;
    //2、創建類
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;
   //3、追蹤哪個i2c適配器被添加或者移除   
	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;
   //4、立即綁定已經存在的適配器
	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}
(2)文件操作集合  

i2c設備驅動的文件操作集合中有兩個ioctl:

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,   //讀
	.write		= i2cdev_write,  //寫
	.unlocked_ioctl	= i2cdev_ioctl,  
	.compat_ioctl	= compat_i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
}; 

兩者的區別在於:

  • 64位的用戶程序運行在64位的kernel上,調用的是compat_ioctl,
  • 32位的APP運行在32位的kernel上,調用的也是unlocked_ioctl。 
(3)讀操作
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	struct i2c_client *client = file->private_data;
        //最多讀8192個字節
	if (count > 8192)
		count = 8192;

	tmp = kzalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;

	pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
		iminor(file_inode(file)), count);
        //接收到i2c傳過來的數據
	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
          //將數據拷貝到用戶空間 if (copy_to_user(buf, tmp, ret)) ret = -EFAULT; kfree(tmp); return ret; }
(4)寫操作
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;
       //分配一塊內存空間,將用戶空間的數據拷貝進去
	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
		iminor(file_inode(file)), count);
     //i2c發送 
	ret = i2c_master_send(client, tmp, count);
	kfree(tmp);
	return ret;
}  
memdup_user函數
/**
 * memdup_user - duplicate memory region from user space
 *
 * @src: source address in user space
 * @len: number of bytes to copy
 *
 * Return: an ERR_PTR() on failure.  Result is physically
 * contiguous, to be freed by kfree().
 */
void *memdup_user(const void __user *src, size_t len)
{
	void *p;
        //分配內核態的空間
	p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN);
	if (!p)
		return ERR_PTR(-ENOMEM);
     //從用戶空間拷貝數據
	if (copy_from_user(p, src, len)) {
		kfree(p);
		return ERR_PTR(-EFAULT);
	}

	return p;
}
(5)i2cdev_ioctl
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
      //設置從機地址
      //I2C_SLAVE和I2C_SLAVE_FORCE的區別在於I2C_SLACE會檢查設備地址,如果地址已經使用,就不能使用重復的地址,否則會返回-EBUSY,而I2C_SLACE_FORCE會跳過檢查 case I2C_SLAVE: case I2C_SLAVE_FORCE: if ((arg > 0x3ff) || (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f)) return -EINVAL; if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg)) return -EBUSY; /* REVISIT: address could become busy later */ client->addr = arg; return 0;
    //設置10bit地址模式
//如果select不等於0選擇10bit地址模式,如果等於0選擇7bit模式,默認7bit。只有適配器支持I2C_FUNC_10BIT_ADDR,這個請求才是有效的 case I2C_TENBIT: if (arg) client->flags |= I2C_M_TEN; else client->flags &= ~I2C_M_TEN; return 0;
//設置傳輸后增加PEC標志(用於數據校驗)
     //這個命令只對SMBus傳輸有效。這個請求只在適配器支持I2C_FUNC_SMBUS_PEC時有效;如果不支持這個命令也是安全的,它不做任何工作  case I2C_PEC: /* * Setting the PEC flag here won't affect kernel drivers, * which will be using the i2c_client node registered with * the driver model core. Likewise, when that client has * the PEC flag already set, the i2c-dev driver won't see * (or use) this setting. */ if (arg) client->flags |= I2C_CLIENT_PEC; else client->flags &= ~I2C_CLIENT_PEC; return 0;
    //獲取適配器支持的功能:SMbus或者普通的I2C case I2C_FUNCS: funcs = i2c_get_functionality(client->adapter); return put_user(funcs, (unsigned long __user *)arg); //i2c讀寫,和i2c_read、i2c_write的區別在於這兩個函數一次只能處理一條消息,而i2c_RDWR一次可以處理多條消息 case I2C_RDWR: { struct i2c_rdwr_ioctl_data rdwr_arg; struct i2c_msg *rdwr_pa; if (copy_from_user(&rdwr_arg, (struct i2c_rdwr_ioctl_data __user *)arg, sizeof(rdwr_arg))) return -EFAULT; if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0) return -EINVAL; /* * Put an arbitrary limit on the number of messages that can * be sent at once */
        //最多處理42條message if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS) return -EINVAL; rdwr_pa = memdup_user(rdwr_arg.msgs, rdwr_arg.nmsgs * sizeof(struct i2c_msg)); if (IS_ERR(rdwr_pa)) return PTR_ERR(rdwr_pa); return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa); } //smbus協議 case I2C_SMBUS: { struct i2c_smbus_ioctl_data data_arg; if (copy_from_user(&data_arg, (struct i2c_smbus_ioctl_data __user *) arg, sizeof(struct i2c_smbus_ioctl_data))) return -EFAULT; return i2cdev_ioctl_smbus(client, data_arg.read_write, data_arg.command, data_arg.size, data_arg.data); }
    //設置重試次數
    //這句話設置適配器收不到ACK時重試的次數為m。默認的重試次數為1。
case I2C_RETRIES: if (arg > INT_MAX) return -EINVAL; client->adapter->retries = arg; break;
    //超時時間,時間單位為jiffes case I2C_TIMEOUT: if (arg > INT_MAX) return -EINVAL; /* For historical reasons, user-space sets the timeout * value in units of 10 ms. */ client->adapter->timeout = msecs_to_jiffies(arg * 10); break; default: /* NOTE: returning a fault code here could cause trouble * in buggy userspace code. Some old kernel bugs returned * zero in this case, and userspace code might accidentally * have depended on that bug. */ return -ENOTTY; } return 0; }
I2C_RDWR應用實例i2c驅動之調用ioctl函數進行讀寫at24c08

i2c數據傳輸主要通過i2c_transfer函數:

/**
 * i2c_transfer - execute a single or combined I2C message
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 *	terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Note that there is no requirement that each message be sent to
 * the same slave address, although that is the most common model.
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;

	if (!adap->algo->master_xfer) {
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}

	/* REVISIT the fault reporting model here is weak:
	 *
	 *  - When we get an error after receiving N bytes from a slave,
	 *    there is no way to report "N".
	 *
	 *  - When we get a NAK after transmitting N bytes to a slave,
	 *    there is no way to report "N" ... or to let the master
	 *    continue executing the rest of this combined message, if
	 *    that's the appropriate response.
	 *
	 *  - When for example "num" is two and we successfully complete
	 *    the first message but get an error part way through the
	 *    second, it's unclear whether that should be reported as
	 *    one (discarding status on the second message) or errno
	 *    (discarding status on the first one).
	 */
	ret = __i2c_lock_bus_helper(adap);
	if (ret)
		return ret;

	ret = __i2c_transfer(adap, msgs, num);
	i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);

	return ret;
}
(6)退出函數
static void __exit i2c_dev_exit(void)
{
	bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);
	i2c_for_each_dev(NULL, i2cdev_detach_adapter);
	class_destroy(i2c_dev_class);
	unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
}

2、i2c_algorithm

兩種i2c算法,一種是普通的I2c數據通訊協議,一種是SMbus協議,是兩種不同的通信方法

/**
 * struct i2c_algorithm - represent I2C transfer method
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter
 *   defined by the msgs array, with num messages available to transfer via
 *   the adapter specified by adap.
 * @master_xfer_atomic: same as @master_xfer. Yet, only using atomic context
 *   so e.g. PMICs can be accessed very late before shutdown. Optional.
 * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
 *   is not present, then the bus layer will try and convert the SMBus calls
 *   into I2C transfers instead.
 * @smbus_xfer_atomic: same as @smbus_xfer. Yet, only using atomic context
 *   so e.g. PMICs can be accessed very late before shutdown. Optional.
 * @functionality: Return the flags that this algorithm/adapter pair supports
 *   from the I2C_FUNC_* flags.
 * @reg_slave: Register given client to I2C slave mode of this adapter
 * @unreg_slave: Unregister given client from I2C slave mode of this adapter
 *
 * The following structs are for those who like to implement new bus drivers:
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 *
 * The return codes from the @master_xfer{_atomic} fields should indicate the
 * type of error code that occurred during the transfer, as documented in the
 * Kernel Documentation file Documentation/i2c/fault-codes.rst.
 */
struct i2c_algorithm {
	/*
	 * If an adapter algorithm can't do I2C-level access, set master_xfer
	 * to NULL. If an adapter algorithm can do SMBus access, set
	 * smbus_xfer. If set to NULL, the SMBus protocol is simulated
	 * using common I2C messages.
	 *
	 * master_xfer should return the number of messages successfully
	 * processed, or a negative value on error
	 */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*master_xfer_atomic)(struct i2c_adapter *adap,
				   struct i2c_msg *msgs, int num);
	int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
			  unsigned short flags, char read_write,
			  u8 command, int size, union i2c_smbus_data *data);
	int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
				 unsigned short flags, char read_write,
				 u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality)(struct i2c_adapter *adap);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

3、i2c總線驅動分析(i2c-core-base.c)

(1)i2c總線定義
struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};
(2)總線注冊
static int __init i2c_init(void)
{
	int retval;

	retval = of_alias_get_highest_id("i2c");

	down_write(&__i2c_board_lock);
	if (retval >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = retval + 1;
	up_write(&__i2c_board_lock);

	retval = bus_register(&i2c_bus_type);
	if (retval)
		return retval;

	is_registered = true;

#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) {
		retval = -ENOMEM;
		goto bus_err;
	}
#endif
	retval = i2c_add_driver(&dummy_driver);
	if (retval)
		goto class_err;

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
	if (IS_ENABLED(CONFIG_ACPI))
		WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	is_registered = false;
	bus_unregister(&i2c_bus_type);
	return retval;
}
(3)i2c設備和驅動匹配規則
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;


	/* Attempt an OF style match */
	if (i2c_of_match_device(drv->of_match_table, client))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);

	/* Finally an I2C match */
	if (i2c_match_id(driver->id_table, client))
		return 1;

	return 0;
}
  • i2c_of_match_device:設備樹匹配方式

    比較I2C設備節點的compatible屬性和of_device_id中的compatible屬性

  • acpi_driver_match_device: ACPI匹配方式
  • i2c_match_id: i2c總線傳統匹配方式

    比較i2c設備名字和i2c驅動的id_table->name字段是否相等

三、SMbus介紹 

1、介紹

  • 系統管理總線(SMBus)是一個兩線接口。通過它,各設備之間以及設備與系統的其他部分之間可以互相通信。
  • 它基於I2C操作原理。SMBus為系統和電源管理相關的任務提供一條控制總線。一個系統利用SMBus可以和多個設備互傳信息,而不需使用獨立的控制線路。
  • 系統管理總線(SMBus)標准涉及三類設備。從設備-接收或響應命令的設備。主設備-用來發布命令,產生時鍾和終止發送的設備。主機,是一種專用的主設備,它提供與系統CPU的主接口。主機必須具有主-從機功能,並且必須支持SMBus通報協議。
  • 在一個系統里只允許有一個主機

2、SMBus和I2C之間的相似點

  • 2條線的總線協議(1個時鍾,1個數據) + 可選的SMBus提醒線
  • 主-從通信,主設備提供時鍾
  • 多主機功能
  • SMBus數據格式類似於I2C的7位地址格式

3、SMBus和I2C之間的不同點

SMbus i2c
傳輸速度 10khz~100khz 最小傳輸速度 10kHz 無最小傳輸速度 
最小傳輸速度 35ms時鍾低超時 無時鍾超時 
固定的邏輯電平 邏輯電平由VDD決定
不同的地址類型(保留、動態等) 7位、10位和廣播呼叫從地址類型
不同的總線協議(快速命令、處理呼叫等) 無總線協議

 

4、SMBus應用用途

  利用系統管理總線,設備可提供制造商信息,告訴系統它的型號/部件號,保存暫停事件的狀態,報告不同類型的錯誤,接收控制參數,和返回它的狀態。SMBus為系統和電源管理相關的任務提供控制總線。

 

  

  

  

  

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM