linux I2C设备驱动

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42462202/article/details/85538037

linux I2C驱动

Linux i2c_adapter、i2c_driver、i2c_client三个结构体关系

linux I2C设备驱动

 

linux内核的总线-设备-驱动模型,当总线上的设备与总线上的驱动匹配时,就会调用驱动的probe函数,完成一系列的操作

I2C也是内核的一种总线

一、I2C设备的4种构建方法

1、静态注册设备

(1)定义一个 i2c_board_info 结构体,有名字,和设备地址

static struct i2c_board_info  my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x2d),
};

(2)注册设备

i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n)

* busnum:哪一条总线,也就是选择哪一个i2c控制器
* info:i2c设备信息
* n:info中有几个设备

i2c_register_board_info做了什么?

i2c_register_board_info(int busnum,	struct i2c_board_info const *info, unsigned len)
{
    devinfo->busnum = busnum; /* 选择i2c总线 */
    devinfo->board_info = *info; /* 绑定设备信息 */
    list_add_tail(&devinfo->list, &__i2c_board_list); /* 将设备信息添加进链表中 */
}

在哪里使用了 __i2c_board_list 这个链表?

i2c_scan_static_board_info(struct i2c_adapter *adapter)
    list_for_each_entry(devinfo, &__i2c_board_list, list) /* 遍历i2c设备链表 */
        i2c_new_device(adapter,	&devinfo->board_info); /* 创建一个i2c_client设备 */
            struct i2c_client	*client;
            

在哪里调用了 i2c_scan_static_board_info 这个函数?

int i2c_register_adapter(struct i2c_adapter *adap)
{
    i2c_scan_static_board_info(adap);
}

adapter是i2c设备器,也就是i2c主机控制器,在启动内核时已经被注册了

调用关系如下

内核启动
    int i2c_register_adapter(struct i2c_adapter *adap)
        i2c_scan_static_board_info(adap)
            list_for_each_entry(devinfo, &__i2c_board_list, list)

所以使用这个方法添加i2c设备,每次都要重新编译内核,无法动态注册i2c设备,不适合我们动态加载insmod

2、直接i2c_new_device,i2c_new_probe_device

i2c_new_device:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

* adap:指定i2c设备器,以后访问设备的时候,使用过哪一个设备器(i2c主机控制器)去访问
* info:指定i2c设备信息

i2c_new_device注册i2c设备的代码 

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>

static struct i2c_board_info my_i2c_dev_info = {
    I2C_BOARD_INFO("my_i2c_dev", 0x50), /* 这个名字和地址很重要 */
};

static struct i2c_client *my_i2c_client;

static int i2c_dev_init(void)
{
    struct i2c_adapter *i2c_adap;

    i2c_adap = i2c_get_adapter(0); /* 得到i2c设备器,i2c设备在哪条总线上 */
    my_i2c_client = i2c_new_device(i2c_adap, &my_i2c_dev_info);
    i2c_put_adapter(i2c_adap);

    return 0;
}

static void i2c_dev_exit(void)
{ 
    i2c_unregister_device(my_i2c_client);
}

module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
MODULE_LICENSE("GPL");

i2c_new_device与i2c_new_probed_device的区别

i2c_new_device:默认设备已经存在

i2c_new_probed_device:对于能识别的设备才会去创建设备

i2c_new_probed_device:

struct i2c_client *
i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr))

* adap:设配器,设备在哪一条总线上
* info:设备的名字信息
* addr_list:设备的地址列表
* probe:会调用这个函数去判断设备是否真正存在,如果不提供此函数的话,有默认的函数
i2c_new_probed_device
{
    /* 检测每一个地址列表中的每一个地址 */
    for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
        /* Test address responsiveness */
        if (probe(adap, addr_list[i])) /* 测试每个地址是否有回应 */
            break;
    }
    
    /* 只有地址存在才来创建 */
    return i2c_new_device(adap, info); /* 注册i2c client设备 */
}
    

i2c_new_probe_device注册i2c设备的代码 

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

static struct i2c_client *my_i2c_client;

static const unsigned short addr_list[] = { 0x55, 0x50, I2C_CLIENT_END };

static int my_i2c_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info my_i2c_dev_info;

	memset(&my_i2c_dev_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(my_i2c_dev_info.type, "my_i2c_dev", I2C_NAME_SIZE);

	i2c_adap = i2c_get_adapter(0);
	my_i2c_client = i2c_new_probed_device(i2c_adap, &my_i2c_dev_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);

	if (my_i2c_client )
		return 0;
	else
		return -ENODEV;
}

static void my_i2c_dev_exit(void)
{
	i2c_unregister_device(my_i2c_client );
}


module_init(my_i2c_dev_init);
module_exit(my_i2c_dev_exit);
MODULE_LICENSE("GPL");

3、从用户空间创建设备

创建设备

echo my_i2c_dev 0x55 > sys/devices/platform/s3c2440-i2c.0/i2c-0/new_device

会导致i2c_new_device被调用

删除设备

echo 0x55 > sys/devices/platform/s3c2440-i2c.0/i2c-0/delete_device

导致i2c_unregister_dev被调用

4、上面三种方法,都需要指定设配器(i2c主机控制器),现在如果不知道设备在哪个设配器上要怎么办呢?因此就有了第四种方法。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>


static int __devinit my_i2c_drv_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int __devexit my_i2c_drv_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static const struct i2c_device_id my_dev_id_table[] = {
	{ "my_i2c_dev", 0 },
	{}
};

static int my_i2c_drv_detect(struct i2c_client *client,
		       struct i2c_board_info *info)
{
	/* 能运行到这里, 表示该addr的设备是存在的
	 * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
	 * 还需要进一步读写I2C设备来分辨是哪款芯片
	 * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
	 */
	
	printk("my_i2c_drv_detect: addr = 0x%x\n", client->addr);

	/* 进一步判断是哪一款 */
	
	strlcpy(info->type, "my_i2c_dev", I2C_NAME_SIZE);
	return 0;
}

static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };

/* 1. 分配/设置i2c_driver */
static struct i2c_driver my_i2c_driver = {
	.class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
	.driver	= {
		.name	= "my_i2c_dev",
		.owner	= THIS_MODULE,
	},
	.probe		= my_i2c_drv_probe,
	.remove		= __devexit_p(my_i2c_drv_remove),
	.id_table	= my_dev_id_table,
	.detect     = my_i2c_drv_detect,  /* 用这个函数来检测设备确实存在 */
	.address_list	= addr_list,   /* 这些设备的地址 */
};

static int my_i2c_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&my_i2c_driver);
	
	return 0;
}

static void my_i2c_drv_exit(void)
{
	i2c_del_driver(&my_i2cc_driver);
}


module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);
MODULE_LICENSE("GPL");

上面的代码回去.class指定的这一类i2c设配器中,查找.address_list这些地址,如果设备存在,就会调用.detect函数,确认是哪一个i2c设备,然后调用i2c_new_device

下面来分析这一个过程

i2c_add_driver
    i2c_register_driver
        driver_register(&driver->driver); /* 会去dev链表中找到匹配的设备,调用probe函数 */

        /* 对于每个设配器调用__process_new_driver */
        i2c_for_each_dev(driver, __process_new_driver);
            __process_new_driver
                i2c_do_add_adapter
                    i2c_detect
                        address_list = driver->address_list; /* 得到要检测的所有地址 */
                        i2c_detect_address
                        i2c_check_addr_validity /* 检测地址是否存在 */
                        driver->detect(temp_client, &info); /* 如果地址存在,调用detect */
                        i2c_new_device /* 创建设备 */

 

二、i2c总线驱动

1、设备一个i2c_driver结构体

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, /* 这个结构体中的名字很重要 */
};

id_tabel中的名字很重要,i2c总线就是靠这个名字来匹配i2c驱动和设备的,一旦匹配上就会调用probe函数

2、注册

static int __init my_i2c_drv_init(void)
{

	i2c_add_driver(&my_i2c_drv_drv);

	return 0;
}

module_init(my_i2c_drv_init);

3、实现probe函数和remove函数

static struct i2c_client *my_i2c_client;

static int my_i2c_drv_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
    /* 记录下i2c设备client */
    my_i2c_client = client;    

    /* 在这里注册字符设备,创建设备节点 */
    
    /* 这里为了简单,只是打印一句话而已 */
    printk("my_i2c_drv_probe\n");

    return 0;
}

static int my_i2c_drv_remove(struct i2c_client *client)
{
    /* 在这里卸载字符设备 */

    /* 这里为了简单,只打印一句话 */
    printk("my_i2c_drv_remove\n")    

    return 0;
}

完整的代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/earlysuspend.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/ioc4.h>
#include <linux/io.h>

#include <linux/proc_fs.h>

#include <mach/gpio.h>
#include <mach/hardware.h>
#include <plat/gpio-cfg.h>
#include <plat/irqs.h>


static struct i2c_client *my_i2c_client;

static const struct i2c_device_id my_i2c_dev_id[] = {
	{ "my_i2c_dev", 0 },
	{ }
};

static int my_i2c_drv_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
    /* 记录下i2c设备client */
    my_i2c_client = client;    

    /* 在这里注册字符设备,创建设备节点 */
    
    /* 这里为了简单,只是打印一句话而已 */
    printk("my_i2c_ddrv_probe\n");

    return 0;
}

static int my_i2c_drv_remove(struct i2c_client *client)
{
    /* 在这里卸载字符设备 */

    /* 这里为了简单,只打印一句话 */
    printk("my_i2c_dev_remove\n")    

    return 0;
}

static struct i2c_driver my_i2c_drv = {
	.driver = {
		.name	= "jt_ts",
        .owner = THIS_MODULE,
	},
	.probe		= my_i2c_drv_probe,
	.remove		= my_i2c_drv_remove,
	.id_table	= my_i2c_dev_id,
};

static int __init my_i2c_drv_init(void)
{

	i2c_add_driver(&my_i2c_drv);

	return 0;
}

static void __exit my_i2c_drv_exit(void)
{

	i2c_del_driver(&my_i2c_drv);

	return;
}

module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);

MODULE_LICENSE("GPL");

三、怎么使用i2c总线传输数据?

/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[1]; 

char val[4]
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = val; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 2; /* 想要传输的字节数 */

/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 1); /* msg:消息,1:传输msg个数 */

举个栗子,完成以下时序的读写

 

 

 

下面是实现代码

/* 定义 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 */

读取到的数据会存储val[]数组中

 

 

展开阅读全文

android + linux 下一个简单的I2C设备驱动问题

02-13

机器上有重力感应IC, 启动使用时, adb shell getevent 环境下有以下信息输出:rnrn/dev/input/event3:0003 0000 00000000rn...............rnrn[color=#FF0000]问题1: event3是如何定义的? (如何把重力感应定义为event3, 应如何查找相关信息)rnrnrn问题2: 请大概解释一下以下驱动(mma7660是重力感应IC, 二个源文件:mma7660.mod.c mma7660.c)rn 这是不是一个完整的驱动结构?[/color]rnrnmma7660.mod.c**************************************************************************rn#include rn#include rn#include rnrnMODULE_INFO(vermagic, VERMAGIC_STRING);rnrnstruct module __this_modulern__attribute__((section(".gnu.linkonce.this_module"))) = rn .name = KBUILD_MODNAME,rn .init = init_module,rn#ifdef CONFIG_MODULE_UNLOADrn .exit = cleanup_module,rn#endifrn .arch = MODULE_ARCH_INIT,rn;rnrnstatic const struct modversion_info ____versions[]rn__usedrn__attribute__((section("__versions"))) = rn 0x6609097d, "module_layout" ,rn 0x39a2bc88, "i2c_del_driver" ,rn 0xa6e8f4fe, "sysfs_remove_group" ,rn 0x3643eb2a, "i2c_register_driver" ,rn 0xa2670b54, "input_event" ,rn 0xb5eeb329, "register_early_suspend" ,rn 0x41916eb7, "sysfs_create_group" ,rn 0x74f317de, "input_register_polled_device" ,rn 0x7886ef1b, "input_set_abs_params" ,rn 0xe1864d63, "dev_err" ,rn 0x18fcff88, "input_allocate_polled_device" ,rn 0x7fab2120, "_dev_info" ,rn 0xc7f50b24, "hwmon_device_register" ,rn 0xeae3dfd6, "__const_udelay" ,rn 0x78f72010, "hwmon_device_unregister" ,rn 0x3c2c5af5, "sprintf" ,rn 0xa7932670, "i2c_smbus_read_byte_data" ,rn 0xb4583d82, "i2c_smbus_write_byte_data" ,rn 0xea147363, "printk" ,rn 0x23269a13, "strict_strtoul" ,rn;rnrnstatic const char __module_depends[]rn__usedrn__attribute__((section(".modinfo"))) =rn"depends=";rnrnMODULE_ALIAS("i2c:mma7660");rnrnMODULE_INFO(srcversion, "9F3D086EB2FC7AFBC5DB637");rnmma7660.mod.c**************************************************************************rnrnrnrn 论坛

没有更多推荐了,返回首页