/* * 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; static int keep_lcd = 0; #define US(us) ((uint16_t) ((us)*112)) static const struct mode mode_db[] = { /* name xres yres clkdiv vfront hsync hback htotal */ /* vsync vback */ { "640x480", 640, 480, 12, 2, 32, 14, US(2.80), US(1.30), US(32.1) }, { "640x480/58", 640, 480, 12, 2, 10, 33, US(3.81), US(1.91), US(32.7) }, { "640x480/61", 640, 480, 11, 2, 9, 29, US(2.50), US(4.06), US(31.5) }, { "640x480/70", 640, 480, 9, 2, 8, 29, US(1.90), US(2.06), US(24.8) }, { "800x600/54", 800, 600, 8, 2, 32, 14, US(4.81), US(0.79), US(28.8) }, { "800x600/56", 800, 600, 8, 2, 1, 22, US(2.00), US(3.56), US(28.5) }, { "800x600/72", 800, 600, 5, 3, 1, 27, US(2.14), US(2.70), US(22.0) }, /* the 1024x768 below is not great but has good parameter tolerance */ { "1024x768", 1024, 768, 8, 2, 32, 14, US(4.51), US(0.79), US(36.0) }, /* illustrate underruns (without DMA) */ { "1024x768ur", 1024, 768, 7, 2, 32, 14, US(2.21), US(0.79), US(33.5) }, { "1024x768/53",1024, 768, 5, 2, 32, 14, US(1.31), US(0.79), US(23.1) }, { "1024x768/50",1024, 768, 5, 6, 3, 29, US(2.10), US(2.46), US(24.5) }, { NULL } }; /* * 640x480: loosely based on the timing from "Build a VGA Monitor * Controller" by Enoch Hwang * * * 640x480/58: http://tinyvga.com/vga-timing/640x480@60Hz * * H: 3.81+0.64+25.42+1.91 us = 31.78 us * * Pixel clock is 25.175 MHz. We have 25.85 MHz, thus: * 25.42/25.75*25.175 = 24.85 * * htotal should be 31.77+(24.85-25.175) = 31.445, but we actually need more. * Seems that our hfront is about 1.7 us. * * Observation: bad FIFO jitter. * * * 640x480/70: http://tinyvga.com/vga-timing/640x480@73Hz * * That's a pretty ugly adaptation. The DMA really seems to dislike the 73 Hz * parameters. * * * 800x600/54: just a lucky combination * * * 800x600/56: http://tinyvga.com/vga-timing/800x600@56Hz * * Note that we have the sync pulses upside-down. * * * 800x600/7: yet another lucky combination * * * 1024x768/50: loosely based on http://tinyvga.com/vga-timing/1024x768@60Hz */ const struct mode *mode = mode_db; static uint32_t clkrt = 0; /* ----- 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 #define KEY_MASK 0x5f70000 static uint32_t old_icmr; static uint32_t old_clkgr; static uint32_t old_lcdctrl; 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 tame_lcd(void) { old_lcdctrl = LCDCTRL; /* LCDCTRL.BST = 0, for 4 word burst, the shortest available */ LCDCTRL = old_lcdctrl & 0xfffffff; } static void restore_lcd(void) { LCDCTRL = old_lcdctrl; } 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); } /* ----- Delay logic ------------------------------------------------------- */ static void delay(uint16_t cycles) { static uint16_t next = 0; uint16_t t; next += cycles; TDHR(TIMER) = next; TFCR = 1 << (TIMER+16); while (!(TFR & (1 << (TIMER+16)))); t = next+24-TCNT(TIMER); t >>= 1; while (t--) asm(""); } /* ----- Frame buffer output ----------------------------------------------- */ 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 */ while (MSCCDR & 1) { MSCCDR >>= 1; clkrt++; } } static void setup_noirq(void) { if (keep_lcd) tame_lcd(); else disable_lcd(); get_timer(); 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; release_timer(); if (keep_lcd) restore_lcd(); else enable_lcd(); } 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+63) >> 6; delay(mode->hback_cycles); /* HSYNC */ PDDATC = HSYNC; MSC_CLKRT = clkrt; /* bus clock = MSC clock / n */ 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; delay(mode->hsync_cycles); // MSC_TXFIFO = 0xffffffff; /* Front porch */ PDFUNS = CMD; PDDATS = HSYNC; PDFUNC = CMD; delay(mode->line_cycles-mode->hback_cycles-mode->hsync_cycles); MSC_TXFIFO = 0; if (MSC_STAT & 3) bad++; } static void hdelay(int cycles) { while (cycles--) { PDDATC = HSYNC; delay(mode->hsync_cycles); PDDATS = HSYNC; delay(mode->line_cycles-mode->hsync_cycles); } } static void frame(const unsigned long *f) { const unsigned long *p; /* VSYNC */ PDDATC = VSYNC; hdelay(mode->vsync_lines); PDDATS = VSYNC; /* Front porch */ hdelay(mode->vfront_lines-1); /* * The horizontal back porch of the previous line is handled inside * "line", so we have to wait for less than a full line here. */ PDDATC = HSYNC; delay(mode->hsync_cycles); PDDATS = HSYNC; delay(mode->line_cycles-mode->hback_cycles-mode->hsync_cycles); for (p = f; p != f+mode->yres; p++) line(*p); delay(mode->hback_cycles); /* Back porch */ hdelay(mode->vback_lines); } /* ----- 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, bytes; ccube_init(); bytes = mode->xres/2; bytes = (bytes+31) & ~31; f_virt = calloc_phys_vec(mode->yres, bytes); gen(f_virt, mode->xres, mode->yres); f_phys = xlat_virt(f_virt, mode->yres); disable_interrupts(); setup_noirq(); TCNT(TIMER) = 0; for (i = 0; !frames || i != frames; i++) { frame(f_phys); if (DTC(DMA)) { fprintf(stderr, "DMA locked up. Need hardware reset.\n"); break; } if (!frames && (PDPIN & KEY_MASK) != KEY_MASK) break; } cleanup_noirq(); enable_interrupts(); } static void list_modes(void) { const struct mode *m; for (m = mode_db; m->name; m++) { printf("\"%s\", %dx%d:\n", m->name, m->xres, m->yres); printf("\t336/%d = %5.2f MHz\n", m->clkdiv+1, 336.0/(m->clkdiv+1)); printf("\tH: %4.2f+...+%4.2f = %5.2f us\n", CYCLES(m->hsync_cycles), CYCLES(m->hback_cycles), CYCLES(m->line_cycles)); printf("\tV: %d+%d+%d+%d lines, %5.2f Hz\n", m->vsync_lines, m->vfront_lines, m->yres, m->vback_lines, 112000000.0/m->line_cycles/ (m->vsync_lines+m->vfront_lines+m->yres+m->vback_lines)); } } static void usage(const char *name) { fprintf(stderr, "usage: %s [-2] [-t] [-m resolution] [frames [file]]\n" " %s -l\n\n" " frames number of frames to display\n" " file PPM file\n\n" " -2 keep on refreshing the LCD display (experimental)\n" " -l list available modes\n" " -m mode select the display mode, default \"%s\"\n" " -t generate a test image\n" , name, 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 = 0; int c; while ((c = getopt(argc, argv, "2lm:t")) != EOF) switch (c) { case '2': keep_lcd = 1; break; case 'l': list_modes(); exit(0); 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; case 0: break; default: usage(*argv); } setup(); session(gen, frames); #if 0 printf("clkdiv: %d, /%d /%d\n", mode->clkdiv, (MSCCDR & 15)+1, 1 << (MSC_CLKRT & 7)); #endif if (bad) printf("%d timeout%s\n", bad, bad == 1 ? "" : "s"); return 0; }