How to write multiple slave i2c client device driver?
Asked Answered
C

2

14

I am trying to develop a driver for an embedded board. The driver is supposed to open up an interface for v4l2 and communicate with 2 devices using i2c. the driver will act as a master.

I can't seem to understand how i2c_device_id array and i2c_add_driver functions work. I read documentation in kernel source but it won't help me on multiple slave clients.

  • Do I have to have two seperate probe functions?
  • Do i have to call i2c_add_driver two times?
  • If not how am I going to be able to save two different clients to be able to send different bytes to different addresses.

I am pasting my code here. I tried to instantiate two i2c_drivers, called i2c_driver_add two times and implemented i2c probe seperately. The code doesn't work telling me that foo1 is already registered when it calls i2c_add_driver for the second time.

I defined two blocks under i2c1 in my dts file like:

&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>;
    };

...

Two blocks are exactly the same except their names.

In the driver file I instantiated following structs:

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,
};

Below are my init and exit functions:

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);

Below is my probe function. I have two copies for it for both slaves.

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;
}
Claire answered 7/9, 2016 at 12:49 Comment(7)
I think your confusing "driver" and "device". You register 1 driver (per device type), which will handle multiple devices, depending on their I2C slave addresses. Of course the driver has to be written to support >1 device, but any decent driver should.Weatherby
@Weatherby yeah i can understand that but registers are hard-coded into device trees. so instantiating one only allows me to read/write from/to one slave.Orthopteran
I don't think you do. You only need one driver with one probe function. Device driver system will then call that probe for each of matching DT definitions. I don't have a good reference handy, sorry, have you checked ldd3? kernel.org/doc/Documentation/driver-model/platform.txt also contains some info.Weatherby
You can have one I2C device as "primary" in the device tree to instantiate the driver, then put the info of second device (i2c bus, slave address, pins) as a sub-node in the primary device's node and read those info in the primary device probe function. Basically you add secondary device's i2c information as additional information to the primary device's device tree node.Default
I guess you are trying to make a dual camera setup for imx6. Usually just like you just have 2 hardware devices and two driver instances and they export two v4l2 devices to user space, just like you connect two USB webcams to a computer. But the way imx6 v4l2 "capture" driver allocates driver data statically makes it impossible to be instantiated twice. So IMO you are on the wrong track trying to manage two camera in one driver instance, rather you should fix the driver to be able to be instantiated twice and let each camera have its own driver instances Let user space decide how to use them.Default
@Default I am interfacing with a camera that supplies 1 output data but requires separate messages to 2 separate i2c slaves residing in the system. By the way I am afraid I didn't understand your comment about primary and sub nodesOrthopteran
@MertCanErgün Then I think you are OK.Default
R
9

This answer is late by 5 months but hopefully it will help someone else who had the same issue (as I did) and could not find a suitable answer.

In short the solution is to use a minor number to represent each slave. Your driver will look up that minor number in your stored list of clients to obtain the proper i2c_client.

Long version Your I2C driver will eventually probably be a character device driver for such an unique device. Otherwise a framework (such as hwmon) may already be implemented and handling multiple slaves is already done by the framework so you need not worry about it. See http://lxr.free-electrons.com/source/drivers/hwmon/ for examples.

Now assuming it is a character device driver, in your driver __init you need to allocate as many minor numbers as slave devices:

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

Now onto your MODULE_DEVICE_TABLE. Enter the entries for each slave remembering that the string must match the device tree compatible entry. The 2nd field is a number, we will use it as both an unique identifier and a minor number (this is the trick):

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

Ok with that in place, your .probe function will be called for each matching device-tree entry. When the .probe function is called, Linux passes in the i2c_client pointer it has instantiated for you. Here's the other part of the trick, have a global table that stores these individual i2c_client pointers. The index of the global table is the minor number. The minor number is the id->driver_data that is also passed in, which was the number you assigned in the foo_idtable before.

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! */
}

Hopefully you are catching on now. When you insmod your .ko, you want to make multiple nodes, one for each minor number:

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

Now when your userspace code tries to use your driver, it will open the file that corresponds to the correct minor number (slave device). In userspace you would do something like:

/* 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);

Your .open implementation will get called.

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];
}

Then for example your userspace code makes a call to the driver's ioctl:

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

In your driver's ioctl implementation:

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 */
}

That's it :). I hope that makes sense. It's a long explanation but I hope I was thorough but not too confusing. The biggest problem is that we have a global table that stores the i2c_cient pointers, but I can't think of a way not to have it since .probe and .open have no way to pass parameters between each other. If anyone has a better solution let me know.

Resolute answered 14/2, 2017 at 20:46 Comment(1)
that looks far more elegant than my workaround which registers one device but changes device when needed which can be confusingOrthopteran
C
2

This is a workaround I am using right now, I will not accept this one as answer as this method doesn't feel right.

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);
Claire answered 9/9, 2016 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.