关于我在驱动orangepi-r1-plus-lts的i2c0接口时遇到的问题

背景

最近我在学习Linux驱动开发,在学完i2c驱动后,打算用手里的orangepi-r1-plus-lts,做一个i2c屏幕,来显示系统的状态信息。

条件

orangepi-r1-plus-lts引脚很少,但庆幸的是开发板有一个i2c引脚。这里我使用的是Linux-7.2.3的内核。

编写设备树

编写驱动前,需要添加设备树节点。这里我们使用的i2c引脚是GPIO2_D0和GPIO2_D1,对应的是rk3328的i2c0。因为我们在i2c0下添加如下节点信息:

复制代码
&i2c0 {
    status = "okay";
    ssd1306: ssd1306@3c {
        compatible = "gwen,ssd1306";
        reg = <0x3c>;
    };
};

编写测试驱动

复制代码
static int oled_probe(struct i2c_client *client){
    printk("oled_probe\n");
    int ret = 0;
    //获取设备号
    ret = alloc_chrdev_region(&ssd1306_dev.devno,0,1,NAME);
    if(ret < 0) goto err_alloc_devn;
    //注册设备
    cdev_init(&ssd1306_dev.cdev,&fops);
    ret = cdev_add(&ssd1306_dev.cdev,ssd1306_dev.devno,1);
    if(ret < 0) goto err_cdev_add;
    //设置驱动类别
    ssd1306_dev.class = class_create(NAME);
    if(IS_ERR(ssd1306_dev.class)){
        ret = -EFAULT;
        goto err_class_create;
    }
    //创建设备节点
    ssd1306_dev.device = device_create(ssd1306_dev.class,NULL,ssd1306_dev.devno,NULL,NAME);
    if(IS_ERR(ssd1306_dev.device)){
        ret = -EFAULT;
        goto err_device_create;
    }
    ssd1306_dev.private_data = client;
    return 0;
    err_device_create:
    class_destroy(ssd1306_dev.class);
    err_class_create:
    cdev_del(&ssd1306_dev.cdev);
    err_cdev_add:
    unregister_chrdev_region(ssd1306_dev.devno,1);
    err_alloc_devn:
    return ret;
}

static void oled_remove(struct i2c_client *client){
    device_destroy(ssd1306_dev.class,ssd1306_dev.devno);
    class_destroy(ssd1306_dev.class);
    cdev_del(&ssd1306_dev.cdev);
    unregister_chrdev_region(ssd1306_dev.devno,1);
    printk("oled_remove\n");
}

//传统匹配模式
static struct i2c_device_id ssd1306_id[] = {
    {
        .name = "gwen,ssd1306",
        .driver_data = 0
    },
    {}
};
//设备树匹配
static struct of_device_id ssd1306_of_match[] = {
    {
        .compatible = "gwen,ssd1306",
    },
    {}
};

static struct i2c_driver driver = {
    .probe = oled_probe,
    .remove = oled_remove,
    .driver = {
        .name = "gwen,ssd1306",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(ssd1306_of_match)
    },
    .id_table = ssd1306_id
};

static int __init oled_init(void)
{
    int ret;

    ret = i2c_add_driver(&driver);
    if (ret) {
        pr_err("ssd1306: i2c_add_driver failed: %d\n", ret);
        return ret;
    }

    pr_info("ssd1306: i2c driver registered\n");
    return 0;
}

static void __exit oled_exit(void){
    i2c_del_driver(&driver);
}

module_init(oled_init);
module_exit(oled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gwen");

这里省略头文件和fops的具体实现。

问题出现

编译后,加载模块。但是probe函数居然没有被调用。因此可确定各,驱动没有和设备树的设备节点匹配成功。后面调试时,我把ssd1306节点又添加到了rk3328的i2c1,i2c2已经i2c3,都是可以触发probe函数的。这是基本可以确定,测试驱动代码时没有问题的,问题应该是在设备树上的了。

解决问题

经过我和GPT的共同努力下,终于找到的问题所在。

在内核提供的rk3328.dtsi设备树下。GPIO2_D0和GPIOD2_D1这两个GPIO被复用为了eth1的状态指示灯控制引脚了。如下图

因此,如果我们要使用orangepi r1-plus-lts的引脚提供的i2c,就必须舍弃eth0网口的状态灯。这里我们屏蔽掉pinctrl-nams 和pinctls-0就可以正常加载驱动了。

最终效果

加载驱动时,成功调用probe函数,退出时也调用了remove函数