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