【问题标题】:How to write multiple slave i2c client device driver?如何编写多个从 i2c 客户端设备驱动程序?
【发布时间】:2016-09-07 12:49:06
【问题描述】:

我正在尝试为嵌入式板开发驱动程序。驱动程序应该为 v4l2 打开一个接口,并使用 i2c 与 2 个设备进行通信。司机将充当主人。

我似乎无法理解 i2c_device_id 数组和 i2c_add_driver 函数的工作原理。我阅读了内核源代码中的文档,但它在多个从客户端上对我没有帮助。

  • 我必须有两个单独的探测功能吗?
  • 我必须拨打i2c_add_driver 两次吗?
  • 如果不是,我将如何保存两个不同的客户端,以便能够将不同的字节发送到不同的地址。

我在这里粘贴我的代码。我尝试实例化两个i2c_drivers,两次调用i2c_driver_add,分别实现i2c probe。当第二次调用i2c_add_driver 时,代码无法告诉我foo1 已经注册。

我在我的 dts 文件中的 i2c1 下定义了两个块,例如:

&i2c1 {

...
    foo0: foo0@00 {
        compatible = "bar,foo0";
        reg = <0x00>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ipu1_2>;
        clocks = <&clks IMX6QDL_CLK_CKO>;
        clock-names = "csi_mclk";
        DOVDD-supply = <&vgen4_reg>; /* 1.8v */
        AVDD-supply = <&vgen3_reg>;  /* 2.8v, on rev C board is VGEN3,
                        on rev B board is VGEN5 */
        DVDD-supply = <&vgen2_reg>;  /* 1.5v*/
        pwn-gpios = <&gpio1 16 1>;   /* active low: SD1_DAT0 */
        rst-gpios = <&gpio1 17 0>;   /* active high: SD1_DAT1 */
        csi_id = <0>;
        mclk = <24000000>;
        mclk_source = <0>;
    };

    foo1: foo1@02 {
        compatible = "bar, foo1";
        reg = <0x02>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ipu1_2>;
        clocks = <&clks IMX6QDL_CLK_CKO>;
        clock-names = "csi_mclk";
        DOVDD-supply = <&vgen4_reg>; /* 1.8v */
        AVDD-supply = <&vgen3_reg>;  /* 2.8v, on rev C board is VGEN3,
                        on rev B board is VGEN5 */
        DVDD-supply = <&vgen2_reg>;  /* 1.5v*/
        pwn-gpios = <&gpio1 16 1>;   /* active low: SD1_DAT0 */
        rst-gpios = <&gpio1 17 0>;   /* active high: SD1_DAT1 */
        csi_id = <0>;
        mclk = <24000000>;
        mclk_source = <0>;
    };

...

除了名称之外,两个块完全相同。

在驱动文件中我实例化了以下结构:

static const struct i2c_device_id foo_id[] = {
    {"foo0", 0},
    {"foo1", 1},
    {},
};

static struct i2c_driver foo0_i2c_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "foo0",
    },
    .probe = foo0_probe,
    .remove = foo0_remove,
    .id_table = foo_id,
};

static struct i2c_driver foo1_i2c_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "foo1",
    },
    .probe = foo1_probe,
    .remove = foo1_remove,
    .id_table = foo_id,
};

下面是我的initexit 函数:

MODULE_DEVICE_TABLE(i2c, foo_id);

static __init int foo_init(void)
{
    u8 err;

    err = i2c_add_driver(&foo0_i2c_driver);
    if (err != 0)
        pr_err("%s:driver registration failed i2c-slave0, error=%d\n",
            __func__, err);

    err = i2c_add_driver(&foo1_i2c_driver);
    if (err != 0)
        pr_err("%s:driver registration failed i2c-slave1, error=%d\n",
            __func__, err);

    return err;
}
static void __exit foo_clean(void)
{
    if((&foo0_i2c_driver) != NULL && i2c0initialized)
    {
        i2c_del_driver(&foo0_i2c_driver);
        i2c0initialized = 0;
    }
    if((&foo1_i2c_driver) != NULL && i2c1initialized)
    {
        i2c_del_driver(&foo1_i2c_driver);
        i2c1initialized = 0;
    }
}

module_init(foo_init);
module_exit(foo_clean);

下面是我的probe 函数。我为这两个奴隶准备了两份副本。

static int foo_probe(struct i2c_client *client,
                          const struct i2c_device_id *device_id)
{
    struct pinctrl *pinctrls;
    struct device *dev = &client->dev;

    int ret = 0;

    pinctrls = devm_pinctrl_get_select_default(dev);
    if(IS_ERR(pinctrls))
    {
        dev_err(dev, "pinctrl setup failed\n");
        return PTR_ERR(pinctrls);
    }

    memset(&foo_data, 0, sizeof(foo_data));
    foo_data.sensor_clk = devm_clk_get(dev, "csi_mclk");
    if(IS_ERR(foo_data.sensor_clk))
    {
        dev_err(dev, "get mclk failed\n");
        return PTR_ERR(foo_data.sensor_clk);
    }

    ret = of_property_read_u32(dev->of_node, "mclk", &(foo_data.mclk));
    if(ret < 0)
    {
        dev_err(dev, "mclk frequency is invalid\n");
        return ret;
    }

    ret = of_property_read_u32(dev->of_node, "mclk_source",
                               (u32 *)&(foo_data.mclk_source));
    if(ret < 0)
    {
        dev_err(dev, "mclk source is invalid\n");
        return ret;
    }

    ret = of_property_read_u32(dev->of_node, "csi_id", &(foo_data.csi));
    if(ret < 0)
    {
        dev_err(dev, "csi_id invalid\n");
        return ret;
    }

    clk_prepare_enable(foo_data.sensor_clk);
    i2c_client0 = client;

    /* custom data structures are set here */   

    foo_reset();

    ret = foo_get_id();

    if(ret < 0 /* || ret != foo_ID */)
    {
        clk_disable_unprepare(foo_data.sensor_clk);
        pr_warning("foo is not found\n");
        return -ENODEV;
    }

    clk_disable_unprepare(foo_data.sensor_clk);
    foo_int_device.priv = &foo_data;
    ret = v4l2_int_device_register(&foo_int_device);

    pr_info("foo is found\n");
    i2c0initialized = 1;
    return ret;
}

【问题讨论】:

  • 我认为您混淆了“驱动程序”和“设备”。您注册了 1 个驱动程序(每个设备类型),它将处理多个设备,具体取决于它们的 I2C 从地址。当然,必须编写驱动程序以支持 >1 设备,但任何体面的驱动程序都应该。
  • @domen 是的,我可以理解,但是寄存器被硬编码到设备树中。所以实例化一个只允许我读取/写入一个奴隶。
  • 我不认为你这样做。您只需要一个具有一种探测功能的驱动程序。然后,设备驱动程序系统将为每个匹配的 DT 定义调用该探测器。我手头没有很好的参考资料,抱歉,你检查过 ldd3 吗? kernel.org/doc/Documentation/driver-model/platform.txt 也包含一些信息。
  • 您可以在设备树中将一个 I2C 设备作为“主要”设备来实例化驱动程序,然后将第二个设备的信息(i2c 总线、从地址、引脚)作为子节点放入主设备的节点并在主设备探测功能中读取这些信息。基本上,您将辅助设备的 i2c 信息作为附加信息添加到主设备的设备树节点。
  • 我猜您正在尝试为 imx6 进行双摄像头设置。通常就像您只有 2 个硬件设备和两个驱动程序实例,它们将两个 v4l2 设备导出到用户空间,就像您将两个 USB 网络摄像头连接到计算机一样。但是imx6 v4l2“捕获”驱动程序静态分配驱动程序数据的方式使得不可能被实例化两次。因此,IMO 试图在一个驱动程序实例中管理两个摄像头时,您走错了路,而是应该修复驱动程序,使其能够被实例化两次,并让每个摄像头都有自己的驱动程序实例,让用户空间决定如何使用它们。

标签: c linux kernel client i2c


【解决方案1】:

这个答案迟了 5 个月,但希望它能帮助遇到同样问题(和我一样)但找不到合适答案的人。

简而言之解决方案是使用一个次要编号来表示每个从站。您的驱动程序将在您存储的客户端列表中查找该次要编号以获得正确的 i2c_client。

加长版 您的 I2C 驱动程序最终可能会成为这种独特设备的字符设备驱动程序。否则,可能已经实现了一个框架(例如 hwmon),并且框架已经完成了对多个从属设备的处理,因此您不必担心。有关示例,请参阅http://lxr.free-electrons.com/source/drivers/hwmon/

现在假设它是一个字符设备驱动程序,在您的驱动程序 __init 中,您需要分配与从设备一样多的次要编号:

alloc_chrdev_region(&dev, *MINOR_START*, *NUM_DEVICES*, name)    
/* for each minor or slave device, do cdev_init and cdev_add */

现在转到您的MODULE_DEVICE_TABLE。输入每个从站的条目,记住该字符串必须与设备树兼容条目匹配。第二个字段是一个数字,我们将它用作唯一标识符和次要数字(这是诀窍):

struct i2c_device_id foo_idtable[] = {
    { "foo_1", 0 },
    { "foo_2", 1 },
    { },
};
MODULE_DEVICE_TABLE(i2c, foo_idtable);

好了,您的.probe 函数将为每个匹配的设备树条目调用。当调用.probe 函数时,Linux 会传入它为您实例化的 i2c_client 指针。这是技巧的另一部分,有一个全局表来存储这些单独的 i2c_client 指针。全局表的索引是次要编号。次要号码是同样传入的id-&gt;driver_data,也就是你之前在foo_idtable中分配的号码。

static int foo_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    /* Store the i2c_client pointer in a global table using minor number as index 
     * make sure to allocate the global table dynamically */
    that_table[id->driver_data] = client;
    /* The id->driver_data is the minor number! */
}

希望你现在能赶上。 当你安装你的 .ko 时,你想要创建多个节点,每个次要编号一个:

insmod foo.ko
make_dir /dev/foo
find_major foo
make_node /dev/foo/foo_0 c <major> 0
make_node /dev/foo/foo_1 c <major> 1

现在,当您的用户空间代码尝试使用您的驱动程序时,它将打开对应于正确次要编号(从设备)的文件。在用户空间中,您会执行以下操作:

/* Accessing slave #0 */
int fd = open("/dev/foo/foo_0", O_RDWR);

/* Accessing slave #1 */
int fd_1 = open("/dev/foo/foo_1", O_RDWR);

您的 .open 实现将被调用。

static int foo_driver_open(struct inode *inode, struct file *filep)
{
    int minor_num = MINOR(inode->i_rdev);
    /* remember the global table we had before that was indexed using the minor number?
     * Let's get the i2c_client stored there and get filep->private_data
     * to point to that i2c_client. Then subsequent read/write/ioctl can just
     * obtain the i2c_client from filep->private_data */
    filep->private_data = that_table[minor_num];
}

然后,例如,您的用户空间代码调用驱动程序的 ioctl:

ioctl(fd, FOO_IOCTL_DO_READ, &msg);
ioctl(fd_1, FOO_IOCTL_DO_WRITE, &msg);

在您的驱动程序的 ioctl 实现中:

long foo_driver_ioctl(struct file *filep, unsinged int cmd, unsigned long arg)
{
    /* the filep->private_data has the i2c_client pointer! yay! */
    struct i2c_client *client = filep->private_data;

    /* now you can talk to your slave device with the i2c_client knowing
     * it is the correct i2c_client */
}

就是这样:)。 我希望这是有道理的。这是一个很长的解释,但我希望我是彻底的,但不要太混乱。最大的问题是我们有一个存储 i2c_cient 指针的全局表,但我想不出没有它的方法,因为.probe.open 无法在彼此之间传递参数。如果有人有更好的解决方案,请告诉我。

【讨论】:

  • 这看起来比我注册一个设备但在需要时更改设备的解决方法要优雅得多,这可能会令人困惑
【解决方案2】:

这是我现在正在使用的一种解决方法,我不会接受这个作为答案,因为这种方法感觉不对。

struct i2c_client *foo_i2c_client;

static int foo_probe(struct i2c_client *client,
                          const struct i2c_device_id *device_id)
{
    foo_i2c_client = client;
    // implementation
}

static int foo_remove(struct i2c_client *client)
{
    // implementation
}

static const struct i2c_device_id foo_id[] = {
    {"foo0", 0},
    {},
};

static struct i2c_driver foo0_i2c_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "foo0",
    },
    .probe = foo0_probe,
    .remove = foo0_remove,
    .id_table = foo_id,
};

static int send_cmd(u8 slv_addr, u8 *buffer, u16 size)
{
    foo_i2c_client->address = slv_addr;
    i2c_master_send(foo_i2c_client, buffer, size);
    // rest of implementation
} 

MODULE_DEVICE_TABLE(i2c, foo_id);

module_i2c_driver(foo0_i2c_driver);

【讨论】:

    猜你喜欢
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    • 2011-04-01
    • 1970-01-01
    • 2018-07-24
    • 2018-02-16
    相关资源
    最近更新 更多