Linux驅動之I2C驅動架構

一、Linux的I2C體系結構

主要由三部分組成:
(1) I2C核心
提供I2C控制器和設備驅動的註冊和註銷方法,I2C通信方法,與適配器無關的代碼以及探測設備等。
(2) I2C控制器驅動(適配器)
(3) I2C設備驅動
在這裏插入圖片描述

二、重要的結構體

  • i2c_adapter
//i2c控制器(適配器)
struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* 總線通信結構體指針 */
	void *algo_data; //algorithm數據

	/* data fields that are valid for all devices	*/
    //併發同步,互斥鎖
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;			/* in jiffies */
	int retries; //重試次數
	struct device dev;		/* the adapter device */

	int nr;
	char name[48]; //適配器名稱
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients; //client鏈表

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;

	struct irq_domain *host_notify_domain;
};
  • i2c_algorithm
//I2C傳輸方法
struct i2c_algorithm {
    //i2c傳輸函數指針
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
    //smbus傳輸函數指針  
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	//返回適配器支持的功能
	u32 (*functionality) (struct i2c_adapter *);
 
//作爲從機時使用
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

SMBus是基於I2C總線規範的,所以上面的傳輸函數要根據自己的總線來選擇,選擇其一就可以。

  • i2c_driver
//I2C驅動,和platform_driver,spi_driver類似
struct i2c_driver {
	unsigned int class;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	int (*probe_new)(struct i2c_client *);
	void (*shutdown)(struct i2c_client *);
	void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
		      unsigned int data);
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table; //該設備所支持的設備ID表

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients; //client鏈表

	bool disable_i2c_core_irq_mapping;
};
  • i2c_client
//I2C設備
struct i2c_client {
	unsigned short flags;		//標誌
	unsigned short addr;		//芯片地址,保存在addr低7位
	char name[I2C_NAME_SIZE];       //設備名稱
	struct i2c_adapter *adapter;	//依附的i2c_adapter
	struct device dev;		//設備結構體
	int irq;			//設備使用的中斷號
	struct list_head detected;      //client鏈表,和i2c_driver中clients的一樣
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	//從機模式回調
#endif
};
  • i2c_msg
//I2C傳輸數據結構體,代表一個消息數據
struct i2c_msg {
	__u16 addr;	//設備地址
	__u16 flags;    //標誌
#define I2C_M_RD		0x0001	/* read data, from slave to master */
					/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_DMA_SAFE	0x0200	/* the buffer of this message is DMA safe */
					/* makes only sense in kernelspace */
					/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN	0x0400	/* length will be first received byte */
#define I2C_M_NO_RD_ACK	0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART	0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;		//消息長度
	__u8 *buf;		//消息數據
};

總結上面結構體關係:

1. i2c_adapter和i2c_algorithm
    i2c_adapter對應物理上的一個適配器,而i2c_algorithm對應一套通信方法,適配器需要通過i2c_algorithm提供的通信函數來產生對應的訪問時序。所以i2c_adapter中包含i2c_algorithm的指針。
    i2c_algorithm使用master_xfer()來產生I2C時序,以i2c_msg爲單位,i2c_msg代表一次傳輸的數據。

2. i2c_driver和i2c_client
    i2c_driver對應一套驅動方法,包含probe,remove等方法。i2c_clent對應真實的物理設備,每個i2c設備都需要一個i2c_client來描述。i2c_driver與i2c_client是一對多的關係,一個i2c_driver上可以支持多個同類型的i2c_client。

3. i2c_adapter和i2c_client
    i2c_adapter與i2c_client的關係和硬件上適配器與設備的關係一致,即i2c_client依附於i2c_adapter。一個適配器可以連接多個設備,所以i2c_adapter中包含i2c_client的鏈表。

三、API函數

//增加/刪除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
void i2c_del_adapter(struct i2c_adapter *adap)

//增加/刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)
 
//i2c傳輸、發送和接收
//完成I2C總線和I2C設備之間的一定數目的I2C message交互
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
//通過封裝i2c_transfer()完成一次I2c發送操作
int i2c_master_send(const struct i2c_client *client,
				  const char *buf, int count) 
//通過封裝i2c_transfer()完成一次I2c接收操作      
int i2c_master_recv(const struct i2c_client *client,
				  char *buf, int count) 

i2c_transfer()函數本身不具備驅動適配器物理硬件以完成消息交互的能力,它只是尋找到與i2c_adapter對應的i2c_algorithm, 並使用i2c_algorithm的master_xfer()函數真正驅動硬件流程。

追蹤i2c_transfer()的源碼會發現下面的代碼

for (ret = 0, try = 0; try <= adap->retries; try++) {
    ret = adap->algo->master_xfer(adap, msgs, num); //真正發送的函數
    if (ret != -EAGAIN)
    break;
    if (time_after(jiffies, orig_jiffies + adap->timeout))
    break;
}

四、適配器(控制器)驅動

由於I2C控制器通常是在內存上的,所以它本身也連接在platform總線上的,通過platform_driver和platform_device的匹配還執行。
(1) probe()完成如下工作:

  • 初始化I2C控制器所使用的硬件資源,如申請IO地址,中斷號,時鐘等。
  • 爲特定I2C控制器實現通信方法,主要是實現i2c_algorithm的master_xfer()和functionality()函數。
  • 通過i2c_add_adapter()添加i2c_adapter的數據結構(i2c_adapter成員已被初始化)。

模板代碼:

static const struct i2c_algorithm xxx_i2c_algo = {
    .master_xfer    = xxx_i2c_master_xfer,
    .functionality    = xxx_i2c_func,
};

static u32 xxx_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
        (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
        I2C_FUNC_SMBUS_BLOCK_DATA;
}

static int xxx_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
                int num)
{
    int ret, ;
    u32 reg;
    struct xxx_i2c *id = adap->algo_data;

    /* Process the msg one by one */
    for (i = 0; i < num; i++, msgs++) {
        i2c_adapter_xxx_start(); /*產生開始位*/
        /*是讀消息*/
        if (msgs[i]->flags &I2C_M_RD)
        {
            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*發送從設備讀地址*/
            i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
            i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]->len長的數據到msgs[i]->buf*/
        }
        else/*是寫消息*/
        {
            i2c_adapter_xxx_setaddr(msg->addr << 1); /*發送從設備寫地址*/
            i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
            i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]->len長的數據到msgs[i]->buf*/
        }
    }
    i2c_adapter_xxx_stop(); /*產生停止位*/
    return num;
}

static int xxx_i2c_probe(struct platform_device *pdev)  // dts裏的設備信息傳遞進來了
{
    struct resource *r_mem;
    struct xxx_i2c *id;
    int ret;

    id = devm_kzalloc(&pdev->dev, sizeof(*id), GFP_KERNEL);
    if (!id)
        return -ENOMEM;
    platform_set_drvdata(pdev, id);

   xxx_adapter_hw_init();    //通常初始化iic適配器使用的硬件資源,如申請IO地址、中斷號、時鐘等
    id->adap.dev.of_node = pdev->dev.of_node;
    id->adap.algo = &xxx_i2c_algo;  // 把altorithm連進來
    id->adap.timeout = XXX_I2C_TIMEOUT;
    id->adap.retries = 3;        /* Default retry value. */
    id->adap.algo_data = id;
    id->adap.dev.parent = &pdev->dev;

    ret = i2c_add_adapter(&id->adap);
    ...
}

static int xxx_i2c_remove(struct platform_device *pdev)
{
    struct xxx_i2c *id = platform_get_drvdata(pdev);

    i2c_del_adapter(&id->adap);
    xxx_adapter_hw_free();            // 硬件相關資源的free

    return 0;
}

static const struct of_device_id xxx_i2c_of_match[] = {
    { .compatible = "cdns,i2c-r1p10", },          
    { /* end of table */ }
};
MODULE_DEVICE_TABLE(of, xxx_i2c_of_match);

static struct platform_driver xxx_i2c_drv = {
    .driver = {
        .name  = DRIVER_NAME,
        .owner = THIS_MODULE,
        .of_match_table = xxx_i2c_of_match,    // dts匹配的依據
        .pm = &xxx_i2c_dev_pm_ops,
    },
    .probe  = xxx_i2c_probe,
    .remove = xxx_i2c_remove,
};

module_platform_driver(xxx_i2c_drv);

xxx_adapter_hw_init實現和具體的CPU和I2C控制器硬件相關的初始化。
functionality() 函數比較簡單,返回支持的通信協議。
master_xfer() 函數在適配器上完成i2c_msg的數據傳輸。

五、設備(外設)驅動

i2c_dirver就是i2c標準總線設備驅動模型中的驅動部分,i2c_client可理解爲i2c總線上掛的外設。

模板代碼:

static struct i2c_driver xxx_driver = {
	.driver = {
		.name = "xxx",
		.of_match_table = xxx_of_match,
		.acpi_match_table = ACPI_PTR(xxx_acpi_ids),
	},
	.probe_new = xxx_probe,
	.remove = xxx_remove,
	.id_table = xxx_ids,
};

static int __init xxx_init(void)
{
    .......
    return i2c_add_driver(&xxx_driver);  // 匹配後,driver中的probe就能執行
}
static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);
}
module_exit(xxx_exit);
module_init(xxx_init);
歡迎大家關注我的微信公衆號!!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章