跳转至

模块使用范例

4 模块使用范例

4.1 利用 i2c-core 接口读写 TWI 设备

在内核源码中有现成的 i2c 设备驱动实例:tina/lichee/kernel/linux-5.4/drivers/misc/eeprom/at24.c, 这是一个 EEPROM 的 I2C 设备驱动,为了验证 I2C 总线驱动,所以其中通过 sysfs 节点实现读写访问。下面对这个文件的一些关键点进行展示介绍:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>

#define EEPROM_ATTR(_name) \ 
{                           \
    .attr = { .name = #_name,.mode = 0444 }, \
    .show = _name_00_show, \ 
}

struct i2c_client *this_client;

static const struct i2c_device_id at24_ids[] = {
    { "24c16", 0 },
    { /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(i2c, at24_ids);

static int eeprom_i2c_rxdata(char *rxdata, int length)
{
    int ret;
    struct i2c_msg msgs[] = {
        {
            .addr = this_client->addr,
            .flags = 0,
            .len = 1,
            .buf = &rxdata[0],
        },
        {
            .addr = this_client->addr,
            .flags = I2C_M_RD,
            .len = length,
            .buf = &rxdata[1],
        },
    };
    ret = i2c_transfer(this_client->adapter, msgs, 2);
    if (ret < 0)
        pr_info("%s i2c read eeprom error: %d\n", __func__, ret);

    return ret;
}    

static int eeprom_i2c_txdata(char *txdata, int length)
{
    int ret;
    struct i2c_msg msg[] = {
        {
            .addr = this_client->addr,
            .flags = 0,
            .len = length,
            .buf = txdata,
        },
    };
    ret = i2c_transfer(this_client->adapter, msg, 1);
    if (ret < 0)
        pr_err("%s i2c write eeprom error: %d\n", __func__, ret);

    return 0;
}

static ssize_t read_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
    int i;
    u8 rxdata[4];
    rxdata[0] = 0x1;
    eeprom_i2c_rxdata(rxdata, 3);

    for(i=0;i<4;i++)
        printk("rxdata[%d]: 0x%x\n", i, rxdata[i]);

    return sprintf(buf, "%s\n", "read end!");
}

static ssize_t write_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
    int i;
    static u8 txdata[4] = {0x1, 0xAA, 0xBB, 0xCC};
    for(i=0;i<4;i++)
        printk("txdata[%d]: 0x%x\n", i, txdata[i]);

    eeprom_i2c_txdata(txdata,4);

    txdata[1]++;
    txdata[2]++;
    txdata[3]++;

    return sprintf(buf, "%s\n", "write end!");
}

static struct kobj_attribute read = EEPROM_ATTR(read);
static struct kobj_attribute write = EEPROM_ATTR(write);

static const struct attribute *test_attrs[] = {
    &read.attr,
    &write.attr,
    NULL,
};

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int err;
    this_client = client;
    printk("1..at24_probe \n");
    err = sysfs_create_files(&client->dev.kobj,test_attrs);
    printk("2..at24_probe \n");

    if(err){
        printk("sysfs_create_files failed\n");
    }
    printk("3..at24_probe \n");

    return 0;
}
static int at24_remove(struct i2c_client *client)
{
    return 0;
}

static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .owner = THIS_MODULE,
    },
    .probe = at24_probe,
    .remove = at24_remove,
    .id_table = at24_ids,
};

static int __init at24_init(void)
{
    printk("%s %d\n", __func__, __LINE__);
    return i2c_add_driver(&at24_driver);
}
module_init(at24_init);

static void __exit at24_exit(void)
{
    printk("%s()%d - \n", __func__, __LINE__);
    i2c_del_driver(&at24_driver);
}
module_exit(at24_exit);

4.2 利用用户态接口读写 TWI 设备

如果配置了 i2c devices interface,可以直接利用文件读写函数来操作 I2C 设备。下面这个程序直接读取 /dev/i2c-* 来读写 i2c 设备:

#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#define CHIP "/dev/i2c-1"
#define CHIP_ADDR 0x50
int main()
{
    unsigned char rddata;
    unsigned char rdaddr[2] = {0, 0}; /* 将要读取的数据在芯片中的偏移量 */
    unsigned char wrbuf[3] = {0, 0, 0x3c}; /* 要写的数据,头两字节为偏移量 */
    printf("hello, this is i2c tester\n");
    int fd = open(CHIP, O_RDWR);
    if (fd < 0)
    {
        printf("open "CHIP"failed\n");
        goto exit;
    }
    if (ioctl(fd, I2C_SLAVE_FORCE, CHIP_ADDR) < 0)
    { /* 设置芯片地址 */
        printf("oictl:set slave address failed\n");
        goto close;
    }
    printf("input a char you want to write to E2PROM\n");
    wrbuf[2] = getchar();
    printf("write return:%d, write data:%x\n", write(fd, wrbuf, 3), wrbuf[2]);
    sleep(1);
    printf("write address return: %d\n",write(fd, rdaddr, 2)); /* 读取之前首先设置读取的偏移量 */
    printf("read data return:%d\n", read(fd, &rddata, 1));
    printf("rddata: %c\n", rddata);
    close(fd);

exit:
    return 0;
}