С 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




Как включить постоянную частоту на определенном выводе процессора в linux, allwinner V3S?