В данной статье хочу поделиться своим опытом написания 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 на устройстве с дисплеем.
Кому интересно — могу выслать полный код модуля:
Предистория
Изначально стояла задача написать драйвер, к которому можно было бы обращаться с помощью стандартных средств типа 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 пинов:
static unsigned int nhd_data_pin_config[] = {
AT91_PIN_PE13, AT91_PIN_PE14, AT91_PIN_PE17, AT91_PIN_PE18,
AT91_PIN_PE19, AT91_PIN_PE20, AT91_PIN_PE21, AT91_PIN_PE22
};
static unsigned int nhd_gpio_pin_config[] = {
AT91_PIN_PE0, // RESET
AT91_PIN_PE2, // DC
AT91_PIN_PE5, // CLK
AT91_PIN_PE6, // RD
AT91_PIN_PE1 // WR
};
Функция передачи байтов по этому интерфейсу имеет вид:
static void nhd_write_data(int command, unsigned short value)
{
int i;
at91_set_gpio_output(AT91_PIN_PE12, 1); //R/D
for (i=0; i<ARRAY_SIZE(nhd_data_pin_config); i++)
at91_set_gpio_output(nhd_data_pin_config[i], (value>>i)&0x01);
if (command)
at91_set_gpio_output(AT91_PIN_PE10, 0); //D/C
else
at91_set_gpio_output(AT91_PIN_PE10, 1); //D/C
at91_set_gpio_output(AT91_PIN_PE11, 0); //WR
at91_set_gpio_output(AT91_PIN_PE26, 0); //CS
at91_set_gpio_output(AT91_PIN_PE26, 1); //CS
at91_set_gpio_output(AT91_PIN_PE11, 1); //WR
}
Как видим, с помощью такой функции можно отправлять на 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. Инициализируем их параметрами нашего дисплея.
static struct fb_fix_screeninfo ssd1963_fix __initdata = {
.id = "SSD1963",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_NONE,
.line_length = 320 * 4,
};
static struct fb_var_screeninfo ssd1963_var __initdata = {
.xres = 320,
.yres = 240,
.xres_virtual = 320,
.yres_virtual = 240,
.width = 320,
.height = 240,
.bits_per_pixel = 32,
.transp = {24, 8, 0},
.red = {16, 8, 0},
.green = {8, 8, 0},
.blue = {0, 8, 0},
.activate = FB_ACTIVATE_NOW,
.vmode = FB_VMODE_NONINTERLACED,
};
В нашем случае под пиксель будет выделено 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):
struct ssd1963_page {
unsigned short x;
unsigned short y;
unsigned long *buffer;
unsigned short len;
int must_update;
};
struct ssd1963 {
struct device *dev;
struct fb_info *info;
unsigned int pages_count;
struct ssd1963_page *pages;
};
struct platform_driver ssd1963_driver = {
.probe = ssd1963_probe,
.remove = ssd1963_remove,
.driver = { .name = "ssd1963" }
};
Инициализация
Как и для любого другого модуля ядра линукс, опишем пару функций init/remove. Начнем с init. Framebuffer драйвера, как правило регистрируются в системе как platform_driver:
static int __init ssd1963_init(void)
{
int ret = 0;
ret = platform_driver_register(&ssd1963_driver);
if (ret) {
pr_err("%s: unable to platform_driver_register\n", __func__);
}
return ret;
}
module_init(ssd1963_init);
Platform driver в свою очередь вызывает функцию probe для конкретного драйвера, которая и выполняет все необходимые операции – аллокацию памяти, резервирование ресурсов, инициализацию структур и т.д. Приведем пример функции ssd1963_probe:
static int __init ssd1963_probe(struct platform_device *dev)
{
int ret = 0;
struct ssd1963 *item;
struct fb_info *info;
// Allocating memory for ssd1663 device
item = kzalloc(sizeof(struct ssd1963), GFP_KERNEL);
if (!item) {
dev_err(&dev->dev,
"%s: unable to kzalloc for ssd1963\n", __func__);
ret = -ENOMEM;
goto out;
}
item->dev = &dev->dev;
dev_set_drvdata(&dev->dev, item);
// Initializing fb_info struct using kernel framebuffer API
info = framebuffer_alloc(sizeof(struct ssd1963), &dev->dev);
if (!info) {
ret = -ENOMEM;
dev_err(&dev->dev,
"%s: unable to framebuffer_alloc\n", __func__);
goto out_item;
}
item->info = info;
//Here info->par pointer is commonly used to store private data
// In our case, we can use it to store pointer to ssd1963 device
info->par = item;
info->dev = &dev->dev;
info->fbops = &ssd1963_fbops;
info->flags = FBINFO_FLAG_DEFAULT;
info->fix = ssd1963_fix;
info->var = ssd1963_var;
ret = ssd1963_video_alloc(item);
if (ret) {
dev_err(&dev->dev,
"%s: unable to ssd1963_video_alloc\n", __func__);
goto out_info;
}
info->screen_base = (char __iomem *)item->info->fix.smem_start;
ret = ssd1963_pages_alloc(item);
if (ret < 0) {
dev_err(&dev->dev,
"%s: unable to ssd1963_pages_init\n", __func__);
goto out_video;
}
info->fbdefio = &ssd1963_defio;
fb_deferred_io_init(info);
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(&dev->dev,
"%s: unable to register_frambuffer\n", __func__);
goto out_pages;
}
ssd1963_setup(item);
ssd1963_update_all(item);
return ret;
out_pages:
ssd1963_pages_free(item);
out_video:
ssd1963_video_free(item);
out_info:
framebuffer_release(info);
out_item:
kfree(item);
out:
return ret;
}
Несколько комментариев к функции. Здесь мы последовательно:
— Выделяем память под наше устройство 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, поэтому приведу здесь только часть функции:
void ssd1963_setup(struct ssd1963 *item)
{
nhd_init_gpio_regs(); //initializations of pins in nhd_data-gpio_pin_config
at91_set_gpio_output(AT91_PIN_PE27, 0); //RESET
udelay(5);
at91_set_gpio_output(AT91_PIN_PE27, 1); //RESET
udelay(100);
nhd_write_data(NHD_COMMAND, 0x01); //Software Reset
...
nhd_write_to_register(0xe0, 0x03); //LOCK PLL
nhd_write_data(NHD_COMMAND, 0xb0); //SET LCD MODE TFT 18Bits
nhd_write_data(NHD_DATA, 0x0c); //SET MODE 24 bits & hsync+Vsync+DEN
…
}
— ssd1963_update_all устанавливает флаг must_update=1 для всех страниц и инициирует механизм обновления дисплея в отложенном контексте с помощью вызова schedule_delayed_work(&item->info->deferred_work, fbdefio->delay);
Итак, с init разобрались, с функцией remove все куда проще, освобождаем выделенную память, и возвращаем FB структуры ядру:
static int ssd1963_remove(struct platform_device *device)
{
struct fb_info *info = platform_get_drvdata(device);
struct ssd1963 *item = (struct ssd1963 *)info->par;
if (info) {
unregister_framebuffer(info);
ssd1963_pages_free(item);
ssd1963_video_free(item);
framebuffer_release(info);
kfree(item);
}
return 0;
}
Операции с фреймбуфером
Итак, пришло время рассмотреть структуру fb_ops:
static struct fb_ops ssd1963_fbops = {
.owner = THIS_MODULE,
.fb_read = fb_sys_read,
.fb_write = ssd1963_write,
.fb_fillrect = ssd1963_fillrect,
.fb_copyarea = ssd1963_copyarea,
.fb_imageblit = ssd1963_imageblit,
.fb_setcolreg = ssd1963_setcolreg,
.fb_blank = ssd1963_blank,
};
Я не привожу здесь все методы структуры, любопытный читатель сможет найти их в исходном коде модуля либо в любом другом драйвере в коде ядра в каталоге drivers/video. Как вы уже догадались, структура fb_ops описывает действия, которые может осуществлять наш драйвер. К счастью, разработчики ядра частично облегчили нам работу, предоставив стандартные функции для работы с FB, имеющие суфикс sys_ или fb_sys, например fb_sys_read. Нам нужно лишь добавить в нашу имплементацию функций из fb_ops (ssd1963_read, ssd1963_write и др.) функционал, позволяющий выполнять обновление данных в нашей импровизированной видео памяти, когда в этом возникнет необходимость.
Например, функция ssd1963_fillrect будет выглядеть так:
static void ssd1963_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
{
sys_fillrect(p, rect);
ssd1963_touch(p, rect->dx, rect->dy, rect->width, rect->height);
}
Очевидно, что системный вызов fb_fillrect обновит видео данные в определенной прямоугольной области экрана, поэтому нам нужно указать, какие именно страницы нам нужно обновить, пометив их флажком must_update, и затем вызвав вручную процедуру обновления видеопамяти:
static void ssd1963_touch(struct fb_info *info, int x, int y, int w, int h)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
struct ssd1963 *item = (struct ssd1963 *)info->par;
int i, ystart, yend;
if (fbdefio) {
//Touch the pages, so the deferred io will update them.
for (i=0; i<item->pages_count; i++) {
ystart=item->pages[i].y;
yend=item->pages[i].y+(item->pages[i].len/info->fix.line_length)+1;
if (!((y+h)<ystart || y>yend)) {
item->pages[i].must_update=1;
}
}
//Schedule the deferred IO to kick in after a delay.
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
}
Обновление данных в видеопамяти происходит в виде отложенном контексте(deferred context). User-space приложение, работающее с графикой, не будет ожидать завершения записи каждого кадра в видеопамять, что вполне логично. Отложенная обработка в fb_info определяется в виде структуры fb_deferred_io:
static struct fb_deferred_io ssd1963_defio = {
.delay = HZ / 20,
.deferred_io = &ssd1963_update,
};
Функция ssd1963_update c прототипом
void ssd1963_update(struct fb_info *info, struct list_head *pagelist);
не обновляет все страницы, а только страницы, которые были изменены в результате перезаписи user-space процессом, или в результате системного вызова, типа fb_fillrect и компании. Соответственно функция имеет вид:
static void ssd1963_update(struct fb_info *info, struct list_head *pagelist)
{
struct ssd1963 *item = (struct ssd1963 *)info->par;
struct page *page;
int i;
list_for_each_entry(page, pagelist, lru) {
item->pages[page->index].must_update=1;
}
//Copy changed pages.
for (i=0; i<item->pages_count; i++) {
if (item->pages[i].must_update) {
item->pages[i].must_update=0;
ssd1963_copy(item, i);
}
}
}
На данном этапе, вы наверняка задались вопросом, что делает функция ssd1963_copy. Она как-раз-таки делает всю “грязную” работу по передаче данных из страниц видеопамяти на искусственно созданную, 8-битную шину на базе GPIO.
Функция ssd1963_copy
Здесь необходимо вспомнить рисунок, на котором изображено как соотносятся наши страницы в памяти с пикселями дисплея. Видим, например, что в page[0] хранится информация для трех верхних линий дисплея по 320 пикселей, и 64 пикселя для 4-й линии. Таких страниц у нас 75, и картинка с рисунка, и как не сложно заметить, page[5] будет выглядеть так же – 3 линии по 320 и одна по 64. Соответственно, функция, принимающая индекс страницы как параметр будет содержать switch(index%5) и в зависимости от офсетов для каждой конкретной страницы отправлять данные в выделенное ей “окно” в памяти дисплея. Функция довольно длинная, поэтому приведу лишь ее часть:
static void ssd1963_copy(struct ssd1963 *item, unsigned int index)
{
unsigned short x,y, startx, endx, starty, endy, offset;
unsigned long *buffer;
unsigned int len;
unsigned int count;
x = item->pages[index].x;
y = item->pages[index].y;
buffer = item->pages[index].buffer;
len = item->pages[index].len;
switch (index%5) {
case 0:
offset = 0;
startx = x;
starty = y;
endx = 319;
endy = y+2;
len = 960;
nhd_set_window(startx, endx, starty, endy);
nhd_write_data(NHD_COMMAND, 0x2c);
for (count = 0; count < len; count++) {
nhd_write_data(NHD_DATA,(unsigned char)((buffer[count+offset])>>16)); //red
nhd_write_data(NHD_DATA,(unsigned char)((buffer[count+offset])>>8)); //green
nhd_write_data(NHD_DATA,(unsigned char)(buffer[count+offset])); //blue
}
offset = len;
startx = x;
starty = y+3;
endx = x+63;
endy = y+3;
len = 64;
nhd_set_window(startx, endx, starty, endy);
nhd_write_data(NHD_COMMAND, 0x2c);
for (count = 0; count < len; count++) {
nhd_write_data(NHD_DATA,(unsigned char)((buffer[count+offset])>>16)); //red
nhd_write_data(NHD_DATA,(unsigned char)((buffer[count+offset])>>8)); //green
nhd_write_data(NHD_DATA,(unsigned char)(buffer[count+offset])); //blue
}
break;
case 1:
….
Здесь функция nhd_set_window конфигурирует с помощью уже известных нам nhd_write_data(NHD_COMMAND, …); область дисплея, в которую будет производится запись данных(пикселей).
nhd_write_data(NHD_COMMAND, 0x2c); — команда LCD контроллеру о том, что сейчас последует поток данных.
Ну и напоследок, скриншот работы программы ts_calibrate из пакета tslib на устройстве с дисплеем.
Кому интересно — могу выслать полный код модуля: