/* * 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 static uint8_t thres = 63; /* ----- 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 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; static uint32_t old_clkgr; static volatile uint32_t *pdpin, *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 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; } static 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); icmr = base+0x1004; icmsr = base+0x1008; icmcr = base+0x100c; clkgr = base+0x20; pdpin = base+0x10300; 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; /* * 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(); } /* ----- Prefetch and delay logic ------------------------------------------ */ #define BURST 32 /* bytes */ static inline void prefetch(const uint8_t *prefetch, int words) { volatile const uint8_t *p = prefetch; while (p != prefetch+words) { (void) *p; p += BURST; } } #define US(us) ((uint16_t) ((us)*112)) static void until(uint16_t cycles) { while ((*tcnt & 0xffff) < cycles); } /* ----- Frame buffer output ----------------------------------------------- */ static int line_pairs = 160; /* set/clear pairs */ static int line_cycles = US(36); /* nominally 31.77 us, but we're too slow */ void setup(void) { mlockall(MCL_CURRENT | MCL_FUTURE); ben_setup(); *pdfunc = R | G | B | HSYNC | VSYNC; *pddirs = R | G | B | HSYNC | VSYNC; } static void line(const uint8_t *line, const uint8_t *fetch) { const uint8_t *p = line; /* HSYNC */ *tcnt = 0; *pddatc = HSYNC; prefetch(fetch, line_pairs); until(US(3.77)); *pddats = HSYNC; /* Front porch */ until(US(3.77+1.79)); while (p != line+2*line_pairs) { *pddats = *p++ << 8; *pddatc = *p++ << 8; } /* Back porch */ 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 uint8_t *f) { const uint8_t *p; /* VSYNC */ *pddatc = VSYNC; hdelay(2); *pddats = VSYNC; /* Front porch */ *tcnt = 0; *pddatc = HSYNC; until(US(3.77)); *pddats = HSYNC; prefetch(f, line_pairs); until(line_cycles); hdelay(31); for (p = f; p != f+240*2*line_pairs; p += 2*line_pairs) { line(p, p+line_pairs); line(p, p+2*line_pairs); } /* Back porch */ hdelay(14); } /* ----- Frame buffer image generation ------------------------------------- */ static uint32_t pick(int set, int bit, uint32_t val) { return set == bit ? val >> 8 : 0; } static uint32_t pattern(int set, int r, int g, int b) { return pick(set, r, R) | pick(set, g, G) | pick(set, b, B); } static void tricolor(uint32_t *f) { int pairs = 2*line_pairs*240; int i; for (i = 0; i != pairs/3; i++) { f[i & ~1] = R; f[i | 1] = G | B; } for (; i != pairs*2/3; i++) { f[i & ~1] = G; f[i | 1] = R | B; } for (; i != pairs; i++) { f[i & ~1] = B; f[i | 1] = R | G; } } static void grid(uint8_t *f) { static uint32_t col[8] = { R | G | B, R, R | G, G, G | B, B, R | B, R | G | B, }; int i, x, y; for (i = 0; i != 8; i++) { x = i*line_pairs/4+line_pairs/8; for (y = 0; y != 240; y++) { uint8_t *p = f+y*2*line_pairs+x; p[0] = p[1] = col[i] >> 8; } } } static void grab(uint8_t *f, int single) { uint32_t *fb = map(0x01d00000, 4*320*240); int x, y; uint32_t pix; uint8_t r, g, b; for (y = 0; y != 240; y++) for (x = 0; x != 320; x++) { pix = *fb++; r = pix >> 16; g = pix >> 8; b = pix; if (single) *f++ = pattern(!(x & 1), r >= thres, g >= thres, b >= thres); else { *f++ = pattern(1, r >= thres, g >= thres, b >= thres); *f++ = pattern(0, r >= thres, g >= thres, b >= thres); } } } /* ----- Command-line parsing and main loop -------------------------------- */ static void session(int frames, int single) { uint8_t f[2*line_pairs*(240+1)]; int i; memset(f, 0, sizeof(f)); grab(f, single); // grid(f); disable_interrupts(); for (i = 0; i != frames; i++) frame(f); enable_interrupts(); } static void usage(const char *name) { fprintf(stderr, "usage: %s frames -d [threshold]\n\n" " frames number of frames to display\n" " threshold channel on/off threshold\n\n" " -d double the number of set/clear pairs\n" , name); exit(1); } int main(int argc, char *const *argv) { int frames; int single = 1; int c; while ((c = getopt(argc, argv, "d")) != EOF) switch (c) { case 'd': single = 0; line_pairs = 320; line_cycles = US(36+26); break; default: usage(*argv); } switch (argc-optind) { case 2: thres = atoi(argv[optind+1]); /* fall through */ case 1: frames = atoi(argv[optind]); break; default: usage(*argv); } setup(); session(frames, single); cleanup(); return 0; }