/* * 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 "ubb-vga.h" 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 PAGE_SIZE 4096 #define SOC_BASE 0x10000000 static volatile uint32_t *icmr, *icmsr, *icmcr; static uint32_t old_icmr; static volatile uint32_t *clkgr, *msccdr; static uint32_t old_clkgr; static volatile uint32_t *pddats, *pddatc; static volatile uint32_t *pddirs, *pddirc; static volatile uint32_t *pdfuns, *pdfunc; static volatile uint32_t *tssr, *tscr; static volatile uint32_t *tesr, *tecr; static volatile uint32_t *tcsr, *tdfr, *tcnt; static volatile uint32_t *msc_strpcl, *msc_stat, *msc_clkrt; static volatile uint32_t *msc_cmdat, *msc_resto, *msc_blklen, *msc_nob; static volatile uint32_t *msc_cmd, *msc_arg, *msc_txfifo; 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 = 1; /* count at PCLK/1 */ *tdfr = 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) { volatile void *base; base = map(SOC_BASE, PAGE_SIZE*3*16); clkgr = base+0x20; msccdr = base+0x68; icmr = base+0x1004; icmsr = base+0x1008; icmcr = base+0x100c; pddats = base+0x10314; pddatc = base+0x10318; pdfuns = base+0x10344; pdfunc = base+0x10348; pddirs = base+0x10364; pddirc = base+0x10368; tssr = base+0x202c; tscr = base+0x203c; tesr = base+0x2014; tecr = base+0x2018; tcsr = base+0x204c+0x10*TIMER; tdfr = base+0x2040+0x10*TIMER; tcnt = base+0x2048+0x10*TIMER; msc_strpcl = base+0x21000; msc_stat = base+0x21004; msc_clkrt = base+0x21008; msc_cmdat = base+0x2100c; msc_resto = base+0x21010; msc_blklen = base+0x21018; msc_nob = base+0x2101c; msc_cmd = base+0x2102c; msc_arg = base+0x21030; msc_txfifo = base+0x2103c; /* * 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 & 0xffff) < cycles); } /* ----- Frame buffer output ----------------------------------------------- */ static int line_words = 640/8; //static int line_cycles = US(36); /* nominally 31.77 us, but we're too slow */ //static int line_cycles = US(32); /* nominally 31.77 us, but we're too slow */ static int line_cycles = US(29.6); /* nominally 31.77 us, but we're too fast */ /* * Note: 29.5 is already too short. Tricky timing. */ 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 = 20; /* set the MSC clock to 336 MHz / 21 = 16 MHz */ *msccdr = 11; /* 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 line(const uint32_t *line, volatile const uint32_t *prefetch) { const uint32_t *p = line; /* Back porch */ *tcnt = 0; *msc_strpcl = 1 << 3; /* reset the MSC */ // while (*msc_stat & (1 << 15)); *msc_txfifo = *p++; until(US(0.79)); /* HSYNC */ *pddatc = HSYNC; *msc_strpcl = 2; /* start MMC clock output */ *msc_cmdat = (1 << 10) | /* 4 bit bus */ (1 << 4) | /* write */ (1 << 3) | /* with data transfer */ 1; /* R1 response */ *msc_strpcl = 4; /* START_OP */ until(US(0.79+3.77-0.3)); /* * Adjustment value tests with the XEN-1510: * * 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 */ /* Front porch */ *pdfuns = CMD; *pddats = HSYNC; *pdfunc = CMD; /* * We don't wait for the end of the front porch because the beginning * of pixel data is determined by the MSC. Instead, we make good use * of the delay to shovel bits into the MSC's FIFO. */ #if 0 /* quick load */ *msc_txfifo = *p++; *msc_txfifo = *p++; #endif while (p != line+line_words) { uint8_t st; do { st = *msc_stat; if (st & 3) { bad++; goto fail; } } while (st & (1 << 7)); *msc_txfifo = *p++; } fail: (void) *prefetch; until(line_cycles); } static void hdelay(int cycles) { while (cycles--) { *tcnt = 0; *pddatc = HSYNC; until(US(3.77)); *pddats = HSYNC; until(line_cycles); } } static void frame(const uint32_t *f) { const uint32_t *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 = 0; *pddatc = HSYNC; until(US(3.77)); *pddats = HSYNC; until(line_cycles-US(0.79)); for (p = f; p != f+480*line_words; p += line_words) line(p, p+line_words); /* Back porch */ hdelay(14); } /* ----- Command-line parsing and main loop -------------------------------- */ static void session(void (*gen)(void *fb, int xres, int yres), int frames) { uint32_t f[2*240*(line_words+1)]; int i; memset(f, 0, sizeof(f)); gen(f, 640, 480); disable_interrupts(); for (i = 0; i != frames; i++) frame(f); enable_interrupts(); } static void usage(const char *name) { fprintf(stderr, "usage: %s [-l threshold] [-t] frames [file]\n\n" " frames number of frames to display\n" " file PPM file\n\n" " -l threshold channel on/off threshold\n" " -t generate a test image\n" , 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, "l:t")) != EOF) switch (c) { case 'l': thres = atoi(optarg); 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; }