I2C linux 驱动程序找不到设备

I2C linux driver can't find device

我写了一个小的 i2c 驱动程序只是为了测试目的。

我正在使用 Rasperry Pi 3,并且已将两个 ssd1306 OLED displays 连接到 GPIO 排针上的 I2C 引脚。我可以使用地址为 0x3c 和 0x3d 的 i2c-tools 连接到它。

我可以使用 i2c-set 将数据发送到显示器:

i2cset -y 1 0x3c [data]
i2cset -y 1 0x3d [data]

命令

i2cdetect -y 1

给我以下输出:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c 3d -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                        

我正在使用以下驱动程序来查看它的作用以及 Linux 中的 I2C 可能如何工作:

#include "i2c_test.h"

#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/types.h>

#include <linux/i2c.h>
#include <linux/mod_devicetable.h>

#define print_client(c) printk("%s: Client (Name: %s), (Flags: %x), (Addr: %x), (Adapter: ), (Device: ), (IRQ: %d)\n",\
                                __FUNCTION__, c->name, c->flags, c->addr, c->irq)
#define print_board(b) printk("%s: Board (Type: %s), (Flags: %x), (Addr: %x), (IRQ: %d)\n",\
                                __FUNCTION__, b->type, (unsigned int)b->flags, (unsigned int)b->addr, b->irq)

static struct i2c_device_id ssd1306_idtable[] = {
    { "ssd1306", 0 },
    {}
};

const unsigned short ssd1306_address_list[] = {
    0x3c,
    0x3d,
    0x7a,
    0x78,
};


MODULE_DEVICE_TABLE(i2c, ssd1306_idtable);

struct dev_pm_ops ssd1306_pm_ops = {
// Don't know how to use
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
int ssd1306_probe(struct i2c_client* client, const struct i2c_device_id * dev_id)
{
    print_client(client);
    return 0;
}
#else //LINUX_VERSION_CODE > KERNEL_VERSION(4,10,0)
int ssd1306_probe_new(struct i2c_client* client)
{
    print_client(client);
    return 0;
}
#endif // Kernel version

int ssd1306_remove(struct i2c_client* client)
{
    print_client(client);
    return 0;
}

void ssd1306_shutdown(struct i2c_client* client)
{
    print_client(client);
}

int ssd1306_detect(struct i2c_client* client, struct i2c_board_info* board_info)
{
    print_client(client);
    print_board(board_info);
    return 0;
}

static struct i2c_driver ssd1306_driver = {
    .driver = {
        .name   = "i2c_test",
        .pm = &ssd1306_pm_ops
    },

    .id_table   = ssd1306_idtable,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
    .probe      = ssd1306_probe,
#else //LINUX_VERSION_CODE > KERNEL_VERSION(4,10,0)
    .probe_new = ssd1306_probe_new, //needs Kernel 4.10 or later
#endif //Kernel Version
    .remove     = ssd1306_remove,
    .class      = I2C_CLASS_HWMON, // correct??
    .detect     = ssd1306_detect,
    .address_list   = ssd1306_address_list,

    //.command  = ssd1306_command,
    .shutdown   = ssd1306_shutdown
};


static int __init mod_init(void)
{
    printk("init " __FILE__ "\n");
    i2c_add_driver(&ssd1306_driver);
    printk("driver added!\n");
    return 0;
}

static void __exit mod_exit(void)
{
    printk("remove driver\n");
    i2c_del_driver(&ssd1306_driver);
    printk("driver removed\n");
    printk("exit " __FILE__ "\n");
}

module_init( mod_init );
module_exit( mod_exit );

MODULE_LICENSE("GPL");

我通过 dmesg 得到以下输出:

[ 1676.649683] init /home/pi/projects/playground/i2c_test/i2c_test.c
[ 1676.649790] driver added!
[ 1812.043182] remove driver
[ 1812.043301] driver removed
[ 1812.043306] exit /home/pi/projects/playground/i2c_test/i2c_test.c

如果有人知道该怎么做或我做错了什么并且可以帮助我,那就太好了。

谢谢

p0kR

加载模块时不会自动探测 I2C 设备(I2C 不为此提供任何标准方法)。因此,要调用您的驱动程序的探测函数,您需要告诉内核您的驱动程序应该处理哪个 I2C 地址。使用 sysfs 的简单方法是:

# echo [your_device_name] [your_device_i2c_addr] > /sys/bus/i2c/devices/i2c-[i2c_bus_number]/new_device

(方括号中的部分用适当的数字和名称代替)

在您的特定情况下,它将是

 # echo ssd1306 0x3c > /sys/bus/i2c/devices/i2c-1/new_device

请注意,内核不会为您做任何检查,因此无论 I2C 设备是否连接,您的 probe 函数都会被调用。

其他方法包括,例如,在设备树数据中指定您的设备和驱动程序。有关详细信息,请参阅 linux kernel documentation

编辑: 实际上在某些情况下存在自动检测(然后调用 detect 函数),但 I2C 总线必须同意(和设备 class 必须匹配,等等)。但据我所知,这种方法并不用于一般设备,而是用于 PC 等内部监控传感器。在大多数情况下,明确指定你的设备是首选方法。

首先,I2C 设备不像 USB 设备那样被动态枚举。 如果您的驱动程序是内置的,显然您的驱动程序将被调用。无需通过 sysfs 接口访问它。 如果您正在使用设备树,请在 .dts 中添加您的 i2c 设备详细信息,并让您的驱动程序在启动期间被调用。