f17ebc73287a2eff7eaa083f37fb400e6bf3548c
[dcpu16] / hw_lem1802.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <sys/time.h>
6
7 #ifdef HAVE_LIBPNG
8 #include <setjmp.h>
9 #include <png.h>
10 #endif /* HAVE_LIBPNG */
11
12 #ifdef HAVE_LIBVNCSERVER
13 #include <rfb/rfb.h>
14 #include <rfb/keysym.h>
15 #endif /* HAVE_LIBVNCSERVER */
16
17 #include "dcpu16.h"
18 #include "chargen-4x8.h"
19 #include "hw_lem1802.h"
20
21 #ifdef DEBUG
22 #define TRACE(...) do { printf("[debug] "); printf(__VA_ARGS__); printf("\n"); } while (0)
23 #else /* DEBUG */
24 #define TRACE(...) do {} while (0)
25 #endif /* DEBUG */
26
27 #ifdef WANT_VARIADIC_VOIDP_CAST
28 #define VOIDP(__x__) ((void *)(__x__))
29 #define VOIDFP(__x__) ((void *)(size_t)(__x__))
30 #else
31 #define VOIDP(__x__) (__x__)
32 #define VOIDFP(__x__) (__x__)
33 #endif
34
35 #define LEM1802_POWER_ON_CYCLES 100000 /* this should vary by, let us say, 10% */
36
37 #define PIX_X 160 /* pixels in display */
38 #define PIX_Y 128 /* pixels in display */
39 #define PIX_BORDER 16 /* border pixels from edge to first tile */
40 #define CELL_X_SZ 4
41 #define CELL_Y_SZ 8
42 #define CELL_X ((PIX_X - (2 * PIX_BORDER)) / CELL_X_SZ) /* tiles in display */
43 #define CELL_Y ((PIX_Y - (2 * PIX_BORDER)) / CELL_Y_SZ) /* tiles in display */
44
45 #define PALETTE_ENTRIES 16
46 static const DCPU16_WORD palette_default_[PALETTE_ENTRIES] = {
47 0x0000, /* black */
48 0x000a, /* blue */
49 0x00a0, /* green */
50 0x00aa, /* cyan */
51 0x0a05, /* red */
52 0x0a0f, /* magenta */
53 0x0aa5, /* yellow */
54 0x0aaf, /* pale blue */
55 0x0555, /* grey */
56 0x055f, /* light blue */
57 0x05f5, /* light green*/
58 0x05ff, /* light cyan */
59 0x0f55, /* light red */
60 0x0f5f, /* light magenta */
61 0x0ff5, /* light yellow */
62 0x0fff /* white */
63 };
64
65 struct pixel_ {
66 unsigned char r;
67 unsigned char g;
68 unsigned char b;
69 unsigned char a;
70 };
71
72 struct lem1802_ {
73 long long cycle_activated; /* running since */
74 long long cycles_until_active_; /* for tracking power-up delay */
75
76 DCPU16_WORD video_base;
77 DCPU16_WORD font_base;
78 DCPU16_WORD palette_base;
79 DCPU16_WORD border_color;
80 struct pixel_ *pixbuf;
81
82 unsigned int refresh_rate; /* redraw every n cycles */
83 unsigned int refresh_tally_; /* tick */
84
85 unsigned int blink_rate; /* toggle every n cycles? still figuring this out.. */
86 unsigned int blink_tally_; /* tick */
87 unsigned int blink_state;
88
89 enum cycle_state_ {
90 CYCLE_IDLE,
91 CYCLE_COPY_TO_RAM,
92 } cycle_state_;
93 const DCPU16_WORD *cycle_state_copy_src_ptr_;
94 DCPU16_WORD cycle_state_copy_dst_addr_;
95 size_t cycle_state_copy_words_;
96
97 int (*render)(void *, struct pixel_ *, size_t, size_t);
98 void *renderer_data;
99 };
100
101 static
102 long long power_on_cycles_(void) {
103 struct tv;
104 long long r = 0;
105
106 #if WANT_DELAY_START
107 gettimeofday(&tv, NULL);
108 r += LEM1802_POWER_ON_CYCLES - (LEM1802_POWER_ON_CYCLES / 10);
109 r += tv.tv_usec % (LEM1802_POWER_ON_CYCLES / 5);
110 #endif
111
112 return r;
113 }
114
115 static inline
116 void pixel_color_(struct pixel_ *pix, DCPU16_WORD color) {
117 unsigned char x;
118
119 x = (color >> 0) & 0x000f;
120 pix->r = x | (x << 4);
121
122 x = (color >> 4) & 0x000f;
123 pix->g = x | (x << 4);
124
125 x = (color >> 8) & 0x000f;
126 pix->b = x | (x << 4);
127
128 x = (color >> 12) & 0x000f;
129 pix->a = x | (x << 4);
130 }
131
132 static
133 void pixbuf_border_paint_(struct pixel_ *pixbuf, struct pixel_ *border) {
134 size_t x, y, i;
135
136 TRACE("%s>> painted border", __func__);
137
138 /* top */
139 for (y = 0; y < PIX_BORDER; y++) {
140 for (x = 0; x < PIX_X; x++) {
141 i = (y * PIX_X) + x;
142 pixbuf[i] = *border;
143 i = ((PIX_Y - y) * PIX_X) + x;
144 pixbuf[i] = *border;
145 }
146 }
147
148 /* sides */
149 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
150 for (x = 0; x < PIX_BORDER; x++) {
151 i = (y * PIX_X) + x;
152 pixbuf[i] = *border;
153 pixbuf[i + (PIX_X - PIX_BORDER)] = *border;
154 }
155 }
156
157 static
158 void font_tile_paint_(struct pixel_ *p, struct pixel_ *fg, struct pixel_ *bg, DCPU16_WORD *tile) {
159 size_t pix_x, pix_y;
160 unsigned char *font_bitmap = (unsigned char *)tile;
161
162 #if 0
163 TRACE("%s>> fg:(%u,%u,%u) bg:(%u,%u,%u) font_bitmap:%02x %02x %02x %02x", __func__,
164 fg->r, fg->g, fg->b,
165 bg->r, bg->g, bg->b,
166 font_bitmap[0], font_bitmap[1], font_bitmap[2], font_bitmap[3]);
167 #endif
168
169 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
170 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
171 if ((font_bitmap[pix_x] >> pix_y) & 0x01)
172 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *fg;
173 else
174 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *bg;
175 }
176 }
177 }
178
179 static
180 void pixbuf_addr_paint_(struct pixel_ *pixbuf, DCPU16_WORD *mem, DCPU16_WORD base, DCPU16_WORD addr, DCPU16_WORD *palette, DCPU16_WORD *tiles, unsigned int blink_state) {
181 struct pixel_ *tilestart = pixbuf; /* start of display */
182 unsigned int cell_x = addr % (PIX_X / CELL_X_SZ),
183 cell_y = addr / (PIX_X / CELL_X_SZ);
184 struct pixel_ fg, bg;
185 DCPU16_WORD *font_bitmap;
186 int blink;
187
188 cell_x = (addr - base) % CELL_X;
189 cell_y = (addr - base) / CELL_X;
190
191 #if 0
192 TRACE("%s>> addr:0x%04x col:%u row:%u v:%hu",
193 __func__,
194 addr,
195 cell_x, cell_y, mem[addr]);
196 #endif
197
198 blink = mem[addr] & 0x0080;
199
200 /* tiles take two words each */
201 font_bitmap = tiles + (2 * (mem[addr] & 0x7f));
202
203 pixel_color_(&bg, palette[(mem[addr] >> 8) & 0x0f]);
204 if (blink && blink_state)
205 pixel_color_(&fg, palette[(mem[addr] >> 8) & 0x0f]);
206 else
207 pixel_color_(&fg, palette[(mem[addr] >> 12) & 0x0f]);
208
209 tilestart += (PIX_X * PIX_BORDER); /* skip top border */
210 tilestart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
211
212 tilestart += PIX_BORDER; /* skip side border */
213 tilestart += (CELL_X_SZ) * cell_x; /* skip to column */
214
215 font_tile_paint_(tilestart, &fg, &bg, font_bitmap);
216 }
217
218 static
219 void lem1802_pixbuf_refresh_full_(struct lem1802_ *display, DCPU16_WORD *mem) {
220 struct pixel_ border;
221 size_t tile;
222
223 #if 0
224 TRACE("%s>> video_base:0x%04x", __func__, display->video_base);
225 #endif
226
227 if (display->cycles_until_active_) {
228 /* show cute power-up sequence.. */
229 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
230 return;
231 }
232
233 if (display->video_base == 0) {
234 /* disconnected, blank display */
235 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
236 return;
237 }
238
239 pixel_color_(&border, display->border_color);
240 pixbuf_border_paint_(display->pixbuf, &border);
241
242 for (tile = 0; tile < CELL_X * CELL_Y; tile++) {
243 pixbuf_addr_paint_(display->pixbuf,
244 mem,
245 display->video_base,
246 display->video_base + tile,
247 display->palette_base ? mem + display->palette_base : (DCPU16_WORD *)palette_default_,
248 display->font_base ? mem + display->font_base : (DCPU16_WORD *)chargen_4x8_glyphs,
249 display->blink_state);
250 }
251 }
252
253 static
254 int pixbuf_render_pnm_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
255 FILE *f = (FILE *)data;
256 size_t i;
257 fprintf(f, "P6\n"
258 "%lu %lu\n"
259 "255\n",
260 x, y);
261 for (i = 0; i < x * y; i++) {
262 fwrite(&pixbuf[i].r, 1, 1, f);
263 fwrite(&pixbuf[i].g, 1, 1, f);
264 fwrite(&pixbuf[i].b, 1, 1, f);
265 }
266 fclose(f);
267
268 return 0;
269 }
270
271 #ifdef HAVE_LIBPNG
272 static
273 int pixbuf_render_png_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
274 FILE *f = (FILE *)data;
275 int retval = 0;
276 png_structp png;
277 png_infop info;
278 size_t i;
279
280 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
281 if (png == NULL) {
282 goto f_done;
283 }
284
285 info = png_create_info_struct(png);
286 if (info == NULL) {
287 png_destroy_write_struct(&png, (png_infopp)NULL);
288 goto f_done;
289 }
290
291 if (setjmp(png_jmpbuf(png))) {
292 png_destroy_write_struct(&png, &info);
293 goto f_done;
294 }
295
296 png_init_io(png, f);
297 png_set_IHDR(png, info, x, y, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
298 png_set_invert_alpha(png);
299 png_write_info(png, info);
300 for (i = 0; i < y; i++) {
301 png_write_row(png, (unsigned char *)(pixbuf + (i * x)));
302 }
303 png_write_end(png, info);
304
305 png_destroy_write_struct(&png, &info);
306
307 f_done:
308 fclose(f);
309
310 return retval;
311 }
312 #endif /* HAVE_LIBPNG */
313
314 #ifdef HAVE_LIBVNCSERVER
315 /* create and return a new screen, easiest to do here because we know the screen dimensions */
316 rfbScreenInfoPtr lem1802_rfb_new(int argc, char *argv[]) {
317 rfbScreenInfoPtr s;
318 int paddedWidth = PIX_X + ( (PIX_X & 3) ? (4 - (PIX_X & 3)) : 0 );
319 int height = PIX_Y;
320 int bitsPerSample = 8;
321 int samplesPerPixel = 3;
322 int bytesPerPixel = 4;
323
324 s = rfbGetScreen(&argc, argv, paddedWidth, height, bitsPerSample, samplesPerPixel, bytesPerPixel);
325 if (s == NULL)
326 return NULL;
327
328 s->alwaysShared = TRUE;
329
330 #if 0
331 s->httpDir = "../classes";
332 #endif
333
334 TRACE("%s>> s:%p", __func__, VOIDP(s));
335 TRACE("%s>> s->kbdAddEvent:%p s->frameBuffer:%p", __func__, VOIDFP(s->kbdAddEvent), s->frameBuffer);
336
337 return s;
338 }
339
340 /* set up a new screen to see our pixels */
341 void lem1802_vnc_associate(struct dcpu16_hw *hw, rfbScreenInfoPtr s) {
342 struct lem1802_ *display = (struct lem1802_ *)hw->data;
343
344 s->desktopName = "NYA ELEKTRISKA LEM1802";
345 s->frameBuffer = (char *)display->pixbuf;
346
347 TRACE("%s>> s:%p\n", __func__, VOIDP(s));
348 }
349
350 /* notify rfb server that pixels may have changed */
351 static
352 int pixbuf_render_vnc_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
353 rfbScreenInfoPtr s = (rfbScreenInfoPtr)data;
354 int retval = 0;
355
356 (void)pixbuf;
357
358 /* derp */
359 if (s)
360 rfbMarkRectAsModified(s, 0, 0, x, y);
361
362 TRACE("%s>>", __func__);
363
364 return retval;
365 }
366 #endif /* HAVE_LIBVNCSERVER */
367
368
369 static
370 void lem1802_reset_(struct dcpu16 *vm, struct dcpu16_hw *hw) {
371 struct lem1802_ *display = (struct lem1802_ *)hw->data;
372
373 (void)vm;
374
375 display->cycle_activated = 0;
376 display->video_base = 0;
377 display->font_base = 0;
378 display->palette_base = 0;
379 display->border_color = 0;
380
381 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
382
383 display->refresh_tally_ = 0;
384 display->blink_tally_ = 0;
385 display->blink_state = 0;
386
387 display->cycle_state_ = 0;
388
389 #if DEBUG
390 vm->trace_cb_("%s>>", __func__);
391 #endif /* DEBUG */
392 }
393
394 static
395 void lem1802_cycle_(struct dcpu16 *vm, struct dcpu16_hw *hw) {
396 struct lem1802_ *display = (struct lem1802_ *)hw->data;
397
398 (void)vm;
399 /*
400 for more cycle-accuracy, could step through video memory, if set,
401 one word per clock..
402 for now just count cycles and issue a full refresh/render
403 every so often
404 */
405
406 display->blink_tally_++;
407 if (display->blink_tally_ >= display->blink_rate) {
408 display->blink_tally_ = 0;
409 display->blink_state ^= 1;
410 TRACE("%s>> blink:%u (%u cycles)", __func__, display->blink_state, display->blink_rate);
411 }
412
413 display->refresh_tally_++;
414 if (display->refresh_tally_ >= display->refresh_rate) {
415 display->refresh_tally_ = 0;
416 if (display->render)
417 TRACE("%s>> refresh", __func__);
418 lem1802_pixbuf_refresh_full_(display, vm->ram);
419 display->render(display->renderer_data, display->pixbuf, PIX_X, PIX_Y);
420 }
421
422 switch (display->cycle_state_) {
423 case CYCLE_IDLE:
424 break;
425
426 case CYCLE_COPY_TO_RAM:
427 TRACE("%s>> copy_to_ram words:%zu src:%p dst_addr:0x%04x",
428 __func__,
429 display->cycle_state_copy_words_,
430 VOIDP(display->cycle_state_copy_src_ptr_),
431 display->cycle_state_copy_dst_addr_);
432 vm->ram[display->cycle_state_copy_dst_addr_] = *display->cycle_state_copy_src_ptr_;
433 display->cycle_state_copy_dst_addr_++;
434 display->cycle_state_copy_src_ptr_++;
435 display->cycle_state_copy_words_--;
436 if (display->cycle_state_copy_words_ == 0) {
437 display->cycle_state_ = CYCLE_IDLE;
438 }
439 break;
440 }
441
442 if (display->cycles_until_active_) {
443 display->cycles_until_active_--;
444 if (display->cycles_until_active_ == 0) {
445 TRACE("%s>> display now active", __func__);
446 }
447 }
448 }
449
450 static
451 void lem1802_hwi_(struct dcpu16 *vm, struct dcpu16_hw *hw) {
452 struct lem1802_ *display = (struct lem1802_ *)hw->data;
453 DCPU16_WORD reg_a = vm->reg[DCPU16_REG_A];
454 DCPU16_WORD reg_b = vm->reg[DCPU16_REG_B];
455
456 TRACE("%s>> A:0x%04x B:0x%04x", __func__, reg_a, reg_b);
457
458 switch (reg_a) {
459 case 0: /* MEM_MAP_SCREEN */
460 if (display->cycle_activated == 0 && reg_b) {
461 display->cycle_activated = vm->cycle_;
462 display->cycles_until_active_ = power_on_cycles_();
463 }
464 display->video_base = reg_b;
465 if (reg_b == 0)
466 display->cycle_activated = 0;
467 break;
468
469 case 1: /* MEM_MAP_FONT */
470 display->font_base = reg_b;
471 break;
472
473 case 2: /* MEM_MAP_PALETTE */
474 display->palette_base = reg_b;
475 break;
476
477 case 3: /* SET_BORDER_COLOR */
478 display->border_color = reg_b & 0x000f;
479 break;
480
481 case 4: /* MEM_DUMP_FONT */
482 display->cycle_state_copy_src_ptr_ = (DCPU16_WORD *)chargen_4x8_glyphs;
483 display->cycle_state_copy_dst_addr_ = reg_b;
484 display->cycle_state_copy_words_ = 256;
485 display->cycle_state_ = CYCLE_COPY_TO_RAM;
486 dcpu16_cycle_inc(vm, 256);
487 break;
488
489 case 5: /* MEM_DUMP_PALETTE */
490 display->cycle_state_copy_src_ptr_ = palette_default_;
491 display->cycle_state_copy_dst_addr_ = reg_b;
492 display->cycle_state_copy_words_ = 16;
493 display->cycle_state_ = CYCLE_COPY_TO_RAM;
494 dcpu16_cycle_inc(vm, 16);
495 break;
496 }
497 }
498
499 static struct renderer_ {
500 char *name;
501 char *args;
502 int (*renderer)(void *, struct pixel_ *, size_t, size_t);
503 } lem1802_renderers_[] = {
504 { "pnm", "filename", pixbuf_render_pnm_ },
505 #ifdef HAVE_LIBPNG
506 { "png", "filename", pixbuf_render_png_ },
507 #endif /* HAVE_LIBPNG */
508 #ifdef HAVE_LIBVNCSERVER
509 { "vnc", "", pixbuf_render_vnc_ },
510 #endif /* HAVE_LIBVNCSERVER */
511 { "none", "", NULL },
512 { NULL, NULL, NULL }
513 };
514
515 int lem1802_renderer_set(struct dcpu16_hw *hw, const char *renderer, void *data) {
516 struct renderer_ *r;
517
518 for (r = lem1802_renderers_; r->renderer; r++) {
519 if (strcmp(renderer, r->name) == 0) {
520 ((struct lem1802_ *)(hw->data))->render = r->renderer;
521 ((struct lem1802_ *)(hw->data))->renderer_data = data;
522 TRACE("%s>> renderer set to %s", __func__, renderer);
523 return 0;
524 }
525 }
526
527 return -1;
528 }
529
530 char *lem1802_renderers_iter(void **iterp, char **name, char **args) {
531 struct renderer_ **r = (struct renderer_ **)iterp;
532
533 if (*r == NULL)
534 *r = lem1802_renderers_;
535 else
536 (*r)++;
537
538 if ((*r)->name == NULL) {
539 *r = NULL;
540 return NULL;
541 }
542
543 *name = (*r)->name;
544 *args = (*r)->args;
545
546 return (*r)->name;
547 }
548
549 int lem1802_data_init_(struct dcpu16_hw *hw, void *data) {
550 (void)data;
551
552 hw->data = calloc(1, sizeof(struct lem1802_));
553 if (hw->data == NULL) {
554 hw->vm->warn_cb_("%s():%s", "calloc", strerror(errno));
555 return -1;
556 }
557
558 ((struct lem1802_ *)(hw->data))->pixbuf = calloc(PIX_X * PIX_Y, sizeof *((struct lem1802_ *)(hw->data))->pixbuf);
559 if (((struct lem1802_ *)(hw->data))->pixbuf == NULL) {
560 hw->vm->warn_cb_("%s():%s", "calloc", strerror(errno));
561 free(hw->data);
562 hw->data = NULL;
563 return -1;
564 }
565
566 ((struct lem1802_ *)(hw->data))->refresh_rate = 1666;
567 ((struct lem1802_ *)(hw->data))->blink_rate = 75000;
568
569 return 0;
570 }
571
572 void lem1802_data_free_(struct dcpu16_hw *hw) {
573 if (hw) {
574 if (hw->data) {
575 /* FIXME: free renderer data */
576 hw->vm->warn_cb_("FIXME");
577
578 if (((struct lem1802_ *)(hw->data))->pixbuf) {
579 free(((struct lem1802_ *)(hw->data))->pixbuf);
580 ((struct lem1802_ *)(hw->data))->pixbuf = NULL;
581 }
582
583 free(hw->data);
584 hw->data = NULL;
585 }
586 }
587 }
588
589
590 static struct dcpu16_hw hw_ = {
591 .name_ = "LEM1802 - Low Energy Monitor",
592 .id_l = 0xf615,
593 .id_h = 0x7349,
594 .ver = 0x1802,
595 .mfg_l = 0x8b36,
596 .mfg_h = 0x1c6c,
597 .hwi = lem1802_hwi_,
598 .cycle = lem1802_cycle_,
599 .reset = lem1802_reset_,
600 .data = (struct lem1802_ *)NULL
601 };
602
603 struct dcpu16_hw_module dcpu16_hw_module_lem1802 = {
604 .template = &hw_,
605 .data_init = lem1802_data_init_,
606 .data_free = lem1802_data_free_,
607 };
608