/* * 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; 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; } 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); 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)); /* HSYNC */ *msc_txfifo = *p++; // *msc_txfifo = *p++; until(US(0.79)); *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 */ #if 1 // *msc_txfifo = *p++; #else for (p = line; p != line+1; p++) for (p = line; p != line+1; p++) *msc_txfifo = *p;//0xf0f0f0f0;//*p; #endif until(US(0.79+3.77-0.2)); /* * Without the -0.2, the XEN-1510 only detects the signal in only about * 20% of all cases. */ /* Front porch */ *pdfuns = CMD; *pddats = HSYNC; *pdfunc = CMD; // *msc_txfifo = *p++; // for (; p != line+4; p++) // *msc_txfifo = *p;//0xf0f0f0f0;//*p; // until(US(0.79+3.77+1.79-0.1)); // while (p != line+32) { //line_words) { while (p != line+line_words) { uint8_t st; do { st = *msc_stat; if (st & 3) { // printf("st 0x%04x\n", st); bad++; goto fail; // return; } } while (st & (1 << 7)); *msc_txfifo = *p; //*msc_txfifo = 0xf0f0f0f0; p++; } fail: //(void) *(volatile uint32_t *) (line+line_words); (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 */ #if 0 hdelay(32); #else hdelay(31); *tcnt = 0; *pddatc = HSYNC; until(US(3.77)); *pddats = HSYNC; until(line_cycles-US(0.79)); #endif for (p = f; p != f+240*line_words; p += line_words) { line(p, p); line(p, p+line_words); } /* Back porch */ hdelay(14); } /* ----- Frame buffer image generation ------------------------------------- */ #if 0 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; } } } #endif static uint8_t pattern(int r, int g, int b) { return (r ? 0x88 : 0) | (g ? 0x11 : 0) | (b ? 0x22 : 0); } static void grab(uint8_t *f) { 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; *f++ = pattern(r >= thres, g >= thres, b >= thres); } } /* ----- Command-line parsing and main loop -------------------------------- */ static void session(int frames, int single) { uint32_t f[2*240*line_words]; int i; memset(f, 0, sizeof(f)); grab((uint8_t *) f); // 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 [threshold]\n\n" " frames number of frames to display\n" " threshold channel on/off threshold\n\n" , name); exit(1); } int main(int argc, char *const *argv) { int frames; int single = 1; int c; while ((c = getopt(argc, argv, "")) != EOF) switch (c) { 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(); printf("%d\n", bad); return 0; }