Search code examples
linuxkerneli2csysfs

i2c kernel driver - Binding between sysfs kobject and i2c_client


I am working on an I2C kernel driver and would like to provide a sysfs file interface in a new folder - /sys/devices/MySensor. However, when I do this I don't know how to associate the i2c client with the new kobject.

Consequently, when my device attribute functions are called, the device object passed in does not allow me to retrieve the registered i2c client.

I declare my attribute as follows:

static ssize_t my_sensor_do_something(struct device *dev, struct device_attribute *attr, char *buf)

{
    struct i2c_client *client;
    struct my_sensor_data *data;
    int size = 0;

    client = to_i2c_client(dev);

    my_sensor_dbgmsg("Client Address:0x%02x\n", client->addr);

    data = i2c_get_clientdata(client);

    return 0
}
static DEVICE_ATTR(do_something, S_IRUGO, my_sensor_do_something, NULL);

static struct attribute *my_sensor_attributes[] = {
    &dev_attr_do_something.attr,
    NULL
};

static const struct attribute_group my_sensor_attr_group = {
    .attrs = my_sensor_attributes,
};

Then, in my probe function, create my subfolder

struct device *my_dev = root_device_register("my_sensor");
err = sysfs_create_group(&my_dev->kobj, &my_sensor_attr_group);

The sub-folder and do_something file is created in /sys/kernel/, however when do_something() is called, the attempt to retrieve the I2C client fails - client->addr is 0 and i2c_get_client_data returns null.

For info, the i2c device is defined in a device tree and I can successfully add device attributes to the existing folder

err = sysfs_create_group(client->dev.kobj, &my_sensor_attr_group);
/sys/bus/i2c/devices/i2c-7/7-004c/

Apologies if this question is vague or lacking sufficient detail. I'm relatively new to this.

Does anyone have an idea what I am missing when I create a new sysfs folder, to associate this with my registered i2c client?

Thanks


Solution

  • You can put the client on my_dev at initialization time.

    dev_set_drvdata(my_dev, client);
    

    Then in the my_sensor_do_something function, use dev_get_drvdata to take the client out

    client = dev_get_drvdata(dev);
    

    The complete example is as follows

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/i2c.h>
    #include <linux/slab.h>
    
    static ssize_t my_sensor_do_something(struct device *dev,
                                      struct device_attribute *attr, char *buf)
    {
        struct i2c_client *client;
        void *data;
    
        client = dev_get_drvdata(dev);
        data = i2c_get_clientdata(client);
        pr_info("Client Address:0x%02x Data:%p\n", client->addr, data);
    
        return 0;
    }
    
    static DEVICE_ATTR(do_something, 0444, my_sensor_do_something, NULL);
    
    static struct attribute *my_sensor_attributes[] = {
        &dev_attr_do_something.attr,
        NULL
    };
    
    static const struct attribute_group my_sensor_attr_group = {
        .attrs = my_sensor_attributes,
    };
    
    static struct device *my_dev;
    static void my_sensor_create(struct i2c_client *client)
    {
        int err;
    
        my_dev = root_device_register("my_sensor");
        dev_set_drvdata(my_dev, client);
        err = sysfs_create_group(&my_dev->kobj, &my_sensor_attr_group);
        if (err)
                pr_info("sysfs_create_group failure.\n");
    }
    
    struct test_device {
        struct i2c_client *client;
    };
    
    static int test_i2c_probe(struct i2c_client *client,
                          const struct i2c_device_id *id)
    {
        struct test_device *dev;
    
        dev = kzalloc(sizeof(struct test_device), GFP_KERNEL);
        if (dev == NULL)
                return -ENOMEM;
    
        dev->client = client;
        i2c_set_clientdata(client, dev);
        my_sensor_create(client);
    
        return 0;
    }
    
    static int test_i2c_remove(struct i2c_client *client)
    {
        struct test_client *dev = i2c_get_clientdata(client);
    
        if (my_dev)
                root_device_unregister(my_dev);
    
        kfree(dev);
        return 0;
    }
    
    static const struct i2c_device_id test_i2c_id[] = {
        {"test_i2c_client", 0},
        {}
    };
    
    static struct i2c_driver test_i2c_driver = {
        .driver   = { .name = "test_i2c_client", },
        .probe    = test_i2c_probe,
        .remove   = test_i2c_remove,
        .id_table = test_i2c_id,
    };
    
    static int __init test_i2c_init_driver(void)
    {
        return i2c_add_driver(&test_i2c_driver);
    }
    
    static void __exit test_i2c_exit_driver(void)
    {
        i2c_del_driver(&test_i2c_driver);
    }
    
    module_init(test_i2c_init_driver);
    module_exit(test_i2c_exit_driver);
    
    MODULE_LICENSE("GPL");