С I2C в Linux вполне можно работать из пространства пользователя. Тем не менее это не очень удобно. Весьма удобнее и правильнее сделать модуль ядра.
Для создания драйвера в ядре Linux предусмотрены вспомогательные макросы. Если Вам не нужно ничего выполнять при инициализации модуля, можно воспользоваться макросом module_i2c_driver.
#define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \ i2c_del_driver)
В качестве параметра ему передается структура i2c_driver, в которой указывается имя устройства и указатели на функции probe, remove.
Макрос module_driver в свою очередь определен так:
#define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
Как видно, в нем определены функции инициализации и деинициализации модуля ядра. Из которых вызываются функции i2c_register_driver(THIS_MODULE, driver) (она же макрос i2c_add_driver(driver)) и i2c_del_driver(struct i2c_driver *) соответственно.
Что использовать решать Вам, я воспользуюсь макросом module_i2c_driver. Как мы помним у меня в качестве подопытного часы ds1307. В Linux уже есть драйвер для них, но мы же не ищем легких путей!?
Параметром макросу передаем структуру:
static struct i2c_driver ds1307_driver = { .driver = { .name = "blabla_ds1307", }, .probe = ds1307_probe, .remove = ds1307_remove, .id_table = ds1307_id, };
Указываем имя драйвера, функцию probe и remove, таблицу идентификаторов поддерживаемых устройсв.
static const struct i2c_device_id ds1307_id[] = { { "blabla_ds1307", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ds1307_id);
Имя устройства используется при поиске подходящего драйвера, второй элемент структуры — параметр, который можно потом достать из этой таблицы при необходимости.
При успешном поиске драйвера вызывается его функция probe. В ней Вы должно проинициализировать устройство или же вычитать его конфиг и дать команду начала работы с ним (запустить поток или еще чего). Мы такого делать не будем, вычитаем данные из часов и выведем их в лог.
Для работы с шиной I2C в ядре предусмотрены функции:
int i2c_master_send(const struct i2c_client *client, const char *buf, int count); int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
Следует отметить что count должен быть менее 64К, т.к. в структуре сообщения i2c в недрах ядра длина 16 битная.
Для хранения данных я сделал такую структуру:
struct ds1307 { u8 sec; u8 min; u8 hour; u8 day; u8 date; u8 month; u8 year; }; static int ds1307_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; const char wr_buf[1] = {0}; struct ds1307 *buffer = kmalloc(sizeof(struct ds1307), GFP_KERNEL); printk(KERN_NOTICE "Blablai2c probing!\n" ); ret = i2c_master_send(client, wr_buf, 1); if (ret < 0) return ret; ret = i2c_master_recv(client, (char *)buffer, sizeof(struct ds1307)); if (ret < 0) return ret; print_hex_dump(KERN_DEBUG, "i2c buffer: ", DUMP_PREFIX_OFFSET, 16, 1, buffer, sizeof(struct ds1307), true); return 0; }
В функции probe выделяем память для данных, которые будем читать из устройства. Конечно же выводим сообщение в лог ядра. Шлем ноль из wr_buf, чтобы установить указатель записи/чтения в часах. И читаем в выделенную при помощи kmalloc память данные по i2c шине. Если функция вернула число меньше 0 значит что-то пошло не так, в случае успеха будет возвращено количество отправленных байт.
Если считали успешно, выводим в лог при помощи функции print_hex_dump.
void print_hex_dump(const char *level, const char *prefix_str, int prefix_type, int rowsize, int groupsize, const void *buf, size_t len, bool ascii)
Очень удобная функция. Параметры:
- level — уровень сообщения, аналогично printk.
- prefix_str — строка, которая будет выведена перед буфером.
- prefix_type — флаг вывода префикса смещения — DUMP_PREFIX_OFFSET, адреса — DUMP_PREFIX_ADDRESS, вывода без префикса — DUMP_PREFIX_NONE.
- rowsize — количество символов выводимых в строку.
- groupsize — вывод по 1,2,4,8 байт.
- buf — данные для вывода.
- len — количество байт в буфере.
- ascii — выводить данные в виде строки ascii после дампа данных.
Теперь скормите новое устройство ядру.
Overlay для устройсва:
/dts-v1/; /plugin/; / { compatible = "brcm,bcm2708"; fragment@0 { target = <&i2c1>; __overlay__ { #address-cells = <1>; #size-cells = <0>; status = "okay"; blabla_ds1307@68 { compatible = "blabla_ds1307"; reg = <0x68>; status = "okay"; }; }; }; };
Получается вот так:
Исходники на GitHub