1 /*
2  * Copyright (c) 2017 Intel Corporation
3  * Copyright (c) 2021, Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /*
9  * This tool uses display controller driver API and requires
10  * a suitable LED matrix controller driver.
11  */
12 
13 #include <zephyr/kernel.h>
14 #include <zephyr/init.h>
15 #include <string.h>
16 #include <zephyr/sys/printk.h>
17 
18 #include <zephyr/display/mb_display.h>
19 #include <zephyr/drivers/display.h>
20 
21 #include "mb_font.h"
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(mb_disp, CONFIG_DISPLAY_LOG_LEVEL);
25 
26 #define MODE_MASK    BIT_MASK(16)
27 
28 #define SCROLL_OFF   0
29 #define SCROLL_START 1
30 #define SCROLL_DEFAULT_DURATION_MS 80
31 
32 #define MB_DISP_XRES 5
33 #define MB_DISP_YRES 5
34 
35 struct mb_display {
36 	const struct device *lm_dev;   /* LED matrix display device */
37 
38 	struct k_work_delayable dwork; /* Delayable work item */
39 
40 	uint8_t         img_count;     /* Image count */
41 
42 	uint8_t         cur_img;       /* Current image or character to show */
43 
44 	uint8_t         scroll:3,      /* Scroll shift */
45 			first:1,       /* First frame of a scroll sequence */
46 			loop:1,        /* Loop to beginning */
47 			text:1,        /* We're showing a string (not image) */
48 			img_sep:1,     /* One column image separation */
49 			msb:1;         /* MSB represents the first pixel */
50 
51 	int32_t         duration;      /* Duration for each shown image */
52 
53 	union {
54 		const struct mb_image *img; /* Array of images to show */
55 		const char            *str; /* String to be shown */
56 	};
57 
58 	/* Buffer for printed strings */
59 	char            str_buf[CONFIG_MICROBIT_DISPLAY_STR_MAX];
60 };
61 
get_font(char ch)62 static inline const struct mb_image *get_font(char ch)
63 {
64 	if (ch < MB_FONT_START || ch > MB_FONT_END) {
65 		return &mb_font[' ' - MB_FONT_START];
66 	}
67 
68 	return &mb_font[ch - MB_FONT_START];
69 }
70 
flip_pixels(uint8_t b)71 static ALWAYS_INLINE uint8_t flip_pixels(uint8_t b)
72 {
73 	b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
74 	b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
75 	b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
76 
77 	return b;
78 }
79 
update_content(struct mb_display * disp,const struct mb_image * img)80 static int update_content(struct mb_display *disp, const struct mb_image *img)
81 {
82 	const struct display_buffer_descriptor buf_desc = {
83 		.buf_size = sizeof(struct mb_image),
84 		.width    = MB_DISP_XRES,
85 		.height   = MB_DISP_YRES,
86 		.pitch    = 8,
87 	};
88 	struct mb_image tmp_img;
89 	int ret;
90 
91 	if (disp->msb) {
92 		for (int i = 0; i < sizeof(struct mb_image); i++) {
93 			tmp_img.row[i] = flip_pixels(img->row[i]);
94 		}
95 
96 		ret = display_write(disp->lm_dev, 0, 0, &buf_desc, &tmp_img);
97 	} else {
98 		ret = display_write(disp->lm_dev, 0, 0, &buf_desc, img);
99 	}
100 
101 	if (ret < 0) {
102 		LOG_ERR("Write to display controller failed");
103 		return ret;
104 	}
105 
106 	LOG_DBG("Image duration %d", disp->duration);
107 	if (disp->duration != SYS_FOREVER_MS) {
108 		k_work_reschedule(&disp->dwork, K_MSEC(disp->duration));
109 	}
110 
111 	return ret;
112 }
113 
start_image(struct mb_display * disp,const struct mb_image * img)114 static int start_image(struct mb_display *disp, const struct mb_image *img)
115 {
116 	int ret;
117 
118 	ret = display_blanking_off(disp->lm_dev);
119 	if (ret < 0) {
120 		LOG_ERR("Set blanking off failed");
121 		return ret;
122 	}
123 
124 	return update_content(disp, img);
125 }
126 
reset_display(struct mb_display * disp)127 static int reset_display(struct mb_display *disp)
128 {
129 	int ret;
130 
131 	disp->str = NULL;
132 	disp->cur_img = 0U;
133 	disp->img = NULL;
134 	disp->img_count = 0U;
135 	disp->scroll = SCROLL_OFF;
136 
137 	ret = display_blanking_on(disp->lm_dev);
138 	if (ret < 0) {
139 		LOG_ERR("Set blanking on failed");
140 	}
141 
142 	return ret;
143 }
144 
current_img(struct mb_display * disp)145 static const struct mb_image *current_img(struct mb_display *disp)
146 {
147 	if (disp->scroll && disp->first) {
148 		return get_font(' ');
149 	}
150 
151 	if (disp->text) {
152 		return get_font(disp->str[disp->cur_img]);
153 	} else {
154 		return &disp->img[disp->cur_img];
155 	}
156 }
157 
next_img(struct mb_display * disp)158 static const struct mb_image *next_img(struct mb_display *disp)
159 {
160 	if (disp->text) {
161 		if (disp->first) {
162 			return get_font(disp->str[0]);
163 		} else if (disp->str[disp->cur_img]) {
164 			return get_font(disp->str[disp->cur_img + 1]);
165 		} else {
166 			return get_font(' ');
167 		}
168 	} else {
169 		if (disp->first) {
170 			return &disp->img[0];
171 		} else if (disp->cur_img < (disp->img_count - 1)) {
172 			return &disp->img[disp->cur_img + 1];
173 		} else {
174 			return get_font(' ');
175 		}
176 	}
177 }
178 
last_frame(struct mb_display * disp)179 static inline bool last_frame(struct mb_display *disp)
180 {
181 	if (disp->text) {
182 		return (disp->str[disp->cur_img] == '\0');
183 	} else {
184 		return (disp->cur_img >= disp->img_count);
185 	}
186 }
187 
scroll_steps(struct mb_display * disp)188 static inline uint8_t scroll_steps(struct mb_display *disp)
189 {
190 	return MB_DISP_XRES + disp->img_sep;
191 }
192 
update_scroll(struct mb_display * disp)193 static int update_scroll(struct mb_display *disp)
194 {
195 	if (disp->scroll < scroll_steps(disp)) {
196 		struct mb_image img;
197 
198 		for (int i = 0; i < MB_DISP_XRES; i++) {
199 			const struct mb_image *i1 = current_img(disp);
200 			const struct mb_image *i2 = next_img(disp);
201 
202 			img.row[i] = ((i1->row[i] >> disp->scroll) |
203 				      (i2->row[i] << (scroll_steps(disp) -
204 						      disp->scroll)));
205 		}
206 
207 		disp->scroll++;
208 		return update_content(disp, &img);
209 	} else {
210 		if (disp->first) {
211 			disp->first = 0U;
212 		} else {
213 			disp->cur_img++;
214 		}
215 
216 		if (last_frame(disp)) {
217 			if (!disp->loop) {
218 				return reset_display(disp);
219 			}
220 
221 			disp->cur_img = 0U;
222 			disp->first = 1U;
223 		}
224 
225 		disp->scroll = SCROLL_START;
226 		return update_content(disp, current_img(disp));
227 	}
228 }
229 
update_image(struct mb_display * disp)230 static int update_image(struct mb_display *disp)
231 {
232 	disp->cur_img++;
233 
234 	if (last_frame(disp)) {
235 		if (!disp->loop) {
236 			return reset_display(disp);
237 		}
238 
239 		disp->cur_img = 0U;
240 	}
241 
242 	return update_content(disp, current_img(disp));
243 }
244 
update_display_work(struct k_work * work)245 static void update_display_work(struct k_work *work)
246 {
247 	struct k_work_delayable *dwork = k_work_delayable_from_work(work);
248 	struct mb_display *disp = CONTAINER_OF(dwork, struct mb_display, dwork);
249 	int ret;
250 
251 	if (disp->scroll) {
252 		ret = update_scroll(disp);
253 	} else {
254 		ret = update_image(disp);
255 	}
256 
257 	__ASSERT(ret == 0, "Failed to update display");
258 }
259 
start_scroll(struct mb_display * disp,int32_t duration)260 static int start_scroll(struct mb_display *disp, int32_t duration)
261 {
262 	/* Divide total duration by number of scrolling steps */
263 	if (duration) {
264 		disp->duration = duration / scroll_steps(disp);
265 	} else {
266 		disp->duration = SCROLL_DEFAULT_DURATION_MS;
267 	}
268 
269 	disp->scroll = SCROLL_START;
270 	disp->first = 1U;
271 	disp->cur_img = 0U;
272 	return start_image(disp, get_font(' '));
273 }
274 
start_single(struct mb_display * disp,int32_t duration)275 static int start_single(struct mb_display *disp, int32_t duration)
276 {
277 	disp->duration = duration;
278 
279 	if (disp->text) {
280 		return start_image(disp, get_font(disp->str[0]));
281 	} else {
282 		return start_image(disp, disp->img);
283 	}
284 }
285 
mb_display_stop(struct mb_display * disp)286 void mb_display_stop(struct mb_display *disp)
287 {
288 	struct k_work_sync sync;
289 	int ret;
290 
291 	k_work_cancel_delayable_sync(&disp->dwork, &sync);
292 	LOG_DBG("delayable work stopped %p", disp);
293 	ret = reset_display(disp);
294 	__ASSERT(ret == 0, "Failed to reset display");
295 }
296 
mb_display_image(struct mb_display * disp,uint32_t mode,int32_t duration,const struct mb_image * img,uint8_t img_count)297 void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration,
298 		      const struct mb_image *img, uint8_t img_count)
299 {
300 	int ret;
301 
302 	mb_display_stop(disp);
303 
304 	__ASSERT(img && img_count > 0, "Invalid parameters");
305 
306 	disp->text = 0U;
307 	disp->img_count = img_count;
308 	disp->img = img;
309 	disp->img_sep = 0U;
310 	disp->cur_img = 0U;
311 	disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
312 
313 	switch (mode & MODE_MASK) {
314 	case MB_DISPLAY_MODE_DEFAULT:
315 	case MB_DISPLAY_MODE_SINGLE:
316 		ret = start_single(disp, duration);
317 		__ASSERT(ret == 0, "Failed to start single mode");
318 		break;
319 	case MB_DISPLAY_MODE_SCROLL:
320 		ret = start_scroll(disp, duration);
321 		__ASSERT(ret == 0, "Failed to start scroll mode");
322 		break;
323 	default:
324 		__ASSERT(0, "Invalid display mode");
325 	}
326 }
327 
mb_display_print(struct mb_display * disp,uint32_t mode,int32_t duration,const char * fmt,...)328 void mb_display_print(struct mb_display *disp, uint32_t mode,
329 		      int32_t duration, const char *fmt, ...)
330 {
331 	va_list ap;
332 	int ret;
333 
334 	mb_display_stop(disp);
335 
336 	va_start(ap, fmt);
337 	vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap);
338 	va_end(ap);
339 
340 	if (disp->str_buf[0] == '\0') {
341 		return;
342 	}
343 
344 	disp->str = disp->str_buf;
345 	disp->text = 1U;
346 	disp->img_sep = 1U;
347 	disp->cur_img = 0U;
348 	disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
349 
350 	switch (mode & MODE_MASK) {
351 	case MB_DISPLAY_MODE_DEFAULT:
352 	case MB_DISPLAY_MODE_SCROLL:
353 		ret = start_scroll(disp, duration);
354 		__ASSERT(ret == 0, "Failed to start scroll mode");
355 		break;
356 	case MB_DISPLAY_MODE_SINGLE:
357 		ret = start_single(disp, duration);
358 		__ASSERT(ret == 0, "Failed to start single mode");
359 		break;
360 	default:
361 		__ASSERT(0, "Invalid display mode");
362 	}
363 }
364 
mb_display_init(struct mb_display * disp)365 static int mb_display_init(struct mb_display *disp)
366 {
367 	struct display_capabilities caps;
368 	int ret;
369 
370 	display_get_capabilities(disp->lm_dev, &caps);
371 	if (caps.x_resolution != MB_DISP_XRES ||
372 	    caps.y_resolution != MB_DISP_YRES) {
373 		LOG_ERR("Not supported display resolution");
374 		return -ENOTSUP;
375 	}
376 
377 	if (caps.screen_info & SCREEN_INFO_MONO_MSB_FIRST) {
378 		disp->msb = 1U;
379 	}
380 
381 	ret = display_set_brightness(disp->lm_dev, 0xFF);
382 	if (ret < 0) {
383 		LOG_ERR("Failed to set brightness");
384 		return ret;
385 	}
386 
387 	k_work_init_delayable(&disp->dwork, update_display_work);
388 
389 	return 0;
390 }
391 
392 static struct mb_display display;
393 
mb_display_get(void)394 struct mb_display *mb_display_get(void)
395 {
396 	return &display;
397 }
398 
mb_display_init_on_boot(void)399 static int mb_display_init_on_boot(void)
400 {
401 
402 	display.lm_dev = DEVICE_DT_GET_ONE(nordic_nrf_led_matrix);
403 	if (!device_is_ready(display.lm_dev)) {
404 		LOG_ERR("Display controller device not ready");
405 		return -ENODEV;
406 	}
407 
408 	return mb_display_init(&display);
409 }
410 
411 SYS_INIT(mb_display_init_on_boot, APPLICATION, CONFIG_DISPLAY_INIT_PRIORITY);
412