. Написание драйвера для LCD дисплея под embedded linux
Написание драйвера для LCD дисплея под embedded linux

Написание драйвера для LCD дисплея под embedded linux

В данной статье хочу поделиться своим опытом написания linux драйвера для цветного дисплея 320х240 от производителя Newhavendisplays, а именно NHD-5.7-320240WFB-CTXI-T1 под embedded linux. Идея написать статью созрела именно по причине того, что ресурсов по написанию framebufer(FB) драйверов не так уж и много, тем более, на русском языке. Модуль был написан далеко не под самое новое ядро(2.6.30), поэтому допускаю, что в интерфейсах FB много чего поменялось с тех пор. Но, тем не менее, надеюсь, статья будет интересна интересующимся разработкой уровня ядра linux. Не исключаю, что реализацию можно было бы сделать проще и изящней, поэтому комментарии и замечания приветствуются.

Предистория

Изначально стояла задача написать драйвер, к которому можно было бы обращаться с помощью стандартных средств типа QT embedded, чтобы в конечном итоге соорудить простую менюшку с иконками и текстом для взаимодействия с пользователем. Платформой служила платка на AT91SAM9G45, a точнее www.armdevs.com/IPC-SAM9G45.html Стримить видео не планировалось. AT91SAM9G45 содержит вполне себе работоспособный встроенный LCD контроллер с поддержкой DMA и довольно скоростной шиной, с которыми потенциально можно было бы добиться приличной скорости и для видео, но увы, хардварно он не совместим с SSD1963. Поэтому было принято решение заюзать для этой цели обычный GPIO интерфейс, как единственную доступную альтернативу.

Интерфейс контроллера SSD1963

Интерфейс контроллера проще всего представить в виде рисунка из даташита дисплея:

С точки зрения разработчика драйвера нас интересуют пины DB0 – DB7. Это 8-битная шина данных, и пины DC, RD, WR, CS, RES которые используются для управления процессом передачи данных на SSD1963. Что касается формата передаваемых данных, данный дисплей использует формат 888. Что значит: 8 байт – Red, 8 байт – Green, 8 байт – Blue. Еще довольно часто в дисплеях такого типа можно встретить варианты 555, 565, и т.д., но это не наш случай. Формат передаваемых данных изображен на рисунке.

Перед тем, как первый байт данных будет выставлен на шину, должно последовать переключения пинов CS и WR из 1 в 0. А после того как байт данных будет установлен, следует переключение CS и WR из 0 в 1, что, собственно и осуществляет передачу байта данных в контроллер SSD1963. Более детально осциллограммы сигналов можно посмотреть в даташите на контроллер. www.newhavendisplay.com/app_notes/SSD1963.pdf

В исходном коде интерфейс опишем массивами GPIO пинов:

Функция передачи байтов по этому интерфейсу имеет вид:

Как видим, с помощью такой функции можно отправлять на LCD контроллер как комманды (например, для конфигурации дисплея), так и данные в виде пикселей.

Фреймбуфер модель ядра

Как известно, linux ядро предоставляет интерфейсы для разных типов драйверов устройств – char drivers, block drivers, usb drivers и т. д. Framebuffer driver также являет собой отдельную подсистему в линуксовой модели драйверов. Основной структурой, которая используется для репрезентации FB драйвера является struct fb_info в linux/fb.h. Кстати, этот хедер файл также будет интересен любителям юмора в коде linux ядра, так как содержит интересный дефайн — #define STUPID_ACCELF_TEXT_SHIT. Думаю, название говорит само за себя. Но, вернемся к структуре fb_info. Нас будут интересовать две структуры, которые она содержит – fb_var_screeninfo и fb_fix_screeninfo. Инициализируем их параметрами нашего дисплея.

В нашем случае под пиксель будет выделено 4 байта: 8-Red, 8-Green, 8-Blue, 8-Transparent Поясню некоторые из полей структур:

.type – способ размещения битов, описывающих пиксели в памяти. Packed pixels означает, что байты (в нашем случае 8888 будут размещены последовательно один за другим).

.visual – глубина цвета дисплея. В нашем случае это truecolor – глубина цвета 24bit

.accel – хардварная акселерация

.transp, red, green, blue – как раз и задают наш 8,8,8,8 формат в виде трех полей – offset, length и msb_right.

Также, для того, чтобы зарегистрировать наш драйвер в ядре, необходимо описать еще две сущности – устройство(device) и драйвер(driver). Опишем FB устройство(struct ssd1963), которое будет содержать страницы нашей видео памяти (struct ss1963_page):

Инициализация

Как и для любого другого модуля ядра линукс, опишем пару функций init/remove. Начнем с init. Framebuffer драйвера, как правило регистрируются в системе как platform_driver:

Platform driver в свою очередь вызывает функцию probe для конкретного драйвера, которая и выполняет все необходимые операции – аллокацию памяти, резервирование ресурсов, инициализацию структур и т.д. Приведем пример функции ssd1963_probe:

Несколько комментариев к функции. Здесь мы последовательно: — Выделяем память под наше устройство ssd1963 — Выделяем память и инициализируем струкруру fb_info, сначала значениями по умолчанию(framebuffer_alloc), так как многие параметры нам изменять не нужно, а затем конкретными значениями для нашего драйвера, как fb_var_screeninfo, fb_fix_screeninfo и fb_ops, которую мы рассмотрим немного позже. — Выделяет память под непрерывный буфер пикселей в виртуальной памяти, которая будет использоваться для записи user-space процессами. — Выделяем ssd1963_page для каждой страницы в виртуальной памяти фреймбуфера. Каждая ssd1963_page будет содержать адрес начала буфера страницы по отношению к общему буферу FB, сдвиг по х, сдвиг по y, и длину буфера страницы. В нашем случае емкость фреймбуфера = line_length*height = 320*4*240 = 307200 байт. Для такой емкости буфера нам потребуется line_length*height/PAGE_SIZE = 307200/4096 = 75 страниц. Отметим, как они будут располагаться в памяти FB. Понимание этого расположения страниц пригодится нам при рассмотрении функции ssd1963_copy немного позже:

— Регистрируем наш FB в системе(register_framebuffer) и инициализируем процедуру отложенного обновления данных (fb_deferred_io_init), детальней об этом в разделе “операции с фреймбуфером”. — ssd1963_setup конфигурирует необходимые GPIO на AT91SAM9G45 CPU и выполняет начальную настройку LCD контроллера. Алгоритм начальной конфигурации в виде отправки набора загадочных байт в хексе взят из документации на SSD1963, поэтому приведу здесь только часть функции:

ssd1963_update_all устанавливает флаг must_update=1 для всех страниц и инициирует механизм обновления дисплея в отложенном контексте с помощью вызова schedule_delayed_work(&item->info->deferred_work, fbdefio->delay);

Итак, с init разобрались, с функцией remove все куда проще, освобождаем выделенную память, и возвращаем FB структуры ядру:

Операции с фреймбуфером

Итак, пришло время рассмотреть структуру fb_ops:

Я не привожу здесь все методы структуры, любопытный читатель сможет найти их в исходном коде модуля либо в любом другом драйвере в коде ядра в каталоге drivers/video. Как вы уже догадались, структура fb_ops описывает действия, которые может осуществлять наш драйвер. К счастью, разработчики ядра частично облегчили нам работу, предоставив стандартные функции для работы с FB, имеющие суфикс sys_ или fb_sys, например fb_sys_read. Нам нужно лишь добавить в нашу имплементацию функций из fb_ops (ssd1963_read, ssd1963_write и др.) функционал, позволяющий выполнять обновление данных в нашей импровизированной видео памяти, когда в этом возникнет необходимость.

Например, функция ssd1963_fillrect будет выглядеть так:

Очевидно, что системный вызов fb_fillrect обновит видео данные в определенной прямоугольной области экрана, поэтому нам нужно указать, какие именно страницы нам нужно обновить, пометив их флажком must_update, и затем вызвав вручную процедуру обновления видеопамяти:

Обновление данных в видеопамяти происходит в виде отложенном контексте(deferred context). User-space приложение, работающее с графикой, не будет ожидать завершения записи каждого кадра в видеопамять, что вполне логично. Отложенная обработка в fb_info определяется в виде структуры fb_deferred_io:

Функция ssd1963_update c прототипом void ssd1963_update(struct fb_info *info, struct list_head *pagelist); не обновляет все страницы, а только страницы, которые были изменены в результате перезаписи user-space процессом, или в результате системного вызова, типа fb_fillrect и компании. Соответственно функция имеет вид:

На данном этапе, вы наверняка задались вопросом, что делает функция ssd1963_copy. Она как-раз-таки делает всю “грязную” работу по передаче данных из страниц видеопамяти на искусственно созданную, 8-битную шину на базе GPIO.

Функция ssd1963_copy

Здесь необходимо вспомнить рисунок, на котором изображено как соотносятся наши страницы в памяти с пикселями дисплея. Видим, например, что в page[0] хранится информация для трех верхних линий дисплея по 320 пикселей, и 64 пикселя для 4-й линии. Таких страниц у нас 75, и картинка с рисунка, и как не сложно заметить, page[5] будет выглядеть так же – 3 линии по 320 и одна по 64. Соответственно, функция, принимающая индекс страницы как параметр будет содержать switch(index%5) и в зависимости от офсетов для каждой конкретной страницы отправлять данные в выделенное ей “окно” в памяти дисплея. Функция довольно длинная, поэтому приведу лишь ее часть:

Здесь функция nhd_set_window конфигурирует с помощью уже известных нам nhd_write_data(NHD_COMMAND, …); область дисплея, в которую будет производится запись данных(пикселей). nhd_write_data(NHD_COMMAND, 0x2c); — команда LCD контроллеру о том, что сейчас последует поток данных.

Ну и напоследок, скриншот работы программы ts_calibrate из пакета tslib на устройстве с дисплеем. Кому интересно — могу выслать полный код модуля:

📎📎📎📎📎📎📎📎📎📎