Linux驱动开发实例(一)

Linux驱动开发实例(一) cxcc 2023-05-17 08:51:44 1017

Linux驱动开发实例(一)

本文介绍TCA9535扩展IO芯片的驱动开发(不涉及中断) 开发板:Ebaina SS928开发板

一、简介

TCA9535是一个i2c扩展IO的芯片,可扩展出16个IO引脚。 TCA9535包括4组寄存器,每组两Byte共16bit,每bit控制一个引脚

  1. 寄存器0,寄存器1 输入寄存器:用于读取P0,P1端口的输入值,
  2. 寄存器2,寄存器3 输出寄存器 :用于设置P0,P1端口的输出值,
  3. 寄存器4,寄存器5 极性反转寄存器:用于当P0,P1端口做为输入时,对输入的电平进行反转处理,即管脚为高电平时,设置这个寄存器中相应的位为1时,读取到的输入寄存器0,1的值就是低电平0了。
  4. 寄存器6,7 配置寄存器:用于配置P0,P1端口的做为输入或是输出。

因此本文主要是关于i2c驱动的编写,以及创建基本的字符设备,及字符设备文件供应用层操作。(基于设备树)

二、设备树下驱动的编写

设备树下设备驱动的匹配方式一般是使用compatible属性,在驱动编写时包含一个匹配表(of_match_table)。在驱动加载时根据匹配表中的compatible属性和设备树节点的compatible属性进行匹配。匹配成功后会调用驱动的probe函数。

在设备树下编写驱动的一般方式为:

  1. 在设备树下添加节点,设置compatible属性
  2. 编写驱动结构体,至少包含name和of_match_table、probe、remove

三、基本字符设备驱动

基本的字符设备并不对应实际的硬件设备,而是虚拟的设备。可通过该设备创建设备文件,应用层对字符设备驱动进行文件操作时会调用设备驱动中对应的接口。

例如在应用层调用open函数,最终会调用内核层的open,应用层的close函数最终会调用内核层的release函数,read、write操作与内核层的接口同名。

因此当应用层调用read、write函数时在驱动中的对应接口操作实际的硬件设备即可。在本文的例程中,当应用层对设备文件进行读写时在驱动中通过i2c读写操作控制TCA9535芯片。

字符设备创建的基本流程是:

  1. 申请设备号:alloc_chrdev_region
  2. 创建设备类:class_create
  3. 初始化一个cdev:cdev_init
  4. 添加一个cdev到系统:cdev_add
  5. 创建设备文件:vice_create

需要注意的是设备文件和cdev之间耦合性极低,仅通过设备号联系。设备文件仅仅是cdev暴露在应用层的接口。

三、注册I2C驱动

在设备树的框架下编写I2C驱动变得非常简单,只需要在对应的I2C设备树节点下添加子节点即可。其中驱动结构体使用i2c专用的驱动结构体(struct i2c_driver)。probe、remove函数也只需实现i2c_driver中的probe和remove函数。name和of_match_table需要填充到i2c_driver下的driver结构体。

/* part 3:实现i2c_driver */

static struct i2c_driver tca9535_driver = {
        .probe_new = tca9535_probe,
        .remove = tca9535_remove,
        .driver = {
                .name = "tca9535",
                .of_match_table = tca9535_of_match,
        },
};

随后使用i2c_register_driver注册到系统即可。在probe函数中会提供i2c_client,此时的i2c_client已经填充设备地址。可以直接使用i2c_transfer进行通信。

四、TCA9535驱动的整体结构

在驱动中使用全局静态变量存储绑定的设备列表。static struct tca9535 *tca9535_list[DEV_COUNT]; 当绑定新设备时会创建tca9535,并在列表中存储地址。设备在数组中对应的下标与设备子设备号相同。

在probe函数中创建tca9535并添加到列表(line[297-299]),在open函数中通过inode获取设备号(line[112]),从列表中查找tca9535绑定到file结构体的私有数据(line[113]),在调用read/write函数时将file的私有数据还原为tca9535指针(line[122])。后面根据读写的内容操作硬件即可。

五、代码编写

实现基本的驱动入口,在tca9535_init中注册i2c驱动,并为创建字符设备做准备(申请主设备号,创建设备类)

/* part 1:驱动三件套 */

module_init(tca9535_init);
module_exit(tca9535_exit);
MODULE_LICENSE("GPL");

tca9535_init需要注册i2c驱动。因此需要实现i2c驱动结构体,需要实现probe_new,remove,of_match_table。在probe中需要为新绑定的设备创建设备文件,在of_match_table中需要设置compatible属性。

/* part 3:实现i2c_driver */

static struct i2c_driver tca9535_driver = {
        .probe_new = tca9535_probe,
        .remove = tca9535_remove,
        .driver = {
                .name = "tca9535",
                .of_match_table = tca9535_of_match,
        },
};

tca9535_probe需要创建设备文件。因此需要实现文件系统操作接口

//设备操作函数
static struct file_operations tca9535_fs_ops={
    .owner = THIS_MODULE,
    .open = open,
    .release = release,
    .read = read,
    .write = write,
    .llseek = llseek
};

主要就这三个部分,把上面涉及的所有函数均实现,驱动基本就可用了。

六、程序源码

设备树节点

&i2c_bus0 {
    tca9535@20 {
        compatible = "i2c,tca9535";
        reg = <0x20>;
    };  
};

驱动程序源码


#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>

#define TCA_PRINT(fmt,...) printk("[tca9535]: "fmt,##__VA_ARGS__)
//最多支持5个设备
#define DEV_COUNT 5

#define TCA9535_INPUT_REG       0x00                    /*!< Input status register */
#define TCA9535_OUTPUT_REG      0x02                    /*!< Output register to change state of output BIT set to 1, output set HIGH */
#define TCA9535_POLARITY_REG    0x04                    /*!< Polarity inversion register. BIT '1' inverts input polarity of register 0x00 */
#define TCA9535_CONFIG_REG      0x06                    /*!< Configuration register. BIT = '1' sets port to input BIT = '0' sets port to output */

struct tca9535{
    struct i2c_client *client;
    struct cdev tca9535_cdev;
    struct device* tca9535_device;
};

//主设备号
static dev_t tca9535_dev_t;
//设备类
static struct class *tca9535_class;
//设备列表
static struct tca9535 *tca9535_list[DEV_COUNT];
static spinlock_t tca9535_spinlock;

static int register_tca9535(struct tca9535 *tca)
{
    int ret;
    spin_lock(&tca9535_spinlock);
    for(ret = 0;ret<DEV_COUNT;ret++){
        if(tca9535_list[ret]==NULL){
            tca9535_list[ret] = tca;
            break;
        }
    }
    spin_unlock(&tca9535_spinlock);
    return (ret==DEV_COUNT)?-1:ret;
}
static void unregister_tca9535(struct tca9535 *tca)
{
    spin_lock(&tca9535_spinlock);
    tca9535_list[MINOR(tca->tca9535_cdev.dev)] = NULL;
    spin_unlock(&tca9535_spinlock);
}

static int read_reg(struct i2c_client *client,uint8_t reg_addr,uint16_t* val)
{
    struct i2c_msg msgs[2];
    int ret = 0;
    uint8_t data[2] = {reg_addr,0};

    msgs[0].addr = client->addr;
    msgs[0].buf = data;
    msgs[0].flags = 0;
    msgs[0].len = 1;

    msgs[1].addr = client->addr;
    msgs[1].buf = data;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 2;

    ret = i2c_transfer(client->adapter,msgs,2);
    if(ret<0)
        return -1;
    *val = *(uint16_t*)data;

    return 0;
}
static int write_reg(struct i2c_client *client,uint8_t reg_addr,uint16_t val)
{
    struct i2c_msg msgs[1];
    int ret = 0;
    uint8_t data[3] = {reg_addr,0};
    *(uint16_t*)(data+1) = val;

    msgs[0].addr = client->addr;
    msgs[0].buf = data;
    msgs[0].flags = 0;
    msgs[0].len = 3;

    ret = i2c_transfer(client->adapter,msgs,1);
    if(ret<0)
        return -1;
    return 0;
}

static inline int tca9535_dev_init(struct tca9535* tca)
{
    int ret = 0;
    uint16_t val;
    ret |= read_reg(tca->client,TCA9535_INPUT_REG,&val);
    ret |= write_reg(tca->client,TCA9535_CONFIG_REG,0xFFFF);//所有接口初始化为输入
    ret |= write_reg(tca->client,TCA9535_POLARITY_REG,0x0000);//取消极性反转
    return ret;
}

int open(struct inode *rw_inode, struct file *rw_file);
int release(struct inode *rw_inode, struct file *rw_file);
ssize_t read(struct file *rw_file, char __user *mem_user, size_t size, loff_t *offset);
ssize_t write(struct file *rw_file, const char __user *mem_user, size_t size, loff_t *offset);
loff_t llseek(struct file *file, loff_t offset, int whence);

int open(struct inode *tca9535_inode, struct file *tca9535_file)
{
    int tca9535_id = MINOR(tca9535_inode->i_rdev);
    tca9535_file->private_data = tca9535_list[tca9535_id];
    return 0;
}
int release(struct inode *tca9535_inode, struct file *tca9535_file)
{
    return 0;
}
ssize_t read(struct file *tca9535_file, char __user *buf, size_t count, loff_t *ppos)
{
    struct tca9535* tca = (struct tca9535*)tca9535_file->private_data;
    int retval = 0;
    ssize_t readlen = 0;
    int i;

    char buffer[16];
    uint16_t config;
    uint16_t input_value;

    retval |= read_reg(tca->client,TCA9535_CONFIG_REG,&config);
    retval |= read_reg(tca->client,TCA9535_INPUT_REG,&input_value);

    if(retval){
        return -ENXIO;
    }

    for(i=0;i<16;i++){
        if(config&(0x01<<i)){
            if(input_value&(0x01<<i)){
                buffer[i] = '1';
            }
            else{
                buffer[i] = '0';
            }
        }
        else{
            buffer[i] = '-';
        }
    }

    /* 判断写入位置是否越界 */
    if (*ppos >= 16) {
        return 0;
    }

    /* 计算实际可写入的字节数 */
    if (count > 16 - *ppos) {
        count = 16 - *ppos;
    }

    /* 将数据从用户空间复制到设备缓冲区 */
    retval = copy_to_user(buf, buffer + *ppos,  count);
    if (retval != 0) {
        return -EFAULT;
    }

    /* 更新文件偏移量 */
    *ppos += count;
    readlen = count;

    return readlen;
}
ssize_t write(struct file *tca9535_file, const char __user *buf, size_t count, loff_t *ppos)
{
    struct tca9535* tca = (struct tca9535*)tca9535_file->private_data;
    int retval = 0;
    ssize_t written = 0;

    int i;

    uint16_t output_value = 0;
    uint16_t config = 0;

    char buffer[16];

    memset(buffer,'-',sizeof(buffer));
    //TCA_PRINT("write count:%ld\n",count);
    /* 判断写入位置是否越界 */
    if (*ppos >= 16) {
        //TCA_PRINT("write out\n");
        return count;
    }

    /* 计算实际可写入的字节数 */
    if (count > 16 - *ppos) {
        count = 16 - *ppos;
    }

    /* 将数据从用户空间复制到设备缓冲区 */
    retval = copy_from_user(buffer + *ppos, buf, count);
    if (retval != 0) {
        return -EFAULT;
    }

    retval |= read_reg(tca->client,TCA9535_CONFIG_REG,&config);
    retval |= read_reg(tca->client,TCA9535_OUTPUT_REG,&output_value);

    if(retval){
        return -ENXIO;
    }

    for(i=0;i<16;i++){
        if(buffer[i]=='r'){
            config |= 0x01<<i;
        }
        else if(buffer[i]=='w'){
            config &= ~(0x01<<i);
        }
        else if(buffer[i]=='1'){
            config &= ~(0x01<<i);
            output_value |= 0x01<<i;
        }
        else if(buffer[i]=='0'){
            config &= ~(0x01<<i);
            output_value &= ~(0x01<<i);
        }
    }
    /* 更新寄存器 */
    retval |= write_reg(tca->client,TCA9535_CONFIG_REG,config);
    retval |= write_reg(tca->client,TCA9535_OUTPUT_REG,output_value);

    if(retval){
        return -ENXIO;
    }

    /* 更新文件偏移量 */
    *ppos += count;
    written = count;

    return written;
}
loff_t llseek(struct file *file, loff_t offset, int whence)
{
    loff_t new_pos = 0;

    switch (whence) {
        case SEEK_SET:
            new_pos = offset;
            break;

        case SEEK_CUR:
            new_pos = file->f_pos + offset;
            break;

        case SEEK_END:
            new_pos = 16 - offset;
            break;

        default:
            return -EINVAL;
    }

    if (new_pos < 0 || new_pos > 16) {
        return -EINVAL;
    }

    file->f_pos = new_pos;
    return new_pos;
}

//设备操作函数
static struct file_operations tca9535_fs_ops={
    .owner = THIS_MODULE,
    .open = open,
    .release = release,
    .read = read,
    .write = write,
    .llseek = llseek
};

/* part 4:实现probe remove of_device_id */

static int tca9535_probe(struct i2c_client *client)
{
    int ret;
    struct tca9535 * tca;

    TCA_PRINT("probe client:0x%02x(7)\n",client->addr);

    tca = (struct tca9535 *)vmalloc(sizeof(struct tca9535));
    if(tca==NULL){
        TCA_PRINT("probe ERROR_ALLOC");
        goto ERROR_ALLOC;
    }
    tca->client = client;
    cdev_init(&tca->tca9535_cdev,&tca9535_fs_ops);

    ret = register_tca9535(tca);
    if(ret==-1){
        TCA_PRINT("probe ERROR_MINOR");
        goto ERROR_MINOR;
    }

    ret = cdev_add(&tca->tca9535_cdev,MKDEV(MAJOR(tca9535_dev_t),ret),1);
    if(ret<0){
        TCA_PRINT("probe ERROR_ADD");
        goto ERROR_ADD;
    }

    tca->tca9535_device = device_create(
            tca9535_class,
            NULL,
            tca->tca9535_cdev.dev,
            NULL,
            "tca9535/chip%02d",
            MINOR(tca->tca9535_cdev.dev));
    if(tca->tca9535_device==NULL){
        TCA_PRINT("probe ERROR_CREATE");
        goto ERROR_CREATE;
    }

    i2c_set_clientdata(client,tca);

    if(tca9535_dev_init(tca)){
        TCA_PRINT("probe ERROR_INIT");
        goto ERROR_INIT;
    }

    return 0;

ERROR_INIT:
    device_destroy(tca9535_class,MINOR(tca->tca9535_cdev.dev));
ERROR_CREATE:
    cdev_del(&tca->tca9535_cdev);
ERROR_ADD:
    unregister_tca9535(tca);
ERROR_MINOR:
    vfree(tca);
ERROR_ALLOC:
    return -1;
}
static int tca9535_remove(struct i2c_client *client)
{
    struct tca9535 *tca = (struct tca9535 *)i2c_get_clientdata(client);

    TCA_PRINT("device remove:%d\n",MINOR(tca->tca9535_cdev.dev));
    device_destroy(tca9535_class,tca->tca9535_cdev.dev);
    cdev_del(&tca->tca9535_cdev);
    unregister_tca9535(tca);
    vfree(tca);
    return 0;
}
static const struct of_device_id tca9535_of_match[] = {
        { .compatible = "i2c,tca9535" },
        { }
};

/* part 3:实现i2c_driver */

static struct i2c_driver tca9535_driver = {
        .probe_new = tca9535_probe,
        .remove = tca9535_remove,
        .driver = {
                .name = "tca9535",
                .of_match_table = tca9535_of_match,
        },
};

/* part 2:实现init exit */

static int tca9535_init(void)
{
    int ret,i;

    TCA_PRINT("tca9535 driver init\n");

    for(i=0;i<DEV_COUNT;i++){
        tca9535_list[i] = NULL;
    }

    spin_lock_init(&tca9535_spinlock);
    //申请设备号
    ret = alloc_chrdev_region(&tca9535_dev_t,0,DEV_COUNT,"tca9535");
    if(ret){
        printk("[tca9535]: dev_t alloc failed!\n");
        goto ERROR_DEV_T;
    }
    TCA_PRINT("MAJOR:%d\n",MAJOR(tca9535_dev_t));

    tca9535_class = class_create(THIS_MODULE,"tca9535");
    if(!tca9535_class){
        printk("[tca9535]: Create tca9535 class failed!\n");\
        goto ERROR_CLASS;
    }
    TCA_PRINT("MAJOR:%d\n",MAJOR(tca9535_dev_t));

    ret = i2c_register_driver(THIS_MODULE,&tca9535_driver);
    if(ret){
        goto ERROR_I2C_REG;
    }
    TCA_PRINT("I2C reg success\n");
    return 0;

    i2c_del_driver(&tca9535_driver);
ERROR_I2C_REG:
    class_destroy(tca9535_class);
ERROR_CLASS:
    unregister_chrdev_region(tca9535_dev_t,DEV_COUNT);
ERROR_DEV_T:
    return -1;
}
static void tca9535_exit(void)
{
    //int i;
    TCA_PRINT("tca9535 driver exit\n");

    i2c_del_driver(&tca9535_driver);
    class_destroy(tca9535_class);
    unregister_chrdev_region(tca9535_dev_t,DEV_COUNT);
}

/* part 1:驱动三件套 */

module_init(tca9535_init);
module_exit(tca9535_exit);
MODULE_LICENSE("GPL");

参考文献

  1. IO扩展芯片TCA9535的驱动程序和中断bug问题的解决
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
cxcc
红包 1 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
cxcc
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区