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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值