linux驱动-I2C

linux I2C的架构

linux I2C是bus里面的一种总线,I2C总线,也可以说是I2C子系统。它的架构图(个人理解,从代码的角度看组织结构图,即架构),示意图如下:
这里写图片描述

从图中,可以看出linux i2c subsystem主要以三个结构体i2c_adpater,i2c_client,i2c_driver为核心,其中i2c_adpater与cpu i2c控制器关联起来,i2c_driver与i2c设备关联起来,而i2c_client就是i2c_adpater和i2c_driver桥梁。

i2c_adpater一般是由cpu生产商,利用linux i2c subsystem提供的接口,将CPU的i2c控制器抽象成i2c_adpater结构体,注册到linux i2c subsystem。

i2c_driver是由客户根据不同i2c设备(一般都是i2c设备地址),利用linux i2c subsystem提供的接口,将设备抽象成i2c_driver结构体,然后注册到linux i2c subsystem。

i2c_client是驱动与设备通信需要用的,当在注册i2c_adpater、i2c_driver时,如果adpater(i2c主机适配器)和driver(i2c设备驱动)匹配上后,linux i2c subsystem就会创造出一个client(客户端结构体,i2c_client),并调用i2c_driver里面的探测函数,将i2c_client指针传给驱动,驱动需要保存这个指针,方便后面通信时用。

i2c_adpater(结构体里面嵌入了struct device)是以device(设备)的形式挂接到i2c总线(i2c_bus_type)上。
i2c_driver(结构体里面嵌入了struct device_driver)是以driver(驱动)的形式挂接到i2c总线上(i2c_bus_type)。
被i2c subsystem创造出来的i2c_client(嵌入了struct device)是以device(设备)的形式挂接到i2c总线。


构建i2c_client方式

构建i2c_client的方式有三种:

  • 1、利用i2c_register_board_info()函数和i2c_adpater的注册方式构建

    • i2c_register_board_info()定义在driver/i2c/i2c_boardinfo.c中,声明在include/linux/i2c.h中。
      i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
      • 功能:将cpu的i2c控制器号和设备地址,直接强行绑定到一起,放入i2c_board_info结构体中,并加入到__i2c_board_list链表中。
      • 第一个参数:busnum就是i2c控制器号,一般现在的cpu都有多个硬件i2c接口,在注册i2c_adpater的时候,会将这些硬件i2c接口编号抽象成i2c_adapter成员nr,因此busnum等于nr。这个参数就根据情况,选择端口号。
      • 第二个参数:info是I2C设备的信息,主要是设备地址和名字,名字一般是要和i2c_driver里面成员中id_table的name相同(就能匹配)。一般info用提供的宏I2C_BOARD_INFO来定义,如:
        static struct i2c_board_info __initdata ft6206_i2c_tpd={ I2C_BOARD_INFO(“ft6206”, (0x70>>1))}
        设备地址是否移位,主要看adpater实现怎么处理读写位的,以及设备地址是原本的地址,还是已经按照读写位处理过的。一般,这里填入的设备地址,不需要有读写位的。
      • 第三个参数:len表示几个设备,在第二个参数里,我们是可以定义一个数组的,来同时注册多个设备。
    • 在注册i2c_adpater时,即调用i2c_scan_static_board_info()(i2c subsystem里面也只有这个时刻会调用这个函数)去扫描i2c_register_board_info注册的设备,如果注册的硬件i2c接口编号与i2c_adapter的nr相同,则创建i2c_client(调用i2c_new_device创建),创建的时候会调用device_register将client挂接到i2c总线上。
    • 在调用i2c_add_driver()注册i2c_driver时候,会调用到driver_register(),这个函数会扫描i2c总线上的设备。而i2c_client内嵌有struct device,这个device在创建client的时候,会把device挂接到i2c总线上,一但扫描到client的时候,会用client的name,与type比较(就是info中的type),当type和注册i2c_driver的id_table中name一样时,就匹配成功,并调用i2c_driver的探测函数(probe),探测函数传递的参数就是i2c_client结构体指针。驱动要保存这个指针,方便后面用。

    • 基于上面这点,注册i2c设备的模块加载时间,一定要早于adpater注册所在模块的加载时间。加载时间控制,有两种方式,一种用include/linux/init.h里面的__define_initcall(fn, 0),即第2个参数,越小就先加载,越大,就越后加载,另外一种,同样的加载函数,即__define_initcall的第2个参数想同的,将要先加载的模块,先编译,放在Makefile前面一些的位置,就能实现。

这种方式就是,已知硬件i2c接口编号和相连的i2c设备地址情况下,用i2c_register_board_info()来绑定,然后驱动通过id_table的name形式与之匹配,来创建client,并在探测函数里面获取client指针,为后面的通信获取client。

  • 2、另外一种,就是利用i2c_driver里面address_list(i2c设备地址列表,可以一个,或者多个)、detect,以及class(这个要与i2c_adpater里面的class一样),在i2c_dirver和i2c_adapter注册时会调用到i2c_detect(),并用address_list地址和i2c总线(i2c_bus_type)上每一个adapter握手一次,如果成功就调用i2c_new_device创建client,并扫描i2c总线上的驱动,找到匹配的,就掉用driver的探测函数
    address_list = driver->address_list;
    if (!driver->detect || !address_list)
        return 0;

    /* Stop here if the classes do not match */
    if (!(adapter->class & driver->class))
        return 0;

调用关系:i2c_register_driver->__process_new_driver()->i2c_do_add_adapter()->i2c_detect()
i2c_detect()里面,会调用到i2c_default_probe和i2c设备握手一次,如果成功也会创建i2c_client,创建完成后,在i2c_new_device()里面会调用device_register()将client挂接(注册)到i2c总线上,挂接过程,会扫描i2c总线i2c_bus_type上的driver(i2c设备驱动程序),client->name名字与驱动名字(i2c_driver中id_table的name)相同,调用i2c_driver里面的探测函数(probe),即匹配成功。

注意:device_register()注册设备(struct device),driver_register()注册设备驱动(device_dirver),在调用两个函数前,都是需要明确指定设备和设备驱动所属总线,然后在注册的时候,都会按照总线匹配方式(即调用总线上的匹配函数),去匹配彼此(注册设备时,就是设备匹配驱动,在注册驱动时,就是用驱动去匹配设备),匹配成功,会调用总线探测函数,总线探测函数会调用总线driver的探测函数(probe)。i2c总线的匹配,是按照i2c_client的name和i2c_driver里面id_table的name是否相同,相同则匹配成功,否则匹配失败。

这种方式,实现的就是用i2c的设备地址,与i2c 总线上的每一个adapter(主机适配器驱动),发起一次握手,成功的,就认为,i2c设备驱动可以用这个adpater,并创建i2c_client,并将client注册到i2c总线,注册过程扫描总线驱动,匹配成功就调用探测函数。这个过程就是,i2c设备地址与adapter自动匹配。

  • 3、利用i2c_driver里面的成员attach_adapter来匹配创建client
int (*attach_adapter)(struct i2c_adapter *);

在调用i2c_register_adapter()注册adapter的时候,或者调用i2c_register_driver注册driver的时候,都会调用到i2c_do_add_adapter():

static int i2c_do_add_adapter(struct i2c_driver *driver,
                  struct i2c_adapter *adap)
{
    /* Detect supported devices on that bus, and instantiate them */
    i2c_detect(adap, driver);

    /* Let legacy drivers scan this bus for matching devices */
    if (driver->attach_adapter) {
        dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
             driver->driver.name);
        dev_warn(&adap->dev, "Please use another way to instantiate "
             "your i2c_client\n");
        /* We ignore the return code; if it fails, too bad */
        driver->attach_adapter(adap);
    }
    return 0;
}

其中attach_adapter就要驱动自己实现,一般还是按照,用adapter和设备通信,确定是否匹配,如果匹配,自己调用i2c_new_device()创建client。在i2c总线上的adapter都会循环,一个一个通过attach_adapter送下来。

这三种方式中,第一种最多,第二种比较自动,第三种驱动开发者要自己做的工作比较多,却很灵活。


i2c_adapter注册

注册函数:

int i2c_add_adapter(struct i2c_adapter *);
int i2c_add_numbered_adapter(struct i2c_adapter *);

两个都可以用来注册i2c_adapter

注销函数:

void i2c_del_adapter(struct i2c_adapter *);

注册和注销,只需要包含include/linux/i2c.h


i2c_driver注册

注册函数:

int i2c_register_driver(struct module *, struct i2c_driver *);
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)

函数和宏都是注册i2c_driver
如:

static const struct i2c_device_id ft6206_tpd_id[] = {{"ft6206",0},{}};
 static struct i2c_driver tpd_i2c_driver = {
    .driver = {
    .name = "ft6206",
    },
    .probe = tpd_probe,
     .remove = tpd_remove,
     .id_table = ft6206_tpd_id,
     .detect = tpd_detect,
};

其中driver.name、probe、id_table、remove是必须的,detect其他,根据情况选择。
其中id_table是i2c总线驱动和client匹配用的,主要是i2c_device_id 的name,要和i2c_client的name,字符串相同,就匹配成功。

总线上设备和driver的匹配,都是按照总线匹配函数匹配的。

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_register()和driver_register()都会调用到这个函数。

注销函数:

void i2c_del_driver(struct i2c_driver *);

注册和注销,只需要包含include/linux/i2c.h


I2C subsystem提供的通信接口

都声明在include/linu/i2c.h里面:

/*
向i2c设备发送数据(写数据),buf发送的数据,count发送多少个字节(一次不能超过64k),失败返回负数,成功返回实际写入的字节数
*/
int i2c_master_send(const struct i2c_client *client, const char *buf,
               int count);

/*
读数据,buf读出数据存放地址,count要接收的字节数(一次不能超过64k),失败返回负数,成功返回实际读取的字节数
*/
int i2c_master_recv(const struct i2c_client *client, char *buf,
               int count);

/*
i2s_msg,是i2c_transfer,__i2c_transfer向i2c_msg读写数据时,将读写信息封装在这个结构体里面
addr:i2c设备地址
flages:标志,当有I2C_M_RD,即最低位为1时表示,本次是读,最低位是0表示写,其他见注释
len:要读写字节的数据,字节为单位,有的函数不能超过64k,就是受限这个len的最大值
buf:要读写的数据存放地址
如果看到部分CPU平台有__u32 timing,表示支持i2c速率可以配置,没有的,在i2c_adapter注册时就固定了
*/
struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
#define I2C_M_STOP      0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};
/*
写数据到i2c设备,写的数据封装在i2c_msg结构体里面,num表示有几个打包(i2c_msg)的msgs发送,即msgs可以是数组。
第一个参数,adap=client->adapter
发送成功返回,实际发送消息包数(sizeof(msgs)/sizeof(i2c_msg)),失败返回负数
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
            int num);
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
              int num);

/*
带smbus的表示支持smbus这种协议的,上面的是普通I2C,也是现在普片的,smbus是比较少的,接口i2c_smbus_xfer如果adapter不支持,会走普通的i2c,即调用__i2c_transfer读写数据
adapter = client->adapter
addr:i2c设备地址
flags:一般可以直接用flags = client->flags
read_wirte:读写标志,一般是I2C_SMBUS_READ(读)、I2C_SMBUS_WRITE(写)
command:命令(寄存器地址)
size:为1时,写时,表示写一个命令(寄存器地址),不带命令参数(寄存器值),读时表示只读不写(命令也不写)
     大于1时,表示读写一个命令,参数个数等于size-1
data:读写的数据存放到这个结构体里面,读写的数据,一个字节时,放入data->byte,两个字节时放入到data->word,超过2个字节时,放入到data->block,最大不超过32个字节,当超过2个字节时,data->block[0]表示的是读取或写入字节数,从data->block[1]开始才是读取或写入的值。
读写成功返回0,失败返回负数
*/            
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
              unsigned short flags, char read_write, u8 command,
              int size, union i2c_smbus_data *data);

/*读一个byte,返回值就是读取的数据,其实也是调用i2c_smbus_xfer实现的*/            
s32 i2c_smbus_read_byte(const struct i2c_client *client);

/*
写一个byte,返回0表示成功,返回负数表示失败,也是调用i2c_smbus_xfer实现的
*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);

/*
读取寄存器command,一个byte值,
返回负数表示失败,成功返回读取的值,也是调用i2c_smbus_xfer实现的
*/
s32 i2c_smbus_read_byte_data(const struct i2c_client *client,
                    u8 command);

/*
向寄存器command,写一个byte值value,
返回负数表示失败,成功返回0,也是调用i2c_smbus_xfer实现的
*/
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
                     u8 command, u8 value);

/*
读取寄存器command,2个byte值,
返回负数表示失败,成功返回读取的值,也是调用i2c_smbus_xfer实现的
*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client,
                    u8 command);

/*
向寄存器command,写2个byte值value,
返回负数表示失败,成功返回0,也是调用i2c_smbus_xfer实现的
*/
s32 i2c_smbus_write_word_data(const struct i2c_client *client,
                     u8 command, u16 value);

/*
i2c_smbus_read_word_data读取到的值,将高8位和低8位交换,返回结果
*/
static inline s32
i2c_smbus_read_word_swapped(const struct i2c_client *client, u8 command)
{
    s32 value = i2c_smbus_read_word_data(client, command);

    return (value < 0) ? value : swab16(value);
}

/*
将要写的16位寄存器值,高8位和低8位交换,后的值,写到寄存器
*/
static inline s32
i2c_smbus_write_word_swapped(const struct i2c_client *client,
                 u8 command, u16 value)
{
    return i2c_smbus_write_word_data(client, command, swab16(value));
}

/* 
读取寄存器command,读取到的值放入values地址中,具体读取了多少个,看返回值。
*/
s32 i2c_smbus_read_block_data(const struct i2c_client *client,
                     u8 command, u8 *values);

/* 
向寄存器command,写入length个字节(不能超过32),写的值放入values中
返回0表示成功,负数表示失败
*/
s32 i2c_smbus_write_block_data(const struct i2c_client *client,
                      u8 command, u8 length, const u8 *values);


/* 
向寄存器command,读取length个字节(不大于32),读取到的值放入values中
返回,实际读取到的字节数
*/
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
                     u8 command, u8 length, u8 *values);

/*
向寄存器command,写入length个字节(不大于32),要写入的值放入values中
返回0表示成功,负数表示失败
*/
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
                      u8 command, u8 length,
                      const u8 *values);

从以上接口,我们完全可以体会到,i2c_client在通信过程中的重要性了。


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值