1 /*
2  * Copyright (c) 2018 PHYTEC Messtechnik GmbH
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <string.h>
9 #include <zephyr/display/cfb.h>
10 
11 #define LOG_LEVEL CONFIG_CFB_LOG_LEVEL
12 #include <zephyr/logging/log.h>
13 LOG_MODULE_REGISTER(cfb);
14 
15 STRUCT_SECTION_START_EXTERN(cfb_font);
16 STRUCT_SECTION_END_EXTERN(cfb_font);
17 
byte_reverse(uint8_t b)18 static inline uint8_t byte_reverse(uint8_t b)
19 {
20 	b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
21 	b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
22 	b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
23 	return b;
24 }
25 
26 struct char_framebuffer {
27 	/** Pointer to a buffer in RAM */
28 	uint8_t *buf;
29 
30 	/** Size of the framebuffer */
31 	uint32_t size;
32 
33 	/** Pointer to the font entry array */
34 	const struct cfb_font *fonts;
35 
36 	/** Display pixel format */
37 	enum display_pixel_format pixel_format;
38 
39 	/** Display screen info */
40 	enum display_screen_info screen_info;
41 
42 	/** Resolution of a framebuffer in pixels in X direction */
43 	uint16_t x_res;
44 
45 	/** Resolution of a framebuffer in pixels in Y direction */
46 	uint16_t y_res;
47 
48 	/** Number of pixels per tile, typically 8 */
49 	uint8_t ppt;
50 
51 	/** Number of available fonts */
52 	uint8_t numof_fonts;
53 
54 	/** Current font index */
55 	uint8_t font_idx;
56 
57 	/** Font kerning */
58 	int8_t kerning;
59 
60 	/** Inverted */
61 	bool inverted;
62 };
63 
64 static struct char_framebuffer char_fb;
65 
get_glyph_ptr(const struct cfb_font * fptr,char c)66 static inline uint8_t *get_glyph_ptr(const struct cfb_font *fptr, char c)
67 {
68 	return (uint8_t *)fptr->data +
69 	       (c - fptr->first_char) *
70 	       (fptr->width * fptr->height / 8U);
71 }
72 
get_glyph_byte(uint8_t * glyph_ptr,const struct cfb_font * fptr,uint8_t x,uint8_t y)73 static inline uint8_t get_glyph_byte(uint8_t *glyph_ptr, const struct cfb_font *fptr,
74 				     uint8_t x, uint8_t y)
75 {
76 	if (fptr->caps & CFB_FONT_MONO_VPACKED) {
77 		return glyph_ptr[x * (fptr->height / 8U) + y];
78 	} else if (fptr->caps & CFB_FONT_MONO_HPACKED) {
79 		return glyph_ptr[y * (fptr->width) + x];
80 	}
81 
82 	LOG_WRN("Unknown font type");
83 	return 0;
84 }
85 
86 /*
87  * Draw the monochrome character in the monochrome tiled framebuffer,
88  * a byte is interpreted as 8 pixels ordered vertically among each other.
89  */
draw_char_vtmono(const struct char_framebuffer * fb,char c,uint16_t x,uint16_t y,bool draw_bg)90 static uint8_t draw_char_vtmono(const struct char_framebuffer *fb,
91 				char c, uint16_t x, uint16_t y,
92 				bool draw_bg)
93 {
94 	const struct cfb_font *fptr = &(fb->fonts[fb->font_idx]);
95 	const bool need_reverse = (((fb->screen_info & SCREEN_INFO_MONO_MSB_FIRST) != 0)
96 			     != ((fptr->caps & CFB_FONT_MSB_FIRST) != 0));
97 	uint8_t *glyph_ptr;
98 
99 	if (c < fptr->first_char || c > fptr->last_char) {
100 		c = ' ';
101 	}
102 
103 	glyph_ptr = get_glyph_ptr(fptr, c);
104 	if (!glyph_ptr) {
105 		return 0;
106 	}
107 
108 	for (size_t g_x = 0; g_x < fptr->width; g_x++) {
109 		const int16_t fb_x = x + g_x;
110 
111 		for (size_t g_y = 0; g_y < fptr->height; g_y++) {
112 			/*
113 			 * Process glyph rendering in the y direction
114 			 * by separating per 8-line boundaries.
115 			 */
116 
117 			const int16_t fb_y = y + g_y;
118 			const size_t fb_index = (fb_y / 8U) * fb->x_res + fb_x;
119 			const size_t offset = y % 8;
120 			uint8_t bg_mask;
121 			uint8_t byte;
122 
123 			if (fb_x < 0 || fb->x_res <= fb_x || fb_y < 0 || fb->y_res <= fb_y) {
124 				continue;
125 			}
126 
127 			byte = get_glyph_byte(glyph_ptr, fptr, g_x, g_y / 8);
128 
129 			if (offset == 0) {
130 				/*
131 				 * The start row is on an 8-line boundary.
132 				 * Therefore, it can set the value directly.
133 				 */
134 				bg_mask = 0;
135 				g_y += 7;
136 			} else if (g_y == 0) {
137 				/*
138 				 * If the starting row is not on the 8-line boundary,
139 				 * shift the glyph to the starting line, and create a mask
140 				 * from the 8-line boundary to the starting line.
141 				 */
142 				byte = byte << offset;
143 				bg_mask = BIT_MASK(offset);
144 				g_y += (7 - offset);
145 			} else {
146 				/*
147 				 * After the starting row, read and concatenate the next 8-rows
148 				 * glyph and clip to the 8-line boundary and write 8-lines
149 				 * at the time.
150 				 * Create a mask to prevent overwriting the drawing contents
151 				 * after the end row.
152 				 */
153 				const size_t lines = ((fptr->height - g_y) < 8) ? offset : 8;
154 
155 				if (lines == 8) {
156 					uint16_t byte2 = byte;
157 
158 					byte2 |= (get_glyph_byte(glyph_ptr, fptr, g_x,
159 								 (g_y + 8) / 8))
160 						  << 8;
161 					byte = (byte2 >> (8 - offset)) & BIT_MASK(lines);
162 				} else {
163 					byte = (byte >> (8 - offset)) & BIT_MASK(lines);
164 				}
165 
166 				bg_mask = (BIT_MASK(8 - lines) << (lines)) & 0xFF;
167 				g_y += (lines - 1);
168 			}
169 
170 			if (need_reverse) {
171 				byte = byte_reverse(byte);
172 				bg_mask = byte_reverse(bg_mask);
173 			}
174 
175 			if (draw_bg) {
176 				fb->buf[fb_index] &= bg_mask;
177 			}
178 
179 			fb->buf[fb_index] |= byte;
180 		}
181 	}
182 
183 	return fptr->width;
184 }
185 
draw_point(struct char_framebuffer * fb,int16_t x,int16_t y)186 static inline void draw_point(struct char_framebuffer *fb, int16_t x, int16_t y)
187 {
188 	const bool need_reverse = ((fb->screen_info & SCREEN_INFO_MONO_MSB_FIRST) != 0);
189 	const size_t index = ((y / 8) * fb->x_res);
190 	uint8_t m = BIT(y % 8);
191 
192 	if (x < 0 || x >= fb->x_res) {
193 		return;
194 	}
195 
196 	if (y < 0 || y >= fb->y_res) {
197 		return;
198 	}
199 
200 	if (need_reverse) {
201 		m = byte_reverse(m);
202 	}
203 
204 	fb->buf[index + x] |= m;
205 }
206 
draw_line(struct char_framebuffer * fb,int16_t x0,int16_t y0,int16_t x1,int16_t y1)207 static void draw_line(struct char_framebuffer *fb, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
208 {
209 	int16_t sx = (x0 < x1) ? 1 : -1;
210 	int16_t sy = (y0 < y1) ? 1 : -1;
211 	int16_t dx = (sx > 0) ? (x1 - x0) : (x0 - x1);
212 	int16_t dy = (sy > 0) ? (y0 - y1) : (y1 - y0);
213 	int16_t err = dx + dy;
214 	int16_t e2;
215 
216 	while (true) {
217 		draw_point(fb, x0, y0);
218 
219 		if (x0 == x1 && y0 == y1) {
220 			break;
221 		}
222 
223 		e2 = 2 * err;
224 
225 		if (e2 >= dy) {
226 			err += dy;
227 			x0 += sx;
228 		}
229 
230 		if (e2 <= dx) {
231 			err += dx;
232 			y0 += sy;
233 		}
234 	}
235 }
236 
draw_text(const struct device * dev,const char * const str,int16_t x,int16_t y,bool wrap)237 static int draw_text(const struct device *dev, const char *const str, int16_t x, int16_t y,
238 		     bool wrap)
239 {
240 	const struct char_framebuffer *fb = &char_fb;
241 	const struct cfb_font *fptr;
242 
243 	if (!fb->fonts || !fb->buf) {
244 		return -ENODEV;
245 	}
246 
247 	fptr = &(fb->fonts[fb->font_idx]);
248 
249 	if (fptr->height % 8) {
250 		LOG_ERR("Wrong font size");
251 		return -EINVAL;
252 	}
253 
254 	if ((fb->screen_info & SCREEN_INFO_MONO_VTILED)) {
255 		for (size_t i = 0; i < strlen(str); i++) {
256 			if ((x + fptr->width > fb->x_res) && wrap) {
257 				x = 0U;
258 				y += fptr->height;
259 			}
260 			x += fb->kerning + draw_char_vtmono(fb, str[i], x, y, wrap);
261 		}
262 		return 0;
263 	}
264 
265 	LOG_ERR("Unsupported framebuffer configuration");
266 	return -EINVAL;
267 }
268 
cfb_draw_point(const struct device * dev,const struct cfb_position * pos)269 int cfb_draw_point(const struct device *dev, const struct cfb_position *pos)
270 {
271 	struct char_framebuffer *fb = &char_fb;
272 
273 	draw_point(fb, pos->x, pos->y);
274 
275 	return 0;
276 }
277 
cfb_draw_line(const struct device * dev,const struct cfb_position * start,const struct cfb_position * end)278 int cfb_draw_line(const struct device *dev, const struct cfb_position *start,
279 		  const struct cfb_position *end)
280 {
281 	struct char_framebuffer *fb = &char_fb;
282 
283 	draw_line(fb, start->x, start->y, end->x, end->y);
284 
285 	return 0;
286 }
287 
cfb_draw_rect(const struct device * dev,const struct cfb_position * start,const struct cfb_position * end)288 int cfb_draw_rect(const struct device *dev, const struct cfb_position *start,
289 		  const struct cfb_position *end)
290 {
291 	struct char_framebuffer *fb = &char_fb;
292 
293 	draw_line(fb, start->x, start->y, end->x, start->y);
294 	draw_line(fb, end->x, start->y, end->x, end->y);
295 	draw_line(fb, end->x, end->y, start->x, end->y);
296 	draw_line(fb, start->x, end->y, start->x, start->y);
297 
298 	return 0;
299 }
300 
cfb_draw_text(const struct device * dev,const char * const str,int16_t x,int16_t y)301 int cfb_draw_text(const struct device *dev, const char *const str, int16_t x, int16_t y)
302 {
303 	return draw_text(dev, str, x, y, false);
304 }
305 
cfb_print(const struct device * dev,const char * const str,uint16_t x,uint16_t y)306 int cfb_print(const struct device *dev, const char *const str, uint16_t x, uint16_t y)
307 {
308 	return draw_text(dev, str, x, y, true);
309 }
310 
cfb_invert_area(const struct device * dev,uint16_t x,uint16_t y,uint16_t width,uint16_t height)311 int cfb_invert_area(const struct device *dev, uint16_t x, uint16_t y,
312 		    uint16_t width, uint16_t height)
313 {
314 	const struct char_framebuffer *fb = &char_fb;
315 	const bool need_reverse = ((fb->screen_info & SCREEN_INFO_MONO_MSB_FIRST) != 0);
316 
317 	if (x >= fb->x_res || y >= fb->y_res) {
318 		LOG_ERR("Coordinates outside of framebuffer");
319 
320 		return -EINVAL;
321 	}
322 
323 	if ((fb->screen_info & SCREEN_INFO_MONO_VTILED)) {
324 		if (x > fb->x_res) {
325 			x = fb->x_res;
326 		}
327 
328 		if (y > fb->y_res) {
329 			y = fb->y_res;
330 		}
331 
332 		if (x + width > fb->x_res) {
333 			width = fb->x_res - x;
334 		}
335 
336 		if (y + height > fb->y_res) {
337 			height = fb->y_res - y;
338 		}
339 
340 		for (size_t i = x; i < x + width; i++) {
341 			for (size_t j = y; j < (y + height); j++) {
342 				/*
343 				 * Process inversion in the y direction
344 				 * by separating per 8-line boundaries.
345 				 */
346 
347 				const size_t index = ((j / 8) * fb->x_res) + i;
348 				const uint8_t remains = y + height - j;
349 
350 				/*
351 				 * Make mask to prevent overwriting the drawing contents that on
352 				 * between the start line or end line and the 8-line boundary.
353 				 */
354 				if ((j % 8) > 0) {
355 					uint8_t m = BIT_MASK((j % 8));
356 					uint8_t b = fb->buf[index];
357 
358 					/*
359 					 * Generate mask for remaining lines in case of
360 					 * drawing within 8 lines from the start line
361 					 */
362 					if (remains < 8) {
363 						m |= BIT_MASK((8 - (j % 8) + remains))
364 						     << ((j % 8) + remains);
365 					}
366 
367 					if (need_reverse) {
368 						m = byte_reverse(m);
369 					}
370 
371 					fb->buf[index] = (b ^ (~m));
372 					j += 7 - (j % 8);
373 				} else if (remains >= 8) {
374 					/* No mask required if no start or end line is included */
375 					fb->buf[index] = ~fb->buf[index];
376 					j += 7;
377 				} else {
378 					uint8_t m = BIT_MASK(8 - remains) << (remains);
379 					uint8_t b = fb->buf[index];
380 
381 					if (need_reverse) {
382 						m = byte_reverse(m);
383 					}
384 
385 					fb->buf[index] = (b ^ (~m));
386 					j += (remains - 1);
387 				}
388 			}
389 		}
390 
391 		return 0;
392 	}
393 
394 	LOG_ERR("Unsupported framebuffer configuration");
395 	return -EINVAL;
396 }
397 
cfb_invert(const struct char_framebuffer * fb)398 static int cfb_invert(const struct char_framebuffer *fb)
399 {
400 	for (size_t i = 0; i < fb->x_res * fb->y_res / 8U; i++) {
401 		fb->buf[i] = ~fb->buf[i];
402 	}
403 
404 	return 0;
405 }
406 
cfb_framebuffer_clear(const struct device * dev,bool clear_display)407 int cfb_framebuffer_clear(const struct device *dev, bool clear_display)
408 {
409 	const struct char_framebuffer *fb = &char_fb;
410 
411 	if (!fb || !fb->buf) {
412 		return -ENODEV;
413 	}
414 
415 	memset(fb->buf, 0, fb->size);
416 
417 	if (clear_display) {
418 		cfb_framebuffer_finalize(dev);
419 	}
420 
421 	return 0;
422 }
423 
cfb_framebuffer_invert(const struct device * dev)424 int cfb_framebuffer_invert(const struct device *dev)
425 {
426 	struct char_framebuffer *fb = &char_fb;
427 
428 	if (!fb) {
429 		return -ENODEV;
430 	}
431 
432 	fb->inverted = !fb->inverted;
433 
434 	return 0;
435 }
436 
cfb_framebuffer_finalize(const struct device * dev)437 int cfb_framebuffer_finalize(const struct device *dev)
438 {
439 	const struct display_driver_api *api = dev->api;
440 	const struct char_framebuffer *fb = &char_fb;
441 	struct display_buffer_descriptor desc;
442 	int err;
443 
444 	if (!fb || !fb->buf) {
445 		return -ENODEV;
446 	}
447 
448 	desc.buf_size = fb->size;
449 	desc.width = fb->x_res;
450 	desc.height = fb->y_res;
451 	desc.pitch = fb->x_res;
452 
453 	if (!(fb->pixel_format & PIXEL_FORMAT_MONO10) != !(fb->inverted)) {
454 		cfb_invert(fb);
455 		err = api->write(dev, 0, 0, &desc, fb->buf);
456 		cfb_invert(fb);
457 		return err;
458 	}
459 
460 	return api->write(dev, 0, 0, &desc, fb->buf);
461 }
462 
cfb_get_display_parameter(const struct device * dev,enum cfb_display_param param)463 int cfb_get_display_parameter(const struct device *dev,
464 			       enum cfb_display_param param)
465 {
466 	const struct char_framebuffer *fb = &char_fb;
467 
468 	switch (param) {
469 	case CFB_DISPLAY_HEIGH:
470 		return fb->y_res;
471 	case CFB_DISPLAY_WIDTH:
472 		return fb->x_res;
473 	case CFB_DISPLAY_PPT:
474 		return fb->ppt;
475 	case CFB_DISPLAY_ROWS:
476 		if (fb->screen_info & SCREEN_INFO_MONO_VTILED) {
477 			return fb->y_res / fb->ppt;
478 		}
479 		return fb->y_res;
480 	case CFB_DISPLAY_COLS:
481 		if (fb->screen_info & SCREEN_INFO_MONO_VTILED) {
482 			return fb->x_res;
483 		}
484 		return fb->x_res / fb->ppt;
485 	}
486 	return 0;
487 }
488 
cfb_framebuffer_set_font(const struct device * dev,uint8_t idx)489 int cfb_framebuffer_set_font(const struct device *dev, uint8_t idx)
490 {
491 	struct char_framebuffer *fb = &char_fb;
492 
493 	if (idx >= fb->numof_fonts) {
494 		return -EINVAL;
495 	}
496 
497 	fb->font_idx = idx;
498 
499 	return 0;
500 }
501 
cfb_get_font_size(const struct device * dev,uint8_t idx,uint8_t * width,uint8_t * height)502 int cfb_get_font_size(const struct device *dev, uint8_t idx, uint8_t *width,
503 		      uint8_t *height)
504 {
505 	const struct char_framebuffer *fb = &char_fb;
506 
507 	if (idx >= fb->numof_fonts) {
508 		return -EINVAL;
509 	}
510 
511 	if (width) {
512 		*width = TYPE_SECTION_START(cfb_font)[idx].width;
513 	}
514 
515 	if (height) {
516 		*height = TYPE_SECTION_START(cfb_font)[idx].height;
517 	}
518 
519 	return 0;
520 }
521 
cfb_set_kerning(const struct device * dev,int8_t kerning)522 int cfb_set_kerning(const struct device *dev, int8_t kerning)
523 {
524 	char_fb.kerning = kerning;
525 
526 	return 0;
527 }
528 
cfb_get_numof_fonts(const struct device * dev)529 int cfb_get_numof_fonts(const struct device *dev)
530 {
531 	const struct char_framebuffer *fb = &char_fb;
532 
533 	return fb->numof_fonts;
534 }
535 
cfb_framebuffer_init(const struct device * dev)536 int cfb_framebuffer_init(const struct device *dev)
537 {
538 	const struct display_driver_api *api = dev->api;
539 	struct char_framebuffer *fb = &char_fb;
540 	struct display_capabilities cfg;
541 
542 	api->get_capabilities(dev, &cfg);
543 
544 	STRUCT_SECTION_COUNT(cfb_font, &fb->numof_fonts);
545 
546 	LOG_DBG("number of fonts %d", fb->numof_fonts);
547 
548 	fb->x_res = cfg.x_resolution;
549 	fb->y_res = cfg.y_resolution;
550 	fb->ppt = 8U;
551 	fb->pixel_format = cfg.current_pixel_format;
552 	fb->screen_info = cfg.screen_info;
553 	fb->buf = NULL;
554 	fb->kerning = 0;
555 	fb->inverted = false;
556 
557 	fb->fonts = TYPE_SECTION_START(cfb_font);
558 	fb->font_idx = 0U;
559 
560 	fb->size = fb->x_res * fb->y_res / fb->ppt;
561 	fb->buf = k_malloc(fb->size);
562 	if (!fb->buf) {
563 		return -ENOMEM;
564 	}
565 
566 	memset(fb->buf, 0, fb->size);
567 
568 	return 0;
569 }
570