Драйверы кадрового буфера |
Предыдущая Содержание Следующая |
Теперь, когда вы понимаете идею в API кадрового буфера и как он обеспечивает аппаратную независимость, давайте изучим архитектуру низкоуровнего драйвера устройства с кадровым буфером на примере навигационной системы. Пример устройства: Навигационная системаРисунок 12.6 показывает видео операции на примере навигационной системы машины, построенной на встроенном процессоре. Приёмник GPS передаёт потоки координат на процессор через интерфейс UART. Приложение рисует графику на основе полученной информации о местоположении и обновляет кадровый буфер в системной памяти. Драйвер кадрового буфера через DMA передаёт данные изображения в буферы дисплея, которые являются частью LCD контроллера процессора. Контроллер передаёт пиксельные данные для отображения на ЖК панель QVGA.
Рисунок 12.6. Отображение на навигационном устройстве с Linux.
Наша цель заключается в разработке программного обеспечения для этой системы, работающего с видео. Давайте предположим, что Linux поддерживает процессор, используемый в этом навигационном устройстве, и что все зависящие от архитектуры интерфейсы, такие как DMA, поддерживаются ядром.
Одна из возможных аппаратных реализаций устройства, показанного на Рисунке 12.6, заключается в использовании процессоре Freescale i.MX21. Ядром процессора в этом случае является ядро ARM9, а встроенным видеоконтроллером является контроллер жидкокристаллического дисплея Liquid Crystal Display Controller (LCDC). Микропроцессоры, как правило, имеют высокопроизводительную внутреннюю шину, которая подключена к таким контроллерам, как DRAM и видео. В случае iMX.21 эта шина называется Улучшенная высокопроизводительная шина, Advanced High-Performance Bus (AHB). LCDC подключается к AHB.
Программное обеспечение навигационной системы для работы с видео построено в целом как приложение GPS, работающее через низкоуровневый драйвер кадрового буфера для контроллера ЖК-дисплея. Приложение получает координат местоположения от приёмника GPS читая /dev/ttySX, где X - это номер UART, подключённого к ресиверу. Затем оно переводит зафиксированную географическую информацию в картинку и записывает пиксельные данные в кадровый буфер, связанных с контроллером ЖК-дисплея. Это делается по аналогии с Распечаткой 12.1, за исключением того, что данные изображения отличаются от нулей для очистки экрана.
Далее в этом разделе основное внимание уделяется только низкоуровневому драйверу кадрового буфера. Как и многие другие драйверные подсистемы, полный набор объектов, режимов и опций, предлагаемых уровнем ядра кадрового буфера, является сложным и может быть изучен только при получении опыта кодирования. Драйвер кадрового буфера для примера системы навигации является относительно простым и является лишь отправной точкой для более глубоких исследований.
Таблица 12.1 описывает регистровую модель LCD контроллера показанного на Рисунке 12.6. Драйвер кадрового буфера в Распечатке 12.2 работает с этими регистрами.
Таблица 12.1. Назначение регистров контроллера LCD, показанного на Рисунке 12.6
Наш драйвер кадрового буфера (названный myfb) реализован в Распечатке 12.2 в виде драйвера платформы. Как вы узнали в Главе 6, платформа представляет собой псевдо шину, обычно используемую для подключения простых устройств, интегрированных на кристалле, к модели устройств ядра. Архитектурно-зависимый код настройки (в arch/your-arch/your-platform/) добавляет платформу, используя platform_device_add(); но для простоты, метод probe() драйвера myfb выполняет это до регистрации себя в качестве драйвера платформы. Обратитесь к разделу "Пример устройства: Сотовый телефон" в Главе 6, где описана общая архитектура драйвера платформы и соответствующие точки входа. Структуры данныхДавайте посмотрим на основные структуры данных и методы, связанные с драйверами кадрового буфера, а затем рассмотрим myfb. Основными структурами являются следующие две:
1.Структура fb_info является центральной структурой данных драйверов кадрового буфера. Эта структура определена в include/linux/fb.h следующим образом: 2.Структура fb_ops содержит адреса всех точек входа, предоставляемых низкоуровневым драйвером кадрового буфера. Первые несколько методов в fb_ops необходимы для функционирования драйвера, а остальные являются необязательными, обеспечивающими ускорение графики. Ответственность каждой функции кратко объясняется комментариями:
Давайте теперь посмотрим на методы драйвера, реализованные для драйвера myfb в Распечатке 12.2. Проверка и установка параметровПроверку разумности переменных, таких как разрешение по X, разрешение по Y и число битов на пиксель, выполняет метод fb_check_var(). Таким образом, если вы используете fbset для установки разрешения по X менее минимального, поддерживаемого контроллером LCD (64 в нашем примере), то эта функция будет ограничивать его до минимального значения, позволяемого оборудованием.
fb_check_var() также устанавливает соответствующий формат RGB. Наш пример использует 16 бит на пиксель, и контроллер связывает каждое слово данных в кадровом буфере с часто используемым кодом RGB565: 5 бит для красного, 6 бит для зелёного и 5 бит для синего. Смещения в словах данных для каждого из трёх цветов также устанавливаются соответственно.
Метод fb_set_par() настраивает регистры контроллера LCD в зависимости от значений, указанных в fb_info.var. Это включает в себя настройку
•Длительности горизонтальной синхронизации, отступ слева и справа в HSYNC_REG; •Длительности вертикальной синхронизации, отступ сверху и снизу в VSYNC_REG; •Видимое разрешение по X и Y в SIZE_REG; •Параметры DMA в DMA_REG.
Предположим, что приложение GPS пытается изменить разрешение QVGA дисплея на 50x50. Ниже приводится цепь событий:
1.Дисплей проинициализирован в разрешение QVGA: 2.Приложение делает следующее: 3.ioctl FBIOPUT_VSCREENINFO на предыдущем этапе вызывает срабатывание myfb_check_var(). Этот метод драйвера выражает недовольство и округляет запрошенное разрешение до минимума, поддерживаемого оборудованием, которое в этом случае 64x64.
4.из ядра кадрового буфера вызывается myfb_set_par(), которая программирует новые параметры отображения в регистры контроллера LCD. 5.fbset теперь выводит новые параметры: Цветовые режимыОбычные цветовые режимы, поддерживаемые видео оборудованием, включают псевдо-цвет и естественные цвета (true color). В первом случае индексные номера преобразуются в пиксели формата RGB путём кодировок. Выбрав подмножество доступных цветов и с помощью индексирования соответствующих цветов вместо значения самого пикселя, вы можете снизить требования к памяти кадрового буфера. Однако, используемое оборудование должно поддерживать эту схему изменения цветового набора (или палитры).
В режиме true color (который есть то, что поддерживает наш пример LCD контроллера), изменение палитр не являются актуальным. Тем не менее, вам всё равно придётся удовлетворить потребности консольного драйвера кадрового буфера, который использует только 16 цветов. Для этого вам необходимо создать псевдо палитру кодированием соответствующих 16-ти необработанных значений RGB в биты, которые могут быть напрямую переданы оборудованию. Эта псевдо палитра хранится в поле pseudo_palette структуры fb_info. В Распечатке 12.2, myfb_setcolreg() заполняет её следующим образом:
((u32*)(info->pseudo_palette))[color_index] = (red << info->var.red.offset) | (green << info->var.green.offset) | (blue << info->var.blue.offset) | (transp << info->var.transp.offset);
Наш LCD контроллер использует 16 бит на пиксель и формат RGB565, и как вы видели ранее, метод fb_check_var() гарантирует, что значения красного, зелёного и синего имеют смещения 11, 5 и 0, соответственно. В дополнение к индексу цвета и значениям красного, синего и зелёного, fb_setcolreg() принимает аргумент transp, чтобы указать желаемые эффекты прозрачности. Этот механизм, называемый альфа-канал, сочетает указанное значение пикселя с цветом фона. LCD контроллер в данном примере не поддерживает альфа-смешивание, так что myfb_check_var() устанавливает смещение и размер transp в ноль.
Абстракция кадрового буфера достаточно мощна, чтобы изолировать приложения от характеристик панели дисплея, будь это RGB или BGR, или что-то другое. Смещения красного, синего и зелёного, установленные fb_check_var(), передаются в пользовательское пространство с помощью структуры fb_var_screeninfo, получаемой через ioctl() FBIOGET_VSCREENINFO. Поскольку такие приложения, как X Windows совместимы с кадровым буфером, они рисуют пиксели в кадровом буфере в зависимости от смещения цветов, возвращаемых этим ioctl().
Размеры битовых полей, используемые кодировкой RGB (5+6+5 = 16 в данном случае) называются глубиной цвета, которая используется консольным драйвером кадрового буфера для выбора файла логотипа для отображения во время загрузки (смотрите раздел "Логотип при загрузке"). Гашение экранаПоддержку для гашения и включения дисплея обеспечивает метод fb_blank(). Это используется в основном для управления питанием. Для гашения дисплея навигационной системы после 10-ти минутного периода бездействия сделайте следующее:
bash> setterm -blank 10
Эта команда проходит вниз по уровням до уровня кадрового буфера и в результате вызывает myfb_blank(), который программирует соответствующие биты в CTRL_REG. Методы ускоренияЕсли ваш пользовательский интерфейс должен выполнять тяжёлые операции, таких как смешивание, изменение размера, двигать рисунки, или генерировать динамический градиент, вам, вероятно, потребуется ускорение графики, чтобы получить приемлемый уровень производительности. Давайте кратко рассмотрим методы fb_ops, которые можно использовать, если ваша видеокарта поддерживает ускорение графики.
Метод fb_imageblit() рисует на дисплее изображение. Эта точка входа даёт возможность драйверу использовать любые специальные возможности, которыми может обладать ваш видео контроллер, чтобы ускорить эту операцию. cfb_imageblit() является универсальной функцией библиотеки, предоставляемой ядром кадрового буфера, чтобы выполнять это, если у оборудование не имеет ускорителя. Она используется, например, для вывода логотипа на экран во время загрузки. fb_copyarea() копирует прямоугольную область из одной области экрана в другую. cfb_copyarea() представляет собой оптимальный способ сделать это, если ваш графический контроллер не обладает магией, чтобы ускорить эту операцию. Метод fb_fillrect() быстро заполняет прямоугольник пиксельными линиями. cfb_fillrect() предлагает общий способ достижения этой цели без ускорителя. Контроллер LCD в нашей навигационной системы не предоставляет ускорения, так что драйвер в примере заполняет эти методы используя универсальные оптимизированные процедуры, предлагаемые ядром кадрового буфера.
DMA из кадрового буфераLCD контроллер в навигационной системе содержит механизм DMA, который получает кадры изображения из системной памяти. Контроллер отправляет полученные графические данные на панель отображения. Скорость DMA поддерживает частоту обновления экрана. Некэшируемый кадровый буфер, подходящий для согласованного доступа, выделяется с использованием dma_alloc_coherent() в myfb_probe(). (Мы обсудили согласованного отображение DMA в Главе 10, "Соединение компонентов периферии".) myfb_set_par() записывает этот выделенный адрес DMA в регистр DMA_REG LCD контроллера.
Когда драйвер включает DMA вызывая myfb_enable_controller(), контроллер начинает переправлять пиксельные данные из кадрового буфера на дисплей с помощью синхронного DMA. Таким образом, когда приложение GPS отображает кадровый буфер (с помощью mmap()) и записывает в неё информацию о местоположении, на LCD закрашиваются пиксели. Контраст и подсветкаLCD контроллер навигационной системы поддерживает управление контрастом с помощью регистра CONTRAST_REG. Драйвер экспортирует это в пространство пользователя с помощью myfb_ioctl(). Приложение GPS управляет контрастом следующим образом:
unsigned int my_fd, desired_contrast_level = 100; /* Открываем кадровый буфер */ my_fd = open("/dev/fb0", O_RDWR); ioctl(my_fd, MYFB_SET_BRIGHTNESS, &desired_contrast_level);
ЖК панель навигационной системы подсвечивается с помощью задней подсветки. Процессор управляет инвертером подсветки через выводы GPIO, поэтому вы можете включить или отключить свет управляя соответствующими контактами. Ядро абстрагирует универсальный интерфейс подсветки через узлы sysfs. Чтобы связаться с этим интерфейсом, ваш драйвер должен заполнить структуру backlight_ops методами получения и обновления настроек подсветки, и зарегистрировать её в ядре с помощью backlight_device_register(). Загляните в drivers/video/backlight/ для поиска исходников интерфейса подсветки и поищите в дереве drivers/ по названию backlight_device_register() видео драйверы, использующие этот интерфейс. В Распечатке 12.2 операции управления подсветкой не реализованы.
Распечатка 12.2. Драйвер кадрового буфера для навигационной системы
Код: #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/platform_device.h>
/* Карта адресов регистров LCD контроллера */ #define LCD_CONTROLLER_BASE 0x01000D00 #define SIZE_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE)) #define HSYNC_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 4)) #define VSYNC_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 8)) #define CONF_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 12)) #define CTRL_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 16)) #define DMA_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 20)) #define STATUS_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 24)) #define CONTRAST_REG (*(volatile u32 *)(LCD_CONTROLLER_BASE + 28)) #define LCD_CONTROLLER_SIZE 32
/* Ресурсы для LCD контроллера как устройства платформы */ static struct resource myfb_resources[] = { [0] = { .start = LCD_CONTROLLER_BASE, .end = LCD_CONTROLLER_SIZE, .flags = IORESOURCE_MEM, }, };
/* Определение устройства платформы */ static struct platform_device myfb_device = { .name = "myfb", .id = 0, .dev = { .coherent_dma_mask = 0xffffffff, }, .num_resources = ARRAY_SIZE(myfb_resources), .resource = myfb_resources, };
/* Установка параметров LCD контроллера */ static int myfb_set_par(struct fb_info *info) { unsigned long adjusted_fb_start; struct fb_var_screeninfo *var = &info->var; struct fb_fix_screeninfo *fix = &info->fix;
/* Старшие 16 бит HSYNC_REG содержат длительность HSYNC, следующие 8 содержат отступ слева, 8 младших содержат отступ справа */ HSYNC_REG = (var->hsync_len << 16) | (var->left_margin << 8)| (var->right_margin);
/* Старшие 16 бит VSYNC_REG содержат длительность VSYNC, следующие 8 содержат отступ сверху, 8 младших содержат отступ снизу */ VSYNC_REG = (var->vsync_len << 16) | (var->upper_margin << 8)| (var->lower_margin);
/* Старшие 16 бит SIZE_REG содержат xres, младшие 16 содержат yres */ SIZE_REG = (var->xres << 16) | (var->yres);
/* Устанавливаем число битов на пиксель, полярность битов, делители частоты для pixclock, и режим цвет/монохром в CONF_REG */ /* ... */
/* Заполняем DMA_REG начальным адресом кадрового буфера выделенного когерентно в myfb_probe(). Изменяем этот адрес для учёта смещения начала области экрана */ adjusted_fb_start = fix->smem_start + (var->yoffset * var->xres_virtual + var->xoffset) * (var->bits_per_pixel) / 8; __raw_writel(adjusted_fb_start, (unsigned long *)DMA_REG); /* Устанавливаем размер DMA и размер меток в DMA_REG */ /* ... */
/* Устанавливаем неизменяемую информацию */ fix->accel = FB_ACCEL_NONE; /* Аппаратного ускорения нет */ fix->visual = FB_VISUAL_TRUECOLOR; /* Режим True color */ fix->line_length = var->xres_virtual * var->bits_per_pixel/8; return 0; }
/* Включение LCD контроллера */ static void myfb_enable_controller(struct fb_info *info) { /* Включение LCD контроллера, старт DMA, запуск тактовых частот и включение питания через запись в CTRL_REG */ /* ... */ }
/* Выключение LCD контроллера */ static void myfb_disable_controller(struct fb_info *info) { /* Включение LCD контроллера, остановка DMA, выключение тактовых частот и питания через запись в CTRL_REG */ /* ... */ }
/* Проверка разумности и изменение переменных */ static int myfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { /* Округление вверх до минимального разрешения, поддерживаемого LCD контроллером */ if (var->xres < 64) var->xres = 64; if (var->yres < 64) var->yres = 64;
/* ... */ /* Это оборудование поддерживает формат цвета RGB565. Для подробностей смотрите раздел "Режимы цвета" */ if (var->bits_per_pixel == 16) { /* Кодирование красного */ var->red.length = 5; var->red.offset = 11; /* Кодирование зелёного */ var->green.length = 6; var->green.offset = 5; /* Кодирование синего */ var->blue.length = 5; var->blue.offset = 0; /* Оборудование не поддерживает альфа канал */ var->transp.length = 0; var->transp.offset = 0; } return 0; }
/* Включение/выключение экрана */ static int myfb_blank(int blank_mode, struct fb_info *info) { switch (blank_mode) { case FB_BLANK_POWERDOWN: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_NORMAL: myfb_disable_controller(info); break; case FB_BLANK_UNBLANK: myfb_enable_controller(info); break; } return 0; }
/* Конфигурирование карты палитры псевдо цветов */ static int myfb_setcolreg(u_int color_index, u_int red, u_int green, u_int blue, u_int transp, struct fb_info *info) { if (info->fix.visual == FB_VISUAL_TRUECOLOR) { /* Делаем требуемые преобразования для конвертации красного, синего, зелёного и прозрачности в значения, которые могут быть напрямую восприняты оборудованием */ /* ... */
((u32 *)(info->pseudo_palette))[color_index] = (red << info->var.red.offset) | (green << info->var.green.offset) | (blue << info->var.blue.offset) | (transp << info->var.transp.offset); } return 0; }
/* Зависимое от устройства определение ioctl */ #define MYFB_SET_BRIGHTNESS _IOW('M', 3, int8_t)
/* Зависимая от устройства ioctl */ static int myfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { u32 blevel ; switch (cmd) { case MYFB_SET_BRIGHTNESS : copy_from_user((void *)&blevel, (void *)arg, sizeof(blevel)) ; /* Пишем blevel в CONTRAST_REG */ /* ... */ break; default: return –EINVAL; } return 0; }
/* Структура fb_ops */ static struct fb_ops myfb_ops = { .owner = THIS_MODULE, .fb_check_var = myfb_check_var,/* Проверка параметров */ .fb_set_par = myfb_set_par, /* Программирование регистров контроллера */ .fb_setcolreg = myfb_setcolreg,/* Установка карты цветов */ .fb_blank = myfb_blank, /* Включение/выключение экрана */ .fb_fillrect = cfb_fillrect, /* Универсальная функция для заливки прямоугольника */ .fb_copyarea = cfb_copyarea, /* Универсальная функция для копирования области */ .fb_imageblit = cfb_imageblit, /* Универсальная функция для рисования */ .fb_ioctl = myfb_ioctl, /* Зависимая от устройства ioctl */ };
/* Процедура устройства платформы probe() */ static int __init myfb_probe(struct platform_device *pdev) { struct fb_info *info; struct resource *res;
info = framebuffer_alloc(0, &pdev->dev); /* ... */ /* Получаем соответствующие ресурсы, заданные при регистрации данного platform_device */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* Получаем разрешение ядра на использование куска памяти ввода/вывода, начиная с LCD_CONTROLLER_BASE и размером в LCD_CONTROLLER_SIZE байт */ res = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
/* Заполняем структуру fb_info постоянными (info->fix) и переменными (info->var) значениями, такими как размер кадрового буфера, xres, yres, bits_per_pixel, fbops, cmap, и так далее */ initialize_fb_info(info, pdev); /* Отсутствует */ info->fbops = &myfb_ops; fb_alloc_cmap(&info->cmap, 16, 0);
/* Связываем когерентно память кадрового буфера с DMA. info->screen_base содержит адрес процессора для отображаемого буфера, info->fix.smem_start carries the associated hardware address */ info->screen_base = dma_alloc_coherent(0, info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start, GFP_DMA | GFP_KERNEL); /* Устанавливаем информацию в info->var для соответствующих регистров LCD контроллера */ myfb_set_par(info);
/* Регистрируемся в ядре кадрового буфера */ register_framebuffer(info); return 0; }
/* Процедура драйвера платформы remove() */ static int myfb_remove(struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct resource *res;
/* Запрет обновления экрана, выключение DMA,... */ myfb_disable_controller(info);
/* Отмена регистрации драйвера кадрового буфера */ unregister_framebuffer(info); /* Освобождение карты цветов */ fb_dealloc_cmap(&info->cmap); kfree(info->pseudo_palette);
/* Освобождение кадрового буфера */ framebuffer_release(info); /* Освобождение области памяти */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(res->start, res->end - res->start + 1); platform_set_drvdata(pdev, NULL); return 0; }
/* Структура драйвера платформы */ static struct platform_driver myfb_driver = { .probe = myfb_probe, .remove = myfb_remove, .driver = { .name = "myfb", }, };
/* Инициализация модуля */ int __init myfb_init(void) { platform_device_add(&myfb_device); return platform_driver_register(&myfb_driver); }
/* Выгрузка модуля */ void __exit myfb_exit(void) { platform_driver_unregister(&myfb_driver); platform_device_unregister(&myfb_device); }
module_init(myfb_init); module_exit(myfb_exit);
| |||||||||||||||||||
Предыдущая Содержание Следующая |