Как написать несколько ведомых драйверов устройств i2c?

Я пытаюсь разработать драйвер для встроенной платы. Предполагается, что драйвер откроет интерфейс для v4l2 и установит связь с двумя устройствами, использующими i2c . водитель будет действовать как мастер.

Кажется, я не понимаю, как i2c_device_id массивы i2c_add_driver и i2c_add_driver . Я прочитал документацию в исходном коде ядра, но это не поможет мне на нескольких подчиненных клиентах.

  • Должен ли я иметь две отдельные функции зондирования?
  • Нужно ли мне два раза звонить i2c_add_driver ?
  • Если нет, то как я могу спасти двух разных клиентов, чтобы отправлять разные байты на разные адреса.

Я вставляю свой код здесь. Я попытался создать экземпляр двух i2c_drivers , названных i2c_driver_add два раза, и внедрил i2c-зонд отдельно. Код не работает, сообщая мне, что foo1 уже зарегистрирован, когда он вызывает i2c_add_driver во второй раз.

Я определил два блока под i2c1 в моем файле dts, например:

 &i2c1 { ... foo0: foo0@00 { compatible = "bar,foo0"; reg = ; pinctrl-names = "default"; pinctrl-0 = ; clocks = ; clock-names = "csi_mclk"; DOVDD-supply = ; /* 1.8v */ AVDD-supply = ; /* 2.8v, on rev C board is VGEN3, on rev B board is VGEN5 */ DVDD-supply = ; /* 1.5v*/ pwn-gpios = ; /* active low: SD1_DAT0 */ rst-gpios = ; /* active high: SD1_DAT1 */ csi_id = ; mclk = ; mclk_source = ; }; foo1: foo1@02 { compatible = "bar, foo1"; reg = ; pinctrl-names = "default"; pinctrl-0 = ; clocks = ; clock-names = "csi_mclk"; DOVDD-supply = ; /* 1.8v */ AVDD-supply = ; /* 2.8v, on rev C board is VGEN3, on rev B board is VGEN5 */ DVDD-supply = ; /* 1.5v*/ pwn-gpios = ; /* active low: SD1_DAT0 */ rst-gpios = ; /* active high: SD1_DAT1 */ csi_id = ; mclk = ; mclk_source = ; }; ... 

Два блока одинаковы, кроме их имен.

В файле драйвера я создавал следующие структуры:

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

Ниже приведены мои функции init и exit :

 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 of_node, "mclk_source", (u32 *)&(foo_data.mclk_source)); if(ret 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; } 

    Этот ответ опаздывает на 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 функция .probe , Linux передает указатель i2c_client, который он создал для вас. Вот и другая часть трюка, есть глобальная таблица, в которой хранятся эти отдельные указатели i2c_client. Индекс глобальной таблицы является второстепенным. id->driver_data номер – id->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  0 make_node /dev/foo/foo_1 c  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 имеют возможности передавать параметры между собой. Если у кого-то есть лучшее решение, дайте мне знать.

    Это обходной путь, который я использую сейчас, я не буду принимать этот ответ в качестве ответа, поскольку этот метод не кажется правильным.

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