I2C драйвер в Linux
С I2C в Linux вполне можно работать из пространства пользователя. Тем не менее это не очень удобно. Весьма удобнее и правильнее сделать модуль ядра.
Для создания драйвера в ядре Linux предусмотрены вспомогательные макросы. Если Вам не нужно ничего выполнять при инициализации модуля, можно воспользоваться макросом module_i2c_driver.
В качестве параметра ему передается структура i2c_driver, в которой указывается имя устройства и указатели на функции probe, remove.
Макрос module_driver в свою очередь определен так:
Как видно, в нем определены функции инициализации и деинициализации модуля ядра. Из которых вызываются функции i2c_register_driver(THIS_MODULE, driver) (она же макрос i2c_add_driver(driver)) и i2c_del_driver(struct i2c_driver *) соответственно.
Что использовать решать Вам, я воспользуюсь макросом module_i2c_driver. Как мы помним у меня в качестве подопытного часы ds1307. В Linux уже есть драйвер для них, но мы же не ищем легких путей!?
Параметром макросу передаем структуру:
Указываем имя драйвера, функцию probe и remove, таблицу идентификаторов поддерживаемых устройсв.
Имя устройства используется при поиске подходящего драйвера, второй элемент структуры — параметр, который можно потом достать из этой таблицы при необходимости.
При успешном поиске драйвера вызывается его функция probe. В ней Вы должно проинициализировать устройство или же вычитать его конфиг и дать команду начала работы с ним (запустить поток или еще чего). Мы такого делать не будем, вычитаем данные из часов и выведем их в лог.
Для работы с шиной I2C в ядре предусмотрены функции:
Следует отметить что count должен быть менее 64К, т.к. в структуре сообщения i2c в недрах ядра длина 16 битная.
Для хранения данных я сделал такую структуру:
В функции probe выделяем память для данных, которые будем читать из устройства. Конечно же выводим сообщение в лог ядра. Шлем ноль из wr_buf, чтобы установить указатель записи/чтения в часах. И читаем в выделенную при помощи kmalloc память данные по i2c шине. Если функция вернула число меньше 0 значит что-то пошло не так, в случае успеха будет возвращено количество отправленных байт.
Если считали успешно, выводим в лог при помощи функции print_hex_dump.
Очень удобная функция. Параметры:
- 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 для устройсва:
Источник
Пишем модуль ядра Linux: I2C
Данная статья посвящена разработке I2C (Inter-Integrated Circuit) модуля ядра Linux. Далее описан процесс реализация базовой структуры I2C драйвера, в которую можно легко добавить реализацию необходимого функционала.
Опишем входные данные: I2C блок для нового процессора «зашитый» на ПЛИС, запущенный Linux версии 3.18.19 и периферийные устройства (EEPROM AT24C64 и BME280).
Принцип работы I2C достаточно прост, но если нужно освежить знания, то можно почитать тут.
Рисунок 1. Временная диаграмма сигналов шины I2C
Перед тем как начать разрабатывать драйвер посмотрим как user space приложения взаимодействуют с модулем ядра, для этого:
- Реализуем небольшое user space приложение, цель которого прочитать уникальный ID регистра I2C устройства. Данный шаг позволит понять интерфей, через который происходит обмен между модулем ядра и пользовательским приложением;
- Познакомимся с вариантом передачи I2C сообщений модулем ядра;
- Добавим модуль ядра в сборку и опишем аппаратную часть устройств в device tree;
- Реализуем общую структуру (скелет) I2C драйвера с небольшими пояснениями.
К сожалению, прикрепить реальные исходники разработанного драйвера не представляется возможным. Также, хочу заметить, все имена, названия и регистровая карта контроллера изменены. В скелет драйвера ни вошло и половины разработанного функционала, тем не менее, структура драйвера является хорошей отправной точкой при разработке. Примеры I2C драйверов можно посмотреть тут.
Шаг первый
Для начала познакомимся с утилитой i2cdetect. Результат работы i2cdetect выглядит следующим образом:
Утилита последовательно выставляет на I2C шину адреса устройств и при получении положительного ответа (в данном случае положительным ответом является ACK) выводит в консоль номер адреса устройства на шине.
Напишем небольшую программку, которая считывает уникальный ID датчика температуры, и выведем результат ее работы в консоль. Выглядит очень просто:
Становятся понятно что модуль ядра принимает данные в виде полей сообщения i2c_rdwr_ioctl_data. Структура содержит такие поля как i2c_msg и nmsgs, которые используется для передачи:
- .addr — адреса устройства;
- .flags — типа операции (чтение или запись);
- .len — длины текущего сообщения;
- .buf- буфера обмена.
Шаг второй
Теперь, не углубляюсь во внутренности, познакомимся с одним вариантом работы I2C драйвера.
Как уже было установлено, модуль ядра получает сообщения в виде структуры. Для примера рассмотрим алгоритм работы драйвера при выполнении операции записи (аппаратно-зависимая часть):
- Сначала заполняется TX FIFO: первым идет адрес устройства, а после оставшиеся данные на передачу;
- Очищается статусный регистр прерывания ISR и разрешаются прерывания в регистре IER (в данном случае прерывание возникающее при отсутствии данных в TX FIFO);
- Разрешается передача данных и устанавливается старт бит на шине.
Весь последующей обмен данными будет происходить в обработчике прерывания.
Драйвера, которые работают по данному алгоритму, можно найти тут. Также у контроллера может не быть FIFO, а только единственный регистр на передачу, но это частный случай с размером FIFO равным одному.
Шаг третий
Добавим модуль ядра в сборку и опишем аппаратную часть устройств в device tree:
1. Создадим source файл в следующей директории:
В результате появится файл:
2. Добавим конфигурацию драйвера в drivers/i2c/busses/Kconfig:
3. Добавим в сборку драйвер drivers/i2c/busses/Makefile:
4. Добавим в devicetree (*.dts) описание I2C блока, а также сразу поддержу eeprom устройства:
Подробно рассматриваться выше перечисленные шаги не будут, но любопытным читателям можно заглянуть сюда.
Шаг четвертый
После ознакомления с принципом работы драйвера приступим к реализации.
Сначала подключим заголовочные файлы, опишем «виртуальную» регистровую карту, а также представление драйвера I2C.
Главными управляющими регистрами контроллера являются:
- Control Register (CTRL) — регистр управления;
- Interrupt Status Register (ISR) — статусный регистр прерывания;
- Interrupt Enable Register (IER) — регистр маски прерывания.
Сердцем драйвера является структура skel_i2c, которая содержит такие поля как:
- .base — указатель на начало регистровой карты;
- .msg — указатель на текущее сообщение;
- .adap — I2C абстракция (клик).
Перейдем к более практической части, опишем типы поддерживаемых драйвером устройств,
функционал I2C адаптера и интерфейс передачи I2C сообщений:
Из названий структур и функций очевидно их назначение, опишем только главную структуру из представленных выше:
- skel_i2c_driver — описывает имя драйвера, таблицу поддерживаемых устройств и функций, которые вызываются в момент загрузки или удаления модуля ядра из системы.
Пора зарегистрировать драйвер в системе, а значит реализовать функцию инициализации контроллера, а также описать skel_i2c_probe (вызывается в момент загрузки драйвера в систему) и skel_i2c_remove (вызывается в момент удаления драйвера из системы).
Наиболее простой функцией является skel_i2c_remove, которая отключает источник тактовой частоты и освобождает используемую память. Функция skel_i2c_init выполняет первичную инициализацию I2C контроллера.
Как упоминалось ранее skel_i2c_probe регистрирует драйвер в системе. Последовательность действий, условно, можно разделить на два этапа:
- Получение системных ресурсов и регистрацию обработчика прерывания skel_i2c_isr;
- Заполнение полей структуры и вызов процедуры добавления нового I2C адаптера.
После того как драйвер зарегистрирован в системе, можно реализовать логику передачи сообщений по интерфейсу:
В первом шаге было описано взаимодействие user space приложения с модулем ядра системы. После того как мы реализовали внутренности драйвера легко увидеть интерфейс, через который происходит обмен. В общем случае передача сообщений происходит следующем образом:
- skel_i2c_xfer — функция напрямую получает сообщения на передачу и последовательно передает каждое сообщение в skel_i2c_xfer_msg. Если во время передачи данных произошла ошибка, то передача данных останавливается;
- skel_i2c_xfer_msg — функция устанавливает все необходимые поля драйвера и инициирует начало передачи сообщений;
- skel_i2c_isr — процедура обработки прерывания. Здесь происходит обработка ошибок, а также обмен данными по шине. Если все данные отправлены/приняты устанавливается флаг done с помощью вызова функции complete, которая сигнализирует о завершении передачи сообщения.
В статье не описаны некоторые тонкости работы. Например, последовательность действий передачи сообщений, так как реализация данного алгоритма является аппаратно зависимой. Мы же сосредоточились на реализации общей части драйвера вне зависимости от аппаратных особенностей контроллера.
Полный скелет драйвера прикреплен ниже. Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Источник
Implementing I2C device drivers in userspace¶
Usually, I2C devices are controlled by a kernel driver. But it is also possible to access all devices on an adapter from userspace, through the /dev interface. You need to load module i2c-dev for this.
Each registered I2C adapter gets a number, counting from 0. You can examine /sys/class/i2c-dev/ to see what number corresponds to which adapter. Alternatively, you can run “i2cdetect -l” to obtain a formatted list of all I2C adapters present on your system at a given time. i2cdetect is part of the i2c-tools package.
I2C device files are character device files with major device number 89 and a minor device number corresponding to the number assigned as explained above. They should be called “i2c-%d” (i2c-0, i2c-1, …, i2c-10, …). All 256 minor device numbers are reserved for I2C.
C example¶
So let’s say you want to access an I2C adapter from a C program. First, you need to include these two headers:
Now, you have to decide which adapter you want to access. You should inspect /sys/class/i2c-dev/ or run “i2cdetect -l” to decide this. Adapter numbers are assigned somewhat dynamically, so you can not assume much about them. They can even change from one boot to the next.
Next thing, open the device file, as follows:
When you have opened the device, you must specify with what device address you want to communicate:
Well, you are all set up now. You can now use SMBus commands or plain I2C to communicate with your device. SMBus commands are preferred if the device supports them. Both are illustrated below:
Note that only a subset of the I2C and SMBus protocols can be achieved by the means of read() and write() calls. In particular, so-called combined transactions (mixing read and write messages in the same transaction) aren’t supported. For this reason, this interface is almost never used by user-space programs.
IMPORTANT: because of the use of inline functions, you have to use вЂ-O’ or some variation when you compile your program!
Full interface description¶
The following IOCTLs are defined:
ioctl(file, I2C_SLAVE, long addr)
Change slave address. The address is passed in the 7 lower bits of the argument (except for 10 bit addresses, passed in the 10 lower bits in this case).
ioctl(file, I2C_TENBIT, long select)
Selects ten bit addresses if select not equals 0, selects normal 7 bit addresses if select equals 0. Default 0. This request is only valid if the adapter has I2C_FUNC_10BIT_ADDR.
ioctl(file, I2C_PEC, long select)
Selects SMBus PEC (packet error checking) generation and verification if select not equals 0, disables if select equals 0. Default 0. Used only for SMBus transactions. This request only has an effect if the the adapter has I2C_FUNC_SMBUS_PEC; it is still safe if not, it just doesn’t have any effect.
ioctl(file, I2C_FUNCS, unsigned long *funcs)
Gets the adapter functionality and puts it in *funcs .
ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)
Do combined read/write transaction without stop in between. Only valid if the adapter has I2C_FUNC_I2C. The argument is a pointer to a:
The msgs[] themselves contain further pointers into data buffers. The function will write or read data to or from that buffers depending on whether the I2C_M_RD flag is set in a particular message or not. The slave address and whether to use ten bit address mode has to be set in each message, overriding the values set with the above ioctl’s.
ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args)
If possible, use the provided i2c_smbus_* methods described below instead of issuing direct ioctls.
You can do plain I2C transactions by using read(2) and write(2) calls. You do not need to pass the address byte; instead, set it through ioctl I2C_SLAVE before you try to access the device.
You can do SMBus level transactions (see documentation file smbus-protocol for details) through the following functions:
All these transactions return -1 on failure; you can read errno to see what happened. The вЂwrite’ transactions return 0 on success; the вЂread’ transactions return the read value, except for read_block, which returns the number of values read. The block buffers need not be longer than 32 bytes.
The above functions are made available by linking against the libi2c library, which is provided by the i2c-tools project. See: https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/.
Implementation details¶
For the interested, here’s the code flow which happens inside the kernel when you use the /dev interface to I2C:
Your program opens /dev/i2c-N and calls ioctl() on it, as described in section “C example” above.
These open() and ioctl() calls are handled by the i2c-dev kernel driver: see i2c-dev.c:i2cdev_open() and i2c-dev.c:i2cdev_ioctl(), respectively. You can think of i2c-dev as a generic I2C chip driver that can be programmed from user-space.
Some ioctl() calls are for administrative tasks and are handled by i2c-dev directly. Examples include I2C_SLAVE (set the address of the device you want to access) and I2C_PEC (enable or disable SMBus error checking on future transactions.)
Other ioctl() calls are converted to in-kernel function calls by i2c-dev. Examples include I2C_FUNCS, which queries the I2C adapter functionality using i2c.h:i2c_get_functionality(), and I2C_SMBUS, which performs an SMBus transaction using i2c-core-smbus.c: i2c_smbus_xfer() .
The i2c-dev driver is responsible for checking all the parameters that come from user-space for validity. After this point, there is no difference between these calls that came from user-space through i2c-dev and calls that would have been performed by kernel I2C chip drivers directly. This means that I2C bus drivers don’t need to implement anything special to support access from user-space.
These i2c.h functions are wrappers to the actual implementation of your I2C bus driver. Each adapter must declare callback functions implementing these standard calls. i2c.h:i2c_get_functionality() calls i2c_adapter.algo->functionality(), while i2c-core-smbus.c: i2c_smbus_xfer() calls either adapter.algo->smbus_xfer() if it is implemented, or if not, i2c-core-smbus.c:i2c_smbus_xfer_emulated() which in turn calls i2c_adapter.algo->master_xfer().
After your I2C bus driver has processed these requests, execution runs up the call chain, with almost no processing done, except by i2c-dev to package the returned data, if any, in suitable format for the ioctl.
© Copyright The kernel development community.
Источник