/* * ubb-vga.c - Output video on UBB with more or less VGA timing * * Written 2011 by Werner Almesberger * Copyright 2011 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ /* * WARNING: this program does very nasty things to the Ben and it doesn't * like company. In particular, it resents: * * - the MMC driver - disable it with * echo jz4740-mmc.0 >/sys/bus/platform/drivers/jz4740-mmc/unbind * - the AT86RF230/1 kernel driver - use a kernel that doesn't have it * - anything that accesses the screen - kill GUI, X server, etc. * - the screen blanker - either disable it or make sure the screen stays * dark, e.g., with * echo 1 >/sys/devices/platform/jz4740-fb/graphics/fb0/blank * - probably a fair number of other daemons and things as well - best to * kill them all. */ #include #include #include #include #include #include #include #include "regs4740.h" #include "ubb-vga.h" #define REG_BASE_PTR base static volatile void *base; static int bad; /* ----- I/O pin assignment ------------------------------------------------ */ #define DAT0 (1 << 10) #define DAT1 (1 << 11) #define DAT2 (1 << 12) #define DAT3 (1 << 13) #define CMD (1 << 8) #define CLK (1 << 9) #define R DAT3 #define G DAT0 #define B DAT1 #define Y DAT2 #define HSYNC CMD #define VSYNC CLK /* ----- Ben hardware ------------------------------------------------------ */ #define TIMER 7 #define DMA 0 static uint32_t old_icmr; static uint32_t old_clkgr; static void disable_interrupts(void) { /* * @@@ Race condition alert ! If we get interrupted/preempted between * reading ICMR and masking all interrupts, and the code that runs * between these two operations changes ICMR, then we may set an * incorrect mask when restoring interrupts, which may hang the system. */ old_icmr = ICMR; ICMSR = 0xffffffff; } static void enable_interrupts(void) { ICMCR = ~old_icmr; } /* * @@@ Disabling the LCD clock will hang operations that depend on the LCD * subsystem to advance. This includes the screen saver. */ static void disable_lcd(void) { old_clkgr = CLKGR; CLKGR = old_clkgr | 1 << 10; } static void enable_lcd(void) { CLKGR = old_clkgr; } static void get_timer(void) { TSCR = 1 << TIMER; /* enable clock */ TCSR(TIMER) = 1; /* count at PCLK/1 */ TDFR(TIMER) = 0xffff; /* count to 0xffff */ TESR = 1 << TIMER; } static void release_timer(void) { TECR = 1 << TIMER; TSSR = 1 << TIMER; } void *map(off_t addr, size_t size) { int fd; void *mem; fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("/dev/mem"); exit(1); } mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr); if (mem == MAP_FAILED) { perror("mmap"); exit(1); } return mem; } static void ben_setup(void) { base = map(SOC_BASE, REG_WINDOW); /* * Ironically, switching the LCD clock on and off many times only * increases the risk of a hang. Therefore, we leave stop it during * all the measurements and only enable it again at the end. */ disable_lcd(); get_timer(); } static void cleanup(void) { release_timer(); enable_lcd(); } /* ----- Delay logic ------------------------------------------------------- */ #define US(us) ((uint16_t) ((us)*112)) static void until(uint16_t cycles) { while ((TCNT(TIMER) & 0xffff) < cycles); } /* ----- Frame buffer output ----------------------------------------------- */ static const struct mode { const char *name; int xres, yres; int line_words; /* xres/8 */ int clkdiv; /* pixel clock = 336 MHz/(clkdiv+1) */ int line_cycles; /* 31.77 us for official VGA */ int hsync_end; /* 0.79+3.77 us for official VGA */ } mode_db[] = { { "640x480", 640, 480, 640/8, 11, US(29.7), US(0.79+3.77-0.3) }, { "800x600", 800, 600, 800/8, 8, US(28.7), US(2.0+3.3+0.3) }, /* the next one may work after adjusting the timing in "frame" */ { "800x600", 800, 600, 800/8, 8, US(28.2), US(2.0+3.3+0.3-0.3) }, /* the 1024x768 below is not great but has good parameter tolerance */ { "1024x768", 1024, 768, 1024/8, 8, US(36.0), US(2.0+3.3) }, /* illustrate underruns */ { "1024x768ur", 1024, 768, 1024/8, 7, US(33.5), US(0.4+2.1+0.5) }, { NULL } }, *mode = mode_db; /* * Adjustment value tests with the XEN-1510 (640x480): * * Adjustment Tries Good Jam FIFO jitter * Quick load * -0.0 10 3 7 0 n * -0.1 10 5 5 0 n * -0.2 10 6 4 0 n * -0.3 10 7 3 0 n * 10 5 5 0 y * -0.4 10 1 0 9 n * 10 5 0 5 n repeat * 10 5 0 5 y * -0.5 10 3 0 7 n * 10 7 0 3 y * -1.0 5 0 5 0 * * Good = image is stable * Jam = does not detect the signal properly, loss of HSYNC, artefacts, * or no image at all * FIFO jitter = some lines get shifted by a "digital" amount */ static void setup(void) { mlockall(MCL_CURRENT | MCL_FUTURE); ben_setup(); PDFUNS = R | G | B | Y; PDFUNC = VSYNC | HSYNC; PDDIRS = VSYNC | HSYNC | R | G | B | Y; PDDATS = VSYNC | HSYNC; PDDATC = R | G | B | Y; MSCCDR = mode->clkdiv; /* set the MSC clock to 336 MHz / 12 = 28 MHz */ CLKGR &= ~(1 << 7); /* enable MSC clock */ MSC_CLKRT = 0; /* bus clock = MSC clock / 1 */ } static void setup_noirq(void) { DMAC = 1; DCS(DMA) = (1 << 3) | (1 << 2); DCS(DMA) = 0; DCM(DMA) = (1 << 23) | /* source address increment */ (4 << 8); /* transfer size is 32 bytes */ DRT(DMA) = 26; /* MSC transmit-fifo-empty transfer request */ } static void cleanup_noirq(void) { DMAC = 0; DCS(DMA) = (1 << 3) | (1 << 2); DCS(DMA) = 0; } static void line(unsigned long line) { /* Back porch */ MSC_STRPCL = 1 << 3; /* reset the MSC */ // DCS(DMA) = (1 << 31) | (1 << 3) | (1 << 2); DCS(DMA) = 1 << 31; DSA(DMA) = line; DTA(DMA) = REG_PADDR(MSC_TXFIFO); /* MUST set this each time */ DTC(DMA) = mode->xres >> 6; until(US(0.79)); /* HSYNC */ PDDATC = HSYNC; MSC_STRPCL = 2; /* start MMC clock output */ MSC_RESTO = 0xffff; MSC_CMDAT = (2 << 9) | /* 4 bit bus */ (1 << 8) | /* DMA */ (1 << 4) | /* write */ (1 << 3) | /* with data transfer */ 1; /* R1 response */ MSC_STRPCL = 4; /* START_OP */ DCS(DMA) = (1 << 31) | /* no descriptor */ 1; until(mode->hsync_end); // MSC_TXFIFO = 0xffffffff; /* Front porch */ PDFUNS = CMD; PDDATS = HSYNC; PDFUNC = CMD; until(mode->line_cycles); if (MSC_STAT & 3) bad++; } static void hdelay(int cycles) { while (cycles--) { TCNT(TIMER) = 0; PDDATC = HSYNC; until(US(3.77)); PDDATS = HSYNC; until(mode->line_cycles); } } static void frame(const unsigned long *f) { const unsigned long *p; /* VSYNC */ PDDATC = VSYNC; hdelay(2); PDDATS = VSYNC; /* Front porch */ hdelay(31); /* * The horizontal back porch of the previous line is handled inside * "line", so we have to wait for less than a full line here. */ TCNT(TIMER) = 0; PDDATC = HSYNC; until(US(3.77)); PDDATS = HSYNC; until(mode->line_cycles-US(0.79)); /* * Note: resetting the timer just before calling "line" isn't enough. * We have t reset it before the loop and right after returning from * "line". */ TCNT(TIMER) = 0; for (p = f; p != f+mode->yres; p++) { line(*p); TCNT(TIMER) = 0; } /* Back porch */ hdelay(14); } /* ----- Command-line parsing and main loop -------------------------------- */ static void session(void (*gen)(void **fb, int xres, int yres), int frames) { void **f_virt; const unsigned long *f_phys; int i; ccube_init(); f_virt = calloc_phys_vec(mode->yres, mode->xres/2); gen(f_virt, mode->xres, mode->yres); f_phys = xlat_virt(f_virt, mode->yres); disable_interrupts(); setup_noirq(); for (i = 0; i != frames; i++) { frame(f_phys); if (DTC(DMA)) { fprintf(stderr, "DMA locked up. Need hardware reset.\n"); break; } } cleanup_noirq(); enable_interrupts(); } static void usage(const char *name) { fprintf(stderr, "usage: %s [-t] [-r resolution] frames [file]\n\n" " frames number of frames to display\n" " file PPM file\n\n" " -m mode select the display mode, default \"%s\"\n" " -t generate a test image\n" , name, mode_db[0].name); exit(1); } int main(int argc, char *const *argv) { void (*gen)(void **fb, int xres, int yres) = grabfb; int frames; int c; while ((c = getopt(argc, argv, "m:t")) != EOF) switch (c) { case 'm': for (mode = mode_db; mode->name; mode++) if (!strcmp(mode->name, optarg)) break; if (!mode->name) { fprintf(stderr, "no mode \"%s\"\n", optarg); exit(1); } break; case 't': gen = tstimg; break; default: usage(*argv); } switch (argc-optind) { case 2: img_name = argv[optind+1]; gen = ppmimg; /* fall through */ case 1: frames = atoi(argv[optind]); break; default: usage(*argv); } setup(); session(gen, frames); cleanup(); if (bad) printf("%d timeout%s\n", bad, bad == 1 ? "" : "s"); return 0; }