Root/ubb-vga/ubb-vga.c

1/*
2 * ubb-vga.c - Output video on UBB with more or less VGA timing
3 *
4 * Written 2011 by Werner Almesberger
5 * Copyright 2011 Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13/*
14 * WARNING: this program does very nasty things to the Ben and it doesn't
15 * like company. In particular, it resents:
16 *
17 * - the MMC driver - disable it with
18 * echo jz4740-mmc.0 >/sys/bus/platform/drivers/jz4740-mmc/unbind
19 * - the AT86RF230/1 kernel driver - use a kernel that doesn't have it
20 * - anything that accesses the screen - kill GUI, X server, etc.
21 * - the screen blanker - either disable it or make sure the screen stays
22 * dark, e.g., with
23 * echo 1 >/sys/devices/platform/jz4740-fb/graphics/fb0/blank
24 * - probably a fair number of other daemons and things as well - best to
25 * kill them all.
26 */
27
28
29#include <stdint.h>
30#include <stdlib.h>
31#include <stdio.h>
32#include <unistd.h>
33#include <string.h>
34#include <fcntl.h>
35#include <sys/mman.h>
36
37#include "regs4740.h"
38#include "ubb-vga.h"
39
40
41#define REG_BASE_PTR base
42
43static volatile void *base;
44
45static int bad;
46static int keep_lcd = 0;
47
48
49#define US(us) ((uint16_t) ((us)*112))
50
51
52static const struct mode mode_db[] = {
53/* name xres yres clkdiv vfront hsync hback htotal */
54/* vsync vback */
55{ "640x480", 640, 480, 12, 2, 32, 14, US(2.80), US(1.30), US(32.1) },
56{ "640x480/58", 640, 480, 12, 2, 10, 33, US(3.81), US(1.91), US(32.7) },
57{ "640x480/61", 640, 480, 11, 2, 9, 29, US(2.50), US(4.06), US(31.5) },
58{ "640x480/70", 640, 480, 9, 2, 8, 29, US(1.90), US(2.06), US(24.8) },
59
60{ "800x600/54", 800, 600, 8, 2, 32, 14, US(4.81), US(0.79), US(28.8) },
61{ "800x600/56", 800, 600, 8, 2, 1, 22, US(2.00), US(3.56), US(28.5) },
62{ "800x600/72", 800, 600, 5, 3, 1, 27, US(2.14), US(2.70), US(22.0) },
63
64/* the 1024x768 below is not great but has good parameter tolerance */
65{ "1024x768", 1024, 768, 8, 2, 32, 14, US(4.51), US(0.79), US(36.0) },
66/* illustrate underruns (without DMA) */
67{ "1024x768ur", 1024, 768, 7, 2, 32, 14, US(2.21), US(0.79), US(33.5) },
68{ "1024x768/53",1024, 768, 5, 2, 32, 14, US(1.31), US(0.79), US(23.1) },
69{ "1024x768/50",1024, 768, 5, 6, 3, 29, US(2.10), US(2.46), US(24.5) },
70{ NULL }
71};
72
73/*
74 * 640x480: loosely based on the timing from "Build a VGA Monitor
75 * Controller" by Enoch Hwang
76 *
77 *
78 * 640x480/58: http://tinyvga.com/vga-timing/640x480@60Hz
79 *
80 * H: 3.81+0.64+25.42+1.91 us = 31.78 us
81 *
82 * Pixel clock is 25.175 MHz. We have 25.85 MHz, thus:
83 * 25.42/25.75*25.175 = 24.85
84 *
85 * htotal should be 31.77+(24.85-25.175) = 31.445, but we actually need more.
86 * Seems that our hfront is about 1.7 us.
87 *
88 * Observation: bad FIFO jitter.
89 *
90 *
91 * 640x480/70: http://tinyvga.com/vga-timing/640x480@73Hz
92 *
93 * That's a pretty ugly adaptation. The DMA really seems to dislike the 73 Hz
94 * parameters.
95 *
96 *
97 * 800x600/54: just a lucky combination
98 *
99 *
100 * 800x600/56: http://tinyvga.com/vga-timing/800x600@56Hz
101 *
102 * Note that we have the sync pulses upside-down.
103 *
104 *
105 * 800x600/7: yet another lucky combination
106 *
107 *
108 * 1024x768/50: loosely based on http://tinyvga.com/vga-timing/1024x768@60Hz
109 */
110
111
112const struct mode *mode = mode_db;
113
114static uint32_t clkrt = 0;
115
116
117/* ----- I/O pin assignment ------------------------------------------------ */
118
119
120#define DAT0 (1 << 10)
121#define DAT1 (1 << 11)
122#define DAT2 (1 << 12)
123#define DAT3 (1 << 13)
124#define CMD (1 << 8)
125#define CLK (1 << 9)
126
127#define R DAT3
128#define G DAT0
129#define B DAT1
130#define Y DAT2
131#define HSYNC CMD
132#define VSYNC CLK
133
134
135/* ----- Ben hardware ------------------------------------------------------ */
136
137
138#define TIMER 7
139#define DMA 0
140#define KEY_MASK 0x5f70000
141
142
143static uint32_t old_icmr;
144static uint32_t old_clkgr;
145static uint32_t old_lcdctrl;
146
147
148static void disable_interrupts(void)
149{
150    /*
151     * @@@ Race condition alert ! If we get interrupted/preempted between
152     * reading ICMR and masking all interrupts, and the code that runs
153     * between these two operations changes ICMR, then we may set an
154     * incorrect mask when restoring interrupts, which may hang the system.
155     */
156
157    old_icmr = ICMR;
158    ICMSR = 0xffffffff;
159}
160
161
162static void enable_interrupts(void)
163{
164    ICMCR = ~old_icmr;
165}
166
167
168/*
169 * @@@ Disabling the LCD clock will hang operations that depend on the LCD
170 * subsystem to advance. This includes the screen saver.
171 */
172
173static void disable_lcd(void)
174{
175    old_clkgr = CLKGR;
176    CLKGR = old_clkgr | 1 << 10;
177}
178
179
180static void enable_lcd(void)
181{
182    CLKGR = old_clkgr;
183}
184
185
186static void tame_lcd(void)
187{
188    old_lcdctrl = LCDCTRL;
189    /* LCDCTRL.BST = 0, for 4 word burst, the shortest available */
190    LCDCTRL = old_lcdctrl & 0xfffffff;
191}
192
193
194static void restore_lcd(void)
195{
196    LCDCTRL = old_lcdctrl;
197}
198
199
200static void get_timer(void)
201{
202    TSCR = 1 << TIMER; /* enable clock */
203    TCSR(TIMER) = 1; /* count at PCLK/1 */
204    TDFR(TIMER) = 0xffff; /* count to 0xffff */
205    TESR = 1 << TIMER;
206}
207
208
209static void release_timer(void)
210{
211    TECR = 1 << TIMER;
212    TSSR = 1 << TIMER;
213}
214
215
216void *map(off_t addr, size_t size)
217{
218    int fd;
219    void *mem;
220
221    fd = open("/dev/mem", O_RDWR | O_SYNC);
222    if (fd < 0) {
223        perror("/dev/mem");
224        exit(1);
225    }
226    mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr);
227    if (mem == MAP_FAILED) {
228        perror("mmap");
229        exit(1);
230    }
231
232    return mem;
233}
234
235
236static void ben_setup(void)
237{
238    base = map(SOC_BASE, REG_WINDOW);
239
240}
241
242
243/* ----- Delay logic ------------------------------------------------------- */
244
245
246static void delay(uint16_t cycles)
247{
248    static uint16_t next = 0;
249    uint16_t t;
250
251    next += cycles;
252    TDHR(TIMER) = next;
253    TFCR = 1 << (TIMER+16);
254    while (!(TFR & (1 << (TIMER+16))));
255    t = next+24-TCNT(TIMER);
256    t >>= 1;
257    while (t--)
258        asm("");
259    
260}
261
262
263/* ----- Frame buffer output ----------------------------------------------- */
264
265
266static void setup(void)
267{
268    mlockall(MCL_CURRENT | MCL_FUTURE);
269    ben_setup();
270
271    PDFUNS = R | G | B | Y;
272    PDFUNC = VSYNC | HSYNC;
273    PDDIRS = VSYNC | HSYNC | R | G | B | Y;
274    PDDATS = VSYNC | HSYNC;
275    PDDATC = R | G | B | Y;
276
277    MSCCDR = mode->clkdiv; /* set the MSC clock to 336 MHz / 12 = 28 MHz */
278    CLKGR &= ~(1 << 7); /* enable MSC clock */
279    while (MSCCDR & 1) {
280        MSCCDR >>= 1;
281        clkrt++;
282    }
283}
284
285
286static void setup_noirq(void)
287{
288    if (keep_lcd)
289        tame_lcd();
290    else
291        disable_lcd();
292    get_timer();
293
294    DMAC = 1;
295    DCS(DMA) = (1 << 3) | (1 << 2);
296    DCS(DMA) = 0;
297
298    DCM(DMA) =
299        (1 << 23) | /* source address increment */
300        (4 << 8); /* transfer size is 32 bytes */
301    DRT(DMA) = 26; /* MSC transmit-fifo-empty transfer request */
302}
303
304
305static void cleanup_noirq(void)
306{
307    DMAC = 0;
308    DCS(DMA) = (1 << 3) | (1 << 2);
309    DCS(DMA) = 0;
310
311    release_timer();
312    if (keep_lcd)
313        restore_lcd();
314    else
315        enable_lcd();
316}
317
318
319static void line(unsigned long line)
320{
321    /* Back porch */
322
323    MSC_STRPCL = 1 << 3; /* reset the MSC */
324
325// DCS(DMA) = (1 << 31) | (1 << 3) | (1 << 2);
326    DCS(DMA) = 1 << 31;
327    DSA(DMA) = line;
328    DTA(DMA) = REG_PADDR(MSC_TXFIFO); /* MUST set this each time */
329    DTC(DMA) = (mode->xres+63) >> 6;
330
331    delay(mode->hback_cycles);
332
333    /* HSYNC */
334
335    PDDATC = HSYNC;
336    MSC_CLKRT = clkrt; /* bus clock = MSC clock / n */
337    MSC_STRPCL = 2; /* start MMC clock output */
338    MSC_RESTO = 0xffff;
339
340    MSC_CMDAT =
341        (2 << 9) | /* 4 bit bus */
342        (1 << 8) | /* DMA */
343        (1 << 4) | /* write */
344        (1 << 3) | /* with data transfer */
345        1; /* R1 response */
346
347    MSC_STRPCL = 4; /* START_OP */
348
349    DCS(DMA) =
350        (1 << 31) | /* no descriptor */
351        1;
352
353    delay(mode->hsync_cycles);
354
355// MSC_TXFIFO = 0xffffffff;
356
357    /* Front porch */
358
359    PDFUNS = CMD;
360    PDDATS = HSYNC;
361    PDFUNC = CMD;
362
363    delay(mode->line_cycles-mode->hback_cycles-mode->hsync_cycles);
364    MSC_TXFIFO = 0;
365    if (MSC_STAT & 3)
366        bad++;
367}
368
369
370static void hdelay(int cycles)
371{
372    while (cycles--) {
373        PDDATC = HSYNC;
374        delay(mode->hsync_cycles);
375        PDDATS = HSYNC;
376        delay(mode->line_cycles-mode->hsync_cycles);
377    }
378}
379
380
381static void frame(const unsigned long *f)
382{
383    const unsigned long *p;
384
385    /* VSYNC */
386    PDDATC = VSYNC;
387    hdelay(mode->vsync_lines);
388    PDDATS = VSYNC;
389
390    /* Front porch */
391
392    hdelay(mode->vfront_lines-1);
393
394    /*
395     * The horizontal back porch of the previous line is handled inside
396     * "line", so we have to wait for less than a full line here.
397     */
398    PDDATC = HSYNC;
399    delay(mode->hsync_cycles);
400    PDDATS = HSYNC;
401    delay(mode->line_cycles-mode->hback_cycles-mode->hsync_cycles);
402
403    for (p = f; p != f+mode->yres; p++)
404        line(*p);
405
406    delay(mode->hback_cycles);
407
408    /* Back porch */
409    hdelay(mode->vback_lines);
410}
411
412
413/* ----- Command-line parsing and main loop -------------------------------- */
414
415
416static void session(void (*gen)(void **fb, int xres, int yres), int frames)
417{
418    void **f_virt;
419    const unsigned long *f_phys;
420    int i, bytes;
421
422    ccube_init();
423    bytes = mode->xres/2;
424    bytes = (bytes+31) & ~31;
425    f_virt = calloc_phys_vec(mode->yres, bytes);
426    gen(f_virt, mode->xres, mode->yres);
427    f_phys = xlat_virt(f_virt, mode->yres);
428
429    disable_interrupts();
430
431    setup_noirq();
432
433    TCNT(TIMER) = 0;
434    for (i = 0; !frames || i != frames; i++) {
435        frame(f_phys);
436        if (DTC(DMA)) {
437            fprintf(stderr,
438                "DMA locked up. Need hardware reset.\n");
439            break;
440        }
441        if (!frames && (PDPIN & KEY_MASK) != KEY_MASK)
442            break;
443    }
444
445    cleanup_noirq();
446
447    enable_interrupts();
448}
449
450
451static void list_modes(void)
452{
453    const struct mode *m;
454
455    for (m = mode_db; m->name; m++) {
456        printf("\"%s\", %dx%d:\n", m->name, m->xres, m->yres);
457        printf("\t336/%d = %5.2f MHz\n",
458            m->clkdiv+1, 336.0/(m->clkdiv+1));
459        printf("\tH: %4.2f+...+%4.2f = %5.2f us\n",
460            CYCLES(m->hsync_cycles), CYCLES(m->hback_cycles),
461            CYCLES(m->line_cycles));
462        printf("\tV: %d+%d+%d+%d lines, %5.2f Hz\n",
463            m->vsync_lines, m->vfront_lines, m->yres, m->vback_lines,
464            112000000.0/m->line_cycles/
465            (m->vsync_lines+m->vfront_lines+m->yres+m->vback_lines));
466    }
467}
468
469
470static void usage(const char *name)
471{
472    fprintf(stderr,
473"usage: %s [-2] [-t] [-m resolution] [frames [file]]\n"
474" %s -l\n\n"
475" frames number of frames to display\n"
476" file PPM file\n\n"
477" -2 keep on refreshing the LCD display (experimental)\n"
478" -l list available modes\n"
479" -m mode select the display mode, default \"%s\"\n"
480" -t generate a test image\n"
481    , name, name, mode_db[0].name);
482    exit(1);
483}
484
485
486int main(int argc, char *const *argv)
487{
488    void (*gen)(void **fb, int xres, int yres) = grabfb;
489    int frames = 0;
490    int c;
491
492    while ((c = getopt(argc, argv, "2lm:t")) != EOF)
493        switch (c) {
494        case '2':
495            keep_lcd = 1;
496            break;
497        case 'l':
498            list_modes();
499            exit(0);
500        case 'm':
501            for (mode = mode_db; mode->name; mode++)
502                if (!strcmp(mode->name, optarg))
503                    break;
504            if (!mode->name) {
505                fprintf(stderr, "no mode \"%s\"\n", optarg);
506                exit(1);
507            }
508            break;
509        case 't':
510            gen = tstimg;
511            break;
512        default:
513            usage(*argv);
514        }
515
516    switch (argc-optind) {
517    case 2:
518        img_name = argv[optind+1];
519        gen = ppmimg;
520        /* fall through */
521    case 1:
522        frames = atoi(argv[optind]);
523        break;
524    case 0:
525        break;
526    default:
527        usage(*argv);
528    }
529
530    setup();
531    session(gen, frames);
532
533#if 0
534    printf("clkdiv: %d, /%d /%d\n", mode->clkdiv,
535        (MSCCDR & 15)+1, 1 << (MSC_CLKRT & 7));
536#endif
537    if (bad)
538        printf("%d timeout%s\n", bad, bad == 1 ? "" : "s");
539    return 0;
540}
541

Archive Download this file

Branches:
master



interactive