I2C驱动框架+I2C设备驱动编写方法

3,471 阅读17分钟

I2C驱动框架

一、主要对象

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,
	.pm		= &i2c_device_pm_ops,
};

I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_prob函数,进而调用I2C驱动的probe函数
特别提示:i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。

2. I2C驱动

struct i2c_driver {
	int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
	struct device_driver driver; //表明这是一个驱动
	const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
	int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
	const unsigned short *address_list; //设备地址
	struct list_head clients; //设备链表
};

3. I2C设备

struct i2c_client {
	unsigned short addr; //设备地址
	char name[I2C_NAME_SIZE]; //设备名称
	struct i2c_adapter *adapter; //适配器,I2C控制器。
	struct i2c_driver *driver; //设备对应的驱动
	struct device dev; //表明这是一个设备
	int irq; //中断号
	struct list_head detected; //节点
};

4. I2C适配器

I2C适配器是什么?

经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C适配器,I2C适配器对应的就是SOC上的I2C控制器。

struct i2c_adapter {    //适配器
	unsigned int id; //适配器的编号
	const struct i2c_algorithm *algo; //算法,发送时序
	struct device dev; //表明这是一个设备
};

I2C算法对应的就是如何发送I2C时序

struct i2c_algorithm {
    /* 作为主设备时的发送函数 */
	int (*master_xfer)(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);
};

小结

I2C驱动有4个重要的东西,I2C总线、I2C驱动、I2C设备、I2C适配器

  • I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等
  • I2C驱动:对应的就是I2C设备的驱动程序
  • I2C设备:是具体硬件设备的一个抽象
  • I2C适配器:用于I2C驱动和I2C设备间的通讯,是SOC上I2C控制器的一个抽象

I2C总线的运行机制 I2C驱动框架可以分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中。

  • I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口
  • I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
  • 当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
  • 在I2C驱动程序中,通过I2C适配器中的算法向I2C硬件设备传输数据。

二、内核源码分析

1. 注册I2C设备

  • 未采用设备树
    注册I2C设备可以通过i2c_new_device,此函数会生成一个i2c_client,指定对应的总线为I2C总线,然后向总线注册设备。
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client	*client;
    client = kzalloc(sizeof *client, GFP_KERNEL);
    
    client->adapter = adap; /* i2c控制器 */
    
    client->addr = info->addr; /* i2c地址 */
    strlcpy(client->name, info->type, sizeof(client->name)); /* i2c's name 用于与驱动匹配 */
        
    client->dev.bus = &i2c_bus_type; /* 指定I2C总线 */
    
    device_register(&client->dev); //向总线注册设备
    return client;
}
//例子
adapter = i2c_get_adapter(0);
static struct i2c_board_info  my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
if (adapter) {
	client = i2c_new_device(adapter, my_i2c_dev_info);
	if (!client)
		pr_err("can't create i2c device %s\n",
			my_i2c_dev_info->type);
}

device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数:

//第一层
int device_register(struct device *dev)
{
    device_add(dev);
}
//第二层
int device_add(struct device *dev)
{
    /* 添加设备到总线的设备链表中 */
    bus_add_device(dev); 
    /* 遍历总线的驱动进行操作 */
    bus_probe_device(dev); 
}
//第三层
int bus_add_device(struct device *dev)
{
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev)
{
    device_attach(dev); 
}
//第四层
int device_attach(struct device *dev)
{
    /* 遍历总线的驱动链表每一项,然后调用__device_attach */
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
//第五层
static int __device_attach(struct device_driver *drv, void *data)
{
    /* 判断设备和驱动是否匹配 */
	if (!driver_match_device(drv, dev)) 
		return 0;
    /* 匹配成功 */
	return driver_probe_device(drv, dev);
}
//第六层
static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
    /* 调用了总线的match函数,这里的总线在注册i2c设备时以及设置为I2C总线了 */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    really_probe(dev, drv);
}

//第七层
struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match, //匹配规则
	.probe		= i2c_device_probe, //匹配后的行为
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
/* 这里调用了i2c_device_match函数
 * i2c_device_match会通过I2C驱动的id_table中每一的name和I2C设备的name进行匹配
 */
 static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    i2c_match_id(driver->id_table, client);
}

static int really_probe(struct device *dev, struct device_driver *drv)
{
    i2c_device_probe(dev)
}
//第八层
 static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0) //字符串匹配
			return id;
		id++;
	}
	return NULL;
}

static int i2c_device_probe(struct device *dev)
{
    /* 调用驱动的probe函数 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}
  • 采用设备树
1. 设备树解析过程
/* kernel会为设备树root节点下所有带'compatible'属性的节点都分配并注册一个platform_device;
 * 另外,如果某节点的'compatible'符合某些matches条件,则会为该节点下所有带'compatible'属性的子节点(child)也分配并注册一个platform_device。
 */
 //数据结构
 struct platform_device {
        const char      *name;
        int             id;
        bool            id_auto;
        /* 以此挂入统一设备模型 */
        struct device   dev;
        u64             platform_dma_mask;
        /* io和irq资源的总数 */
        u32             num_resources;
        /* 指向resource数组 */
        struct resource *resource;

        const struct platform_device_id *id_entry;
};
//第一层  
/* kerner加载 */
start_kernel
--> arch_call_rest_init
    --> rest_init
        --> kernel_init
            --> kernel_init_freeable
                --> do_basic_setup
                    --> do_initcalls
                        --> of_platform_default_populate_init
//第二层
/* drivers/of/platform.c */
static int __init of_platform_default_populate_init(void)
{
        /* 检查of_root("/"节点)是否为NULL */
        if (!of_have_populated_dt())
                return -ENODEV;

        /* 进行实际的platform_device填充操作 */
        of_platform_default_populate(NULL, NULL, NULL);

        return 0;
}
/* 在do_initcalls会被调用执行 */
arch_initcall_sync(of_platform_default_populate_init);
//第三层
const struct of_device_id of_default_bus_match_table[] = {
        { .compatible = "simple-bus", },
        { .compatible = "simple-mfd", },
        { .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
        { .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
        {} /* Empty terminated list */
};
int of_platform_default_populate(struct device_node *root,
                                 const struct of_dev_auxdata *lookup,
                                 struct device *parent)
{
        /* of_default_bus_match_table即为上述的matches条件,其他形参均为NULL */
        return of_platform_populate(root, of_default_bus_match_table, lookup,
                                    parent);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);
//第五层
int of_platform_populate(struct device_node *root,
                        const struct of_device_id *matches,
                        const struct of_dev_auxdata *lookup,
                        struct device *parent)
{
        struct device_node *child;
        int rc = 0;
        /* 传入的root为NULL,获取"/"节点 */
        root = root ? of_node_get(root) : of_find_node_by_path("/");
        /* 遍历"/"节点下所有的child节点 */
        for_each_child_of_node(root, child) {
                /* 分配并创建platform_device */
                rc = of_platform_bus_create(child, matches, lookup, parent, true);
                if (rc) {
                        of_node_put(child);
                        break;
                }
        } 
        /* 设置已填充标志位,避免重复填充 */
        of_node_set_flag(root, OF_POPULATED_BUS);

        of_node_put(root);
        return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
//第六层
/* bus:root下的child节点
 * matches:of_default_bus_match_table
 * lookup:NULL
 * parent:NULL
 * strict:true
 */
static int of_platform_bus_create(struct device_node *bus,
                                  const struct of_device_id *matches,
                                  const struct of_dev_auxdata *lookup,
                                  struct device *parent, bool strict)
{
        const struct of_dev_auxdata *auxdata;
        struct device_node *child;
        struct platform_device *dev;
        const char *bus_id = NULL;
        void *platform_data = NULL;
        int rc = 0;

        /* 只为含"compatible"属性的节点创建platform_device */
        if (strict && (!of_get_property(bus, "compatible", NULL))) {
                return 0;
        }
        /* 跳过符合of_skipped_node_table条件的节点 */
        if (unlikely(of_match_node(of_skipped_node_table, bus))) {
                return 0;
        }
        /* 跳过已经创建过platform_device的节点 */
        if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
                return 0;
        }
        /* 创建并填充platform_device */
        dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
        /* 1、创建platform_device失败,则直接返回,继续遍历root下其他child node
         * 2、创建platform_device成功,但当前node不符合matches条件,即compatible属性值
         *    不为"simple-bus"、"simple-mfd"、"isa"等时,也直接返回;否则继续为当前node
         *    下所有含compatible属性的child node创建并填充platform_device
         */
        if (!dev || !of_match_node(matches, bus))
                return 0;
        /* 遍历当前node下的所有child node */
        for_each_child_of_node(bus, child) {
                /* 递归调用of_platform_bus_create函数 */
                rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
                if (rc) {
                        of_node_put(child);
                        break;
                }
        }
        /* 设置已填充标志位:OF_POPULATED_BUS */
        of_node_set_flag(bus, OF_POPULATED_BUS);
        return rc;
}
//第七层
static struct platform_device *of_platform_device_create_pdata(
                                        struct device_node *np,
                                        const char *bus_id,
                                        void *platform_data,
                                        struct device *parent)
{
        struct platform_device *dev;
        /* of_device_is_available: 检查节点的status属性,如果没有该属性,或者属性值
         *                         为"ok"、"okay",则认为该node是有效的
         */
        if (!of_device_is_available(np) ||
            of_node_test_and_set_flag(np, OF_POPULATED))
                return NULL;
        /* 创建platform_device结构体,并对结构体成员进行赋值:
         *      如dev->dev.of_node = of_node_get(np),即将当前的device_node结构体
         *      赋值给了platform_device->device.of_node成员,即完成绑定操作
         */
        dev = of_device_alloc(np, bus_id, parent);
        if (!dev)
                goto err_clear_flag;

        dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
        if (!dev->dev.dma_mask)
                dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
        /* struct bus_type platform_bus_type = {
         *          .name           = "platform",
         *          .dev_groups     = platform_dev_groups,
         *          .match          = platform_match,
         *          .uevent         = platform_uevent,
         *          .dma_configure  = platform_dma_configure,
         *          .pm             = &platform_dev_pm_ops,
         * };
         * 设置struct device的总线类型,此后与platform_driver的匹配即是通过
         * platform_match函数
         */
        dev->dev.bus = &platform_bus_type;
        dev->dev.platform_data = platform_data;
        of_msi_configure(&dev->dev, dev->dev.of_node);
        /* 调用device_add加入统一设备模型 */
        if (of_device_add(dev) != 0) {
            ...
        }

        return dev;
}

至此,为所有设备树中所有符合条件的node都创建了platform_device结构体,node下描述的资源也解析到了platform_device中,并通过dev成员将该node描述的设备加入了统一设备模型。

在统一设备模型中,每次device或者driver加入bus中,都会调用对应bus的match函数(如platform_match)对driver或者device链表进行遍历,如有匹配项,则喜结连理,进入driver的probe函数。

spi、i2c等真实物理总线的控制器设备(cotroller)是作为platform_device挂入了platform bus的,所以在spi master或者i2c adapter等driver注册到platform bus时,会与device进行配对并进入drvier的probe函数,在probe函数中会对控制器节点下的子节点(即spi、i2c从设备)进行解析,创建对应的spi_device、i2c_client等结构体,最终挂入对应的spi、i2c总线。

这样,设备树中描述的所有设备都有了对应的xxx_device,并加入了统一设备模型中。

2. i2c从设备节点创建过程
/* 首先找到i2c控制器(adaper)驱动,他是与设备树通过compatible匹配 */
static const struct of_device_id i2c_imx_dt_ids[] = {
	{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
	{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
	{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
	{ .compatible = "fsl,imx7d-i2c", .data = &imx7d_i2c_hwdata, },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);

static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,
	.remove = i2c_imx_remove,
	.driver = {
		.name = DRIVER_NAME,
		.pm = I2C_IMX_PM_OPS,
		.of_match_table = i2c_imx_dt_ids,
	},
	.id_table = imx_i2c_devtype,
};

匹配后进入i2c_imx_probe(struct platform_device *pdev)函数

//第一层
i2c_imx_probe //i2c-imx.c
--> i2c_add_numbered_adapter //i2c-core-base.c
    --> __i2c_add_numbered_adapter //i2c-core-base.c
        --> i2c_register_adapter //i2c-core-base.c
            -->of_i2c_register_devices //i2c-core-of.c
//第二层 i2c-core-of.c
void of_i2c_register_devices(struct i2c_adapter *adap)
{
	struct device_node *bus, *node;
	struct i2c_client *client;
	/* 获取总线 */
	bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
	if (!bus)
		bus = of_node_get(adap->dev.of_node);
		
    /* 遍历有效的子节点 */
    for_each_available_child_of_node(bus, node) { 
            /* 如已被填充则跳过 */
            if (of_node_test_and_set_flag(nc, OF_POPULATED))
                    continue;
            client = of_i2c_register_device(adap, node);
    }
}
//第三层 of_i2c_register_device
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
						 struct device_node *node)
{
	struct i2c_board_info info = {};
	const __be32 *addr_be;
	u32 addr;
	/* 获取I2C从设备地址 */
	addr_be = of_get_property(node, "reg", &len);
	addr = be32_to_cpup(addr_be); 
	/* 填充info */
	/* 填充info.type */
	if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
		dev_err(&adap->dev, "of_i2c: modalias failure on %pOF\n",
			node);
		return ERR_PTR(-EINVAL);
	}
	/* 填充info.addr */
	info.addr = addr;
	/* 如上注册 */
	result = i2c_new_device(adap, &info);
	
}

2. 注册I2C驱动

与注册设备驱动过程基本一致

//第一层
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    driver->driver.bus = &i2c_bus_type; //指定I2C总线
    
    driver_register(&driver->driver); //向总线注册驱动
}
//第二层
int driver_register(struct device_driver *drv)
{
    bus_add_driver(drv);
}
//第三层
int bus_add_driver(struct device_driver *drv)
{
    driver_attach(drv); //此函数会遍历总线设备链表进行操作
    
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加进bus的driver链表中
}
//第四层
int driver_attach(struct device_driver *drv)
{
    /* 遍历总线的设备链表,调用__driver_attach */
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//第五层
static int __driver_attach(struct device *dev, void *data)
{
	if (!driver_match_device(drv, dev))
		return 0;
    
    driver_probe_device(drv, dev);
}

3. I2C适配器构建及其驱动介绍

I2C适配器驱动就是SOC的I2C控制器驱动,主要是由SOC厂商去编写,我们不用过分注意细节。内部两个重要的数据结构i2c_adapter和 i2c_algorithm

//第一层
struct i2c_adapter{
    const struct i2c_algorithm *algo; /* 总线访问算法 */
}
/* i2c_algorithm 就是I2C适配器与IIC设备进行通信的方法。*/
//第二层
struct i2c_algorithm {
    ......
    /* I2C适配器的传输函数,此函数完成与IIC设备的通信 */
    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);
    
    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
    ......
};
/* 实例-构建适配器 */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    i2c->adap.algo    = &s3c24xx_i2c_algorithm; //构建了算法
    i2c_add_numbered_adapter(&i2c->adap); //注册了适配器
}

4. I2C数据传输

上面介绍I2C数据传输是通过I2C适配器完成的,下面来分析一下源码在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数肯定是通过I2C适配器的算法进行操作的,如下

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    adap->algo->master_xfer(adap, msgs, num); //调用适配器的算法
}

设备驱动编写方法

i2c设备驱动重点关注两个数据结构i2c_client 和 i2c_driver,前者是描述设备信息的,后者是描述驱动的。

一、注册设备

1. 设置I2C设备驱动信息

  • 匹配ID方式
static const struct i2c_device_id my_i2c_dev_id[] = {
	{ "my_i2c_dev", 0},  /* 设备名字 */
	{ }
};
 
static struct i2c_driver my_i2c_drv = {
	.driver = {
		.name	= "no", /* 这个名字不重要 */
        .owner = THIS_MODULE,
	},
	.probe		= my_i2c_drv_probe, /* 当匹配到i2c设备时调用 */
	.remove		= my_i2c_drv_remove, /* 当卸载i2c设备或驱动时调用 */
	.id_table	= my_i2c_dev_id, /* 这个结构体中的名字很重要 */
};

其中my_i2c_dev非常的重要,因为这个名字就是用来和设备进行匹配的名字。

  • 设备树匹配方式
/* 设备树匹配列表 */
static const struct of_device_id my_i2c_dev_of_match[] = {
	{ .compatible = "my_i2c_dev, 0" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver my_i2c_drv = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "no",
		   	.of_match_table = my_i2c_dev_of_match,
		   },
};

2. 注册i2c设备驱动

static int __init my_i2c_drv_init(void)
{
 
	i2c_add_driver(&my_i2c_drv);
 
	return 0;
}

3.注册i2c设备

  • 匹配id方式
    /* 静态注册-只能在内核启动时就进行i2c设备注册 */
  1. 定义一个i2c_board_info对象
static struct i2c_board_info  my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
  1. 通过i2c_register_board_info注册
/*
 * busnum:哪一条总线,也就是选择哪一个i2c控制器
 * info:i2c设备信息数组
 * n:数组有几项
 */
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n);
i2c_register_board_info(0, my_i2c_dev_info, ARRAY_SIZE(my_i2c_dev_info));

/* 动态注册可以在内核运行期间注册,也就是可以应用 在加载驱动模块中 */

  1. 定义一个i2c_board_info对象
static struct i2c_board_info  my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
  1. 通过i2c_new_device来动态注册
/*
 * adap:指定i2c设备器,以后访问设备的时候,使用过哪一个设备器(i2c主机控制器)去访问
 * info:指定i2c设备信息
 */
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

  • 使用设备树时
/* 在i2c节点下添加设备信息 */
&i2c1 {
    my_i2c_dev@20 {
        compatible = "my_i2c_dev,0"
    } 
}

二、数据传输函数介绍

1. 传输函数

/*
 * adap:i2c适配器
 * msgs:消息数据
 * num:数组的个数
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

2. 传输报文msg的组成

struct i2c_msg {
	__u16 addr;	//从设备地址
	__u16 flags; //读或写
	__u16 len;	//消息的长度
	__u8 *buf;	//消息
};
  • 例子

/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[2]; 
 
char val[10]
 
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = 0x80; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 1; /* 想要传输的字节数 */
 
/* 填充msg */
msg[1].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[1].flags = 1; /* 1表示读 */
msg[1].buf = val; /* 读到的数据存在这里 */
msg[1].len = 4; /* 想要读取的字节数 */
 
 
/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 2); /* 有两个msg */

项目实战FAXS21002

一、三轴陀螺仪FAXS21002要点

FXAS21002C是一种小型、低功率、偏航、俯仰和滚角速率陀螺仪,具有16位ADC分辨率。全尺寸范围从±250°/s到±2000°/s可调。它具有I2C和SPI接口。

1. I2C时序图

ST:start signal;
Device Address:芯片地址根据硬件连接0x20或者0x21;
Register Address:寄存器地址; SR:repeat start signal;
ACK:acknowledge;
NACK:not acknowledge;
SP:stop;
R/W:read(1)/write(0)

二、运行模式转换

  • 当CTRL_REG3[EXTCTRLEN]=0时,工作模式由CTRL_REG1[ACTIVE]位来决定。
  • 当CTRL_REG3[EXTCTRLEN]=1时,工作模式由外部引脚来决定。

三、寄存器描述

1. 寄存器图表

addrnamedescription
0x0STATUS
0x01-0x06OUT_X_MSB OUT_X_LSB OUT_Y_MSB OUT_Y_LSB OUT_Z_MSB OUT_Z_LSB以16位二进制补码存储角度率的寄存器(大端模式);注:为了防止丢失数据,用户需要一次读取所有寄存器内的数据(6个子节-一个寄存器一个子节)
0x07DR_STATUSOUT类寄存器的数据状态,如数据是否更新。
0x08F_STATUSFIFO的状态(溢出、水印、样本数)
0x09F_SETUP配置FIFO
0x0AF_EVENT监视FIFIO事件状态
0x0BINT_SOURCE_FLAG记录中断产生源(boot,event,threshold,ready)
0x0CWHO_AM_I产商
0x0DCTRL_REG0设备的配置(只能在standby和ready模式设置),如带宽、SPI接口模式(3 or 4线)、高通滤波器截止频率、高通滤波使能、量程选择--灵敏度)
0x0ERT_CFGRT_CFG寄存器用于启用速率阈值中断生成
0x0FRT_SRC指示阈值事件源
0x10RT_THS为检测速率设置阈值和设置Debouce计数器模式
0x11RT_COUNT设置退出计数的数量
0x12TEMP温度
0x13CTRL_REG1CTRL_REG1寄存器用于配置设备ODR(输出数据速率)、设置操作模式、对设备进行软重置以及执行自测功能。
0x14CTRL_REG2这个寄存器为设备上可用的各种中断源启用和分配输出引脚和逻辑极性。
0x15CTRL_REG3这个寄存器用于启用FSR扩展、外部电源控制输入,以及在执行FIFO数据的突发读取时修改自动增量读取地址指针行为的选项。

2. 主要配置寄存器(只能在Standby和ready模式进行配置)

(1) 0x0D CTRL_REG0

配置低通和高通的截止频率、SPI接口模式(3线或者4线)、配置量程。 。

(2) 0x13 CTRL_REG1

配置工作模式、数据转换速率、自检、软复位。

五、在文件系统中的体现

通过sys中的总线查看信息

cd /sys/bus/i2c 

devices: 表示注册了那些设备,其中i2c-x是i2c总线,x是id,根据其在设备树i2c总线下的节点顺序而定。至于x-addr,则是i2cx下的子节点,后面跟的是地址。

六、调试经历

1. printk打印等级

cat /proc/sys/kernel/printk

上面的命令可以查看当前的打印等级,出现4 4 1 7分别表示:
控制台日志级别:优先级高于该值的消息将被打印至控制台;
默认的消息日志级别:将用该优先级来打印没有优先级的消息;
最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);
默认的控制台日志级别:控制台日志级别的缺省值;
其中第一个就是终端不显示打印信息的原因。未指定日志级别的printk() 采用的默认级别是DEFAULT_MESSAGE_LOGLEVEL,这个宏在kernel/printk.c 中被定义为整数4,即对应KERN_WARNING。
解决方案

echo 7 > /proc/sys/kernel/printk

2. i2c调试神器i2c-tools

(1)安装
sudo apt-install i2c-tools
(2)命令
i2cdetect -l #检测有几组i2c总线在系统上
i2cdetect -r -y 1 #检测i2c-1上的挂载情况
i2cdump -f -y 1 0x20 #i2cdump查看地址位0x20器件所有寄存器的值,这个很有用
i2cset -f -y 1 0x20 0x01 0x3f (设置i2c-1上0x20器件的0x01寄存器值为0x3f)
i2cget -f -y 1 0x20 0x01 (读取i2c-1上0x20器件的0x01寄存器值)

3. 利用vscode远程调试APP

(1) 给vscode安装插件“Remote Development”
(2) ctrl+shift+p,输入调试,打开launch.json
(3) 主要修改name(调试项目名)、program(可执行文件路径)、cwd(程序源码路径)、miDebuggerPath(交叉编译器路径)、miDebuggerServerAddress(开发板ip)
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "pintest",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/test/pintest",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}/test",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],

            "miDebuggerPath": "/usr/local/arm64/gcc-arm-8.2-2019.01-x86_64-aarch64-linux-gnu/bin/aarch64-linux-gnu-gdb",
            "miDebuggerServerAddress": "192.168.2.88:2001"
        }
    ]
}
(4) 编译文件(加-g选项)并把编译好的APP发送到开发板
(5) 在开发板端输入gdbserver 192.168.2.22:2001 pintest
(6) vscode启动调试(F5)

七、开机加载模块

模块名:fxas21002.ko

1. 添加需开机自启动加载的驱动模块:

打开vim /etc/modules添加对应模块名称: fxas21002(注意不要ko后缀)

2. 拷贝驱动模块到对应的目录下

将模块放到对应的位置,注意将驱动按类别放,因fxas21002属于i2c设备,因此就将其放在了drivers下的i2c下。具体目录如下:/lib/modules/4.14.159/kernel/drivers/i2c/(内核名称因系统不同而不同,可通过uname -a查询)

八、直接将驱动编译进内核

linux内核将新驱动添加到menuconfig菜单

项目实战LKT4304