I2C客户端驱动程序

本文介绍了I2C总线的特性,包括其双线制结构和常用时钟频率。详细讨论了I2C客户端驱动程序的架构,如probe和remove函数,以及如何通过id_table配置设备。此外,还概述了如何通过内核API与I2C设备交互,以及在设备树中声明和注册I2C设备的方法。
摘要由CSDN通过智能技术生成

        由NXP发明的I2C总线是双线制,由串行数据(SDA)、串行时钟(SCL)构成的异步串行总线。SCL有主设备生成,以便在总线上同步数据的传输。I2C用在嵌入式系统中,经常用于连接串行的EEPROM、RTC时钟、GPIO扩张器、温度传感器等。时钟频率常在10K~100K、400K~2M HZ不等。

一、I2C客户端驱动程序架构

        I2C控制器驱动程序在设备和总线之间提供抽象层。实现了I2C总线框架,I2C客户端驱动有struct i2c_driver{}表示,而I2C客户端设备有struct i2c_client{}表示。

下面是i2c_driver{}的主要成员

struct i2c_driver {
    //标准驱动程序模型接口
    int (*probe)(struct i2_client *, const struct i2c_device_id);
    int (*remove)(struct i2c_client *);
    // 与枚举无关的驱动类型接口
    void (*shutdown)(struct i2c_client *);
    struct device_driver driver;
    const struct i2c_device_id *id_table;
};

1. 驱动probe函数和remove流程

        在probe函数中主要实现如下几个主要功能:

  •         检查设备是否是所期望的设备;使用i2c_check_functionality()检查I2C总线控制器是否支持设备所需要的功能;
  • 初始化设备,使用函数i2c_set_client_data()设置特定的数据;
  • 注册合适的内核框架;
// probe函数的原型:
static int foo_probe(struct i2c_client *client, struct i2c_device_id *id);

// 其中struct i2c_client{}代表I2C设备本身,其主要成员如下:
struct i2c_client {
    unsigned short flags;
    unsigned short addr;  // chip address, 7bit,地址被存储在低7位
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;
    struct device dev;
    int irq; // 有设备发出的irq
    struct list_head detected;
    #if IS_ENABLED(CONFIG_I2C_SLAVE)
        i2c_slave_cb_t slave_cb;
    #endif
}; 
// 其中struct i2c_device_id 是正在探测的设备所对应的i2c设备id项

// 设置私有数据的函数如下:
void i2c_set_clientdata(struct i2c_client *client, void *data);
void *i2c_get_clientdata(struct i2c_client *client);
// 在这两个函数的内部会调用dev_set_drvdata和dev_get_drvdata,设置和去除struct device结构中的void *driver_data字段


一个probe函数实现的例子:

// 私有数据
struct mc9s08dz60 {
    struct i2c_client *client;
    struct gpio_chip chip;
};

static int mc9s08dz60_probe(struct i2c_client *client,
                        const struct i2c_device_id *id) {
    struct mc9s08dz60 *mc9s;
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
        return -EIO;
    mc9s = devm_kzalloc(sizeof(*mc9s), GFP_KERNEL);
    mc9s->client = client;
    i2c_set_clientdata();
    // [...]; // 设置gpio chip相关的功能;
    return gpiochip_add(&mc9s->chip);
};

驱动程序的remove函数流程:

// i2c驱动remove函数原型
static int foo_remove(struct i2c_client *client);

// remove函数实现示例
static int mc9s08dz60_remove(struct i2c_client *client) {
    struct mc9s08dz60 *mc9s = i2c_get_clientdata(client);
    return gpiochip_remove(&mc9s->chip);
}

与platform_driver类似,module_*_driver函数的原型是void module_i2c_driver(foo_driver);

2. 驱动程序和设备通过id_table配置的方式

I2C设备对应的id结构是struct i2c_device_id{}

struct i2c_device_id {
    char name[I2C_NAME_SIZE];
    kernel_ulong_t driver_data;
};

使用struct i2c_device_id{}的示例如下:

static struct i2c_device_id foo_id_table[] = {
    {"foo", my_id_for_foo},
    {"bar", my_id_for_bar},
};

MODULE_DEVICE_TABLE(i2c, foo_id_table); // 为了让linux Device Tree根据设备自动加载特定内核module

struct i2c_driver foo_driver = {
    .driver = { .name = "foo",
    },
    .id_table = foo_id_table,  // 通过SoC平台文件的方式probe设备
    .probe = foo_probe,
    .remove = foo_remove,
}

// 要自动注册i2c driver,使用宏module_i2c_driver,
// 自动调用i2c_driver_register()
module_i2c_driver(foo_driver);

二、访问客户端方式

        i2c内核提供两种API,一种是普通I2C通信,另一种用于兼容SMBUS的设备,它也适用于I2C设备。

        普通i2c通信的基本函数:

// i2c通信的基本函数
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);

// 下面是一种更灵活的i2c发送消息方式,i2c_transfer()函数可以发送一组消息,消息组可以读和写的组合
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num);

// i2c_msg{}格式如下:
struct i2c_msg {
    u16 addr;  // 从设备地址
    u16 flags; // 0代表写入,I2C_M_RD代表读取;
    u16 len;
    u16 *buf;
};

下面是microchip I2C 24LC512 EEPROM字符驱动程序的读取函数,

ssize_t eep_read(struct file *filp, char __user *buf,
    size_t count, loff_t *f_pos) {
    [...];
    int _reg_addr = dev->current_pointer;
    u8 reg_addr[2];
    reg_addr[0] = (u8)(_reg_addr>>8);
    reg_addr[1] = (u8)(_reg_addr);

    struct i2c_msg msg[2];
    msg[0].addr = dev->client->addr;
    msg[0].flags = 0; // 写入
    msg[0].len = 2;
    msg[0].buf = reg_addr; // 写入地址

    msg[1].addr = dev->client->addr;
    msg[1].flags = I2C_M_RD; // 读取
    msg[1].len = count;
    msg[2].buf = dev->data;
    if (i2c_transfer(dev->client->adapter, msg, 2))
        pr_err("ee241c512: i2c_transfer failed.");

    if (copy_to_user(buf, dev->data, count)!=0) return -EIO;

    [...] // 省略其他流程
};

SMBus是intel开发的双线总线,与I2C非常相似。I2C设备与SMBUS兼容,反之则不然。因此,在不知道设备是SMBus设备还是I2C设备的情况下,使用SMBus相关函数是最安全的。

// SMBus Api
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 comand);
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 comand);
s32 i2c_smbus_read_block_data(struct i2c_client *client, u8 comand, u8 *values);

s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 comand, u8 value);
s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 comand, u16 value);
s32 i2c_smbus_write_block_data(struct i2c_client *client, u8 comand,
                                u8 length, u8 *values);

三、I2C设备和DT树

1. 在SoC开发板文件中实例化I2C设备的方式(旧的方式)

        结构体struct i2c_board_info{}用来表示开发板上I2C设备所使用的结构,其定义如下:

struct i2c_board_info {
    char type[I2C_NAME_SIZE];  // 这个值应该与i2c_driver.driver.name相同,来进行匹配
    unsigned short addr;
    void *platform_data;
    int irq;
};

接着在SoC board文件中,定义struct i2c_board_info{}数组,使用函数i2c_register_board_info()进行注册,其原型:

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

2. 在DT中声明I2C设备

        I2C设备节点都是他们所在总线节点的子节点,每个设备只分配一个地址,不涉及长度或范围。声明I2C设备需要的标准属性是reg(用于匹配总线上设备的地址)和compatible属性(用于匹配设备与驱动程序)。

&i2c2 {
    pcf8523: rtc@68 {
        compatbile="nxp,pcf8523";
        reg = <0x68>;
    };
    eeprom: ee241c512@55 {
        compatbile="packt,ee241c512";
        reg = <0x55>;
    };
};

3. 根据DT中的配置定义和注册I2C客户端驱动程序

        使用了DT后,需要增加一个struct of_device_id{}数组。

static struct of_device_id foobar_of_match {
    {.compatible="packt,foobar-device",},
    {}
};
MODULE_DEVICE_TABLE(of, foobar_of_match); // 用于检测到设备时自动加载模块

static struct i2c_driver foo_drier {
    .driver = {.name="foo",
        .of_match_table = of_match_ptr
    },
    .probe = foo_probe,
    .id_table = foo_id, // 在4.10以前的内核中,必须存在id_table成员。
};

static int my_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    struct of_device_match = of_match_device(foobar_of_match, &client->dev);
    if (match) {
        /*设备树相关代码*/
    } else {
        ...
    }
};

module_i2c_driver(foo_driver);

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值