I2C設備驅動書寫框架

前言

在正式介紹I2C驅動框架之前,我們先了解一些基礎知識。內核有兩種i2c驅動程序的編寫方式。分別稱這兩種方式爲“Adapter方式(LEGACY)”和“Probe方式(new style)”。但是legacy的方式已經過時了,較新的內核版本已經無法編譯通過,之前也寫過legacy方式的驅動,可以參考下面的連接:I2C驅動程序。本文是介紹new style的編寫方式。

正文

1、struct i2c_driver

static const struct i2c_device_id stk3420_ids[] = {
    {IR_STK_DEV_BASENAME, 0},
    {/*end of list*/}
};

MODULE_DEVICE_TABLE(i2c, stk3420_ids); /* 第一個參數和xxx_device_id相關 */

static struct of_device_id stk3420_dt_ids[] = {
    { .compatible = "sensortek,stk3420" },
    { }
};

static struct i2c_driver stk3420_i2c_driver = {
    .driver = {
        .name  = STK3420_DRV_NAME,
#ifdef CONFIG_PM
        .pm    = &stk3420_i2c_pm,
#endif
        .of_match_table = of_match_ptr(stk3420_dt_ids),
    },
    .probe      = stk3420_i2c_probe,
    .remove     = stk3420_i2c_remove,
    .id_table   = stk3420_ids,
};

首先定義一個i2c_driver結構體,包含幾個重要的點:
(1).driver
1).name:定義了這個驅動的名稱,在驅動註冊進總線的階段會用到,如果有同名的驅動就不會重複註冊了
2).pm:電源管理相關的,主要定義了suspend和resume函數,後面我們再詳細介紹
3).of_match_table:用於匹配對應的device,也就是匹配dts中的節點

(2).probe
如果找到匹配的device,就會調用probe函數了

(3).remove
卸載驅動後會調用的函數

(4).id_table
在匹配device時,首先會通過of_match_table來檢測,如果不匹配或者不存在of_match_table的時候,纔會通過id_table來匹配,對應的流程如下,通過調用bus的match函數來匹配:
 

代碼路徑:drivers\i2c\i2c-core.c
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv)) /* 通過of_match_table來匹配device */
		return 1;

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

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table) /* 通過id_table來匹配device */
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

 2、i2c_add_driver

static int __init stk3420_init(void)
{
    return i2c_add_driver(&stk3420_i2c_driver);
}

定義完struct i2c_driver結構體後,調用i2c_add_driver函數將其註冊到I2C總線。i2c_add_driver函數其實也是driver_register的封裝,和其他的函數比如platform_driver_register是類似的,這裏就不展開了。

3、stk3420_i2c_probe

找到匹配的device後,就會調用driver的probe函數了。

static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, stk3420_show_enable, stk3420_store_enable); /* 省略stk3420_show_enable和stk3420_store_enable函數 */
static struct device_attribute *stk3420_attr_list[] = {
    &dev_attr_enable,
};

static int stk3420_i2c_probe(struct i2c_client *client, const struct i2c_device_id *did)
{
	struct stk3420_dev_data *dev_data;
	dev_data = kzalloc(sizeof(struct stk3420_dev_data), GFP_KERNEL);
    if (dev_data == NULL) {
        pr_err("Failed to malloc stk3420_dev_data\n");
        return -ENOMEM;
    }

	/*
	 * 在probe函數中具體做什麼,一般看具體實現什麼功能,這裏我給出一些大部分驅動都要實現的步驟
	 */
	 
	/*
	 * 1、解析dts文件中的節點 
	 */
	struct device_node *np = client->dev.of_node;
	if (!np) {
        dev_err(&client->dev, "no device tree\n");
        return -EINVAL;
    }
	gpio = of_get_named_gpio_flags(np, "power-gpio", 0, &pwr_flags);
    if (gpio_is_valid(gpio)) {
        dev_data->pwr_pin = of_get_named_gpio_flags(np, "power-gpio", 0, &pwr_flags);
        dev_data->pwr_en_level = (pwr_flags == GPIO_ACTIVE_HIGH) ? 1 : 0;
    } else {
        dev_data->pwr_pin = -1;
    }
	 
	  
	/*
	 * 2、一般都需要創建一些節點,方便應用層去操作驅動,那麼我們就需要在/sys/class/下創建相關節點
	 */
	struct class *dev_class;
    struct device *ctl_dev;
	dev_class = class_create(THIS_MODULE, "gesture");
    ctl_dev = device_create(dev_class, NULL, 0, NULL, "control");
    if (IS_ERR(ctl_dev)) {
        dev_err(ctl_dev, "Failed to create bt char device\n");
        ret = PTR_ERR(ctl_dev);
        goto err_create_dev;
    }
    device_create_file(ctl_dev, stk3420_attr_list[0]); /* 這步之後就創建了/sys/class/gesture/control/enable */
	
	/*
	 * 3、有可能需要對I2C設備做一些初始化的動作,這時候就會涉及到和I2C從設備的數據讀寫操作
	 */
	
	/* 3.1、讀操作,讀取地址STK3420_STATE_REG的值到buf中 */
	u8 buf[1];
	stk3420_reg_read(client, STK3420_STATE_REG, buf);
	
	/* 3.2、寫操作,將0x00寫到寄存器STK3420_STATE_REG中 */
	stk3420_reg_write(client, STK3420_STATE_REG, 0x00);
	
	/*
	 * 其他的操作就根據自己需要的功能進行定製了,比如設置中斷函數等等
	 */

}

(1)I2C設備具體的讀寫函數

static int stk3420_reg_read(const struct i2c_client *client, u8 addr, u8 *val)
{
    int ret;
    struct i2c_msg msg[2];
	
	/*數據傳輸3要素:源,目的,長度*/

	/*在讀取從設備的數據前,需要將存儲數據的空間的地址告訴它*/
    msg[0].addr   = client->addr; /* 目的 */
    msg[0].flags  = client->flags & I2C_M_TEN; /* I2C_M_TEN:this is a ten bit chip address */
    msg[0].len    = 1;  /* 地址 = 1bytes */
    msg[0].buf    = &addr;  /* 源 */

	/*然後啓動讀操作*/
    msg[1].addr   = client->addr; /* 源 */
    msg[1].flags  = client->flags & I2C_M_TEN;
    msg[1].flags |= I2C_M_RD; /* 讀操作 */
    msg[1].len    = 1; /* 讀取的字節數,實際就是val的大小 */
    msg[1].buf    = val;  /* 存儲讀取到的數據 */

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret < 0)
        dev_err(&client->dev, "%s:i2c read error\n", __func__);
    return ret;
}

static int stk3420_reg_write(const struct i2c_client *client,
                             u8 addr, u8 val)
{
    int ret;
    struct i2c_msg msg;
    u8 send[2];

    send[0]   = addr;
    send[1]   = val;

	/*數據傳輸3要素:源,目的,長度*/
    msg.addr  = client->addr;  /* 目的 */
    msg.flags = client->flags & I2C_M_TEN;
    msg.len   = 2; /* 地址+數據 = 2bytes,也就是send數組的大小了,如果想傳送更多的數據,可以數組的大小可以增加 */
    msg.buf   = send;  /* 源 */

    ret = i2c_transfer(client->adapter, &msg, 1);
    if (ret < 0)
        dev_err(&client->dev, "%s:i2c write error\n", __func__);

    return ret;
}

4、stk3420_i2c_remove

static int stk3420_i2c_remove(struct i2c_client *client)
{
	/*
	 * 銷燬函數就根據probe函數中調用了什麼,這裏就銷燬什麼,比如我調用了kzalloc,這裏就需要kfree
	 */
    struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);
    kfree(dev_data);

    return 0;
}

5、stk3420_i2c_pm

電源管理相關的函數,主要是在suspend和resume的時候一些相關的操作

int stk3420_suspend(struct device *dev)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);

    /* 自定義一些需要在suspend時候的操作 */

    return 0;
}

int stk3420_resume(struct device *dev)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);
	
	/* 自定義一些需要在resume時候的操作 */

    return 0;
}

static SIMPLE_DEV_PM_OPS(stk3420_i2c_pm, stk3420_suspend, stk3420_resume);

結語

整個框架比較粗糙,省略了許多細節,但是能大概瞭解I2C設備驅動的框架,再讀一份完整的驅動代碼時也能有思路。而且展示了I2C設備的讀寫函數的含義,這裏對於數據的傳輸通過i2c_transfer函數,也就是master_xfer的方式。在一些驅動中我們還會看到通過i2c_smbus_read_byte_data函數來傳輸數據的,也就是smbus_xfer的方式。這是目前適配器主要支持兩種傳輸方法:smbus_xfer和master_xfer。一般來說,如果設配器支持了master_xfer那麼它也可以模擬支持smbus的傳輸。但如果只實現smbus_xfer,則不支持一些i2c的傳輸。後面我再出一篇文章講解通過smbus_xfer方式的驅動框架。

參考連接:
https://www.cnblogs.com/simonshi/archive/2011/02/24/1963426.html

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