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[]数组中

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Linux I2C设备驱动Linux内核中的一个子系统,用于处理I2C总线上的设备驱动程序。I2C是一种串行通信协议,通常用于连接各种外设,例如传感器、LCD屏幕、EEPROM、温度传感器等。 Linux I2C设备驱动程序通常包括以下几个部分: 1. i2c_driver结构体:定义I2C设备驱动的属性和操作函数。这个结构体包含了设备的名称、ID等信息,以及设备的初始化函数、读写函数等。通过注册这个结构体,将I2C设备驱动程序和I2C总线绑定在一起。 2. i2c_client结构体:定义I2C设备的属性和操作函数。这个结构体包含了设备的地址、名称等信息,以及设备的读写函数等。通过这个结构体,可以访问I2C设备,读写寄存器等。 3. probe函数:用于初始化I2C设备。当I2C总线扫描到一个新的设备时,会调用该函数,完成设备的初始化工作。 4. remove函数:用于卸载I2C设备。当I2C总线上的设备被移除时,会调用该函数,完成设备的清理工作。 5. ioctl函数:用于实现设备的特殊操作。例如,设置I2C设备的工作模式、读取设备的状态等。 通过实现这些函数,可以编写一个完整的Linux I2C设备驱动程序。在驱动程序中,可以使用Linux内核提供的函数,例如i2c_transfer函数、i2c_smbus_read_byte函数等,来实现I2C设备的读写操作。同时,也可以使用Linux的调试工具来调试驱动程序,例如dmesg命令、insmod命令等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值