text: /*
   * This file is part of the coreboot project.
   *
   * Copyright (C) 2007 Juergen Beisert <juergen@kreuzholzen.de>
   *
   * 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; version 2 of the License.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
   */
  
  /**
   * @brief Activate the VGA feature in a Geode GX1 based system with one
   * of five possible VESA modes: VGA, SVGA, XGA, 4:3 SXGA and 5:4 SXGA.
   * Also it is prepared to display a splash screen.
   *
   * In a Geode GX1 environment the companion CS5530 is the VGA
   * interface only. It contains a PLL for pixel clock generation,
   * DACs to generate the analogue RGB signals, drivers for HSYNC
   * and VSYNC and drivers for a digital flatpanel.
   * The graphic feature itself (framebuffer, acceleration unit)
   * is not part of this device. It is part of the CPU device.
   * But both depend on each other, we cannot divide them into
   * different drivers. So this driver is not only a CS5530 driver,
   * it is also a Geode GX1 chipset graphic driver.
   */
  #include <arch/io.h>
  #include <device/device.h>
  #include <device/pci.h>
  #include <device/pci_ops.h>
  #include <device/pci_ids.h>
  #include <console/console.h>
  #include <cpu/amd/gx1def.h>
  #include <delay.h>
  
  #if CONFIG_GX1_VIDEO
  /*
   * Some register descriptions that are no listed in cpu/amd/gx1def.h
   */
  #define CS5530_DOT_CLK_CONFIG	0x0024
  #define CS5530_DISPLAY_CONFIG	0x0004
  
  #define DC_FB_ST_OFFSET		0x8310	/* framebuffer start offset */
  #define DC_CB_ST_OFFSET		0x8314	/* compression start offset */
  #define DC_CURS_ST_OFFSET	0x8318	/* cursor start offset */
  #define DC_VID_ST_OFFSET	0x8320	/* video start offset */
  #define DC_LINE_DELTA		0x8324	/* fb and cb skip counts */
  #define DC_BUF_SIZE		0x8328	/* fb and cb line size */
  #define DC_H_TIMING_1		0x8330	/* horizontal timing... */
  #define DC_H_TIMING_2		0x8334
  #define DC_H_TIMING_3		0x8338
  #define DC_FP_H_TIMING		0x833C
  #define DC_V_TIMING_1		0x8340	/* vertical timing... */
  #define DC_V_TIMING_2		0x8344
  #define DC_V_TIMING_3		0x8348
  #define DC_FP_V_TIMING		0x834C
  #define DC_TIMING_CFG		0x8308
  #define DC_OUTPUT_CFG		0x830C
  
  /**
   * what colour depth should be used as default (in bpp)
   * Note: Currently no other value than 16 is supported
   */
  #define COLOUR_DEPTH 16
  
  /**
   * Support for a few basic video modes
   * Note: all modes only for CRT. The flatpanel feature is
   * not supported here (due to the lack of hardware to test)
   */
  struct video_mode {
  	int pixel_clock;		/*<< pixel clock in Hz */
  	unsigned long pll_value;	/*<< pll register value for this clock */
  
  	int visible_pixel;		/*<< visible pixels in one line */
  	int hsync_start;		/*<< start of hsync behind visible pixels */
  	int hsync_end;			/*<< end of hsync behind its start */
  	int line_length;		/*<< whole line length */
  
  	int visible_lines;		/*<< visible lines on screen */
  	int vsync_start;		/*<< vsync start behind last visible line */
  	int vsync_end;			/*<< end of vsync behind its start */
  	int picture_length;		/*<< whole screen length */
  
  	int sync_pol;		/*<< 0: low, 1: high, bit 0 hsync, bit 1 vsync */
  };
  
  /*
   * values for .sync_pol in struct video_mode
   */
  #define HSYNC_HIGH_POL 0
  #define HSYNC_LOW_POL 1
  #define VSYNC_HIGH_POL 0
  #define VSYNC_LOW_POL 2
  
  /**
   * 640x480 @ 72Hz hsync: 37.9kHz
   * VESA standard mode for classic 4:3 monitors
   * Copied from X11:
   * ModeLine "640x480" 31.5 640 664 704 832 480 489 491 520 -hsync -vsync
   */
  static const struct video_mode mode_640x480 = {
  	.pixel_clock = 31500000,
  	.pll_value = 0x33915801,
  
  	.visible_pixel = 640,
  	.hsync_start = 664,
  	.hsync_end = 704,	/* 1.27 us sync length */
  	.line_length = 832,	/* 26.39us */
  
  	.visible_lines = 480,
  	.vsync_start = 489,
  	.vsync_end = 491,
  	.picture_length = 520, /* 13.89ms */
  
  	.sync_pol = HSYNC_LOW_POL | VSYNC_LOW_POL,
  };
  
  /**
   * 800x600 @ 72Hz hsync: 48.1kHz
   * VESA standard mode for classic 4:3 monitors
   * Copied from X11:
   * ModeLine "800x600" 50.0 800 856 976 1040 600 637 643 666 +hsync +vsync
   */
  static const struct video_mode mode_800x600 = {
  	.pixel_clock = 50000000,
  	.pll_value = 0x23088801,
  
  	.visible_pixel = 800,
  	.hsync_start = 856,
  	.hsync_end = 976,
  	.line_length = 1040, /* 20.8us */
  
  	.visible_lines = 600,
  	.vsync_start = 637,
  	.vsync_end = 643,
  	.picture_length = 666, /* 13.89ms */
  
  	.sync_pol = HSYNC_HIGH_POL | VSYNC_HIGH_POL,
  };
  
  /**
   * 1024x768 @ 70Hz (VESA) hsync: 56.5kHz
   * Standard mode for classic 4:3 monitors
   * Copied from X11:
   * ModeLine "1024x768" 75.0 1024 1048 1184 1328 768 771 777 806 -hsync -vsync
   */
  static const struct video_mode mode_1024x768 = {
  	.pixel_clock = 75000000,
  	.pll_value = 0x37E22801,
  
  	.visible_pixel = 1024,
  	.hsync_start = 1048,
  	.hsync_end = 1184,
  	.line_length = 1328,	/* 17.7us */
  
  	.visible_lines = 768,
  	.vsync_start = 771,
  	.vsync_end = 777,
  	.picture_length = 806,	/* 14.3us */
  
  	.sync_pol = HSYNC_LOW_POL | VSYNC_LOW_POL,
  };
  
  /**
   * 1280x960 @ 60Hz (VESA) hsync: 60.0kHz
   * Mode for classic 4:3 monitors
   * Copied from X11:
   * ModeLine "1280x960" 108.0 1280 1376 1488 1800 960 961 964 1000 +hsync +vsync
   */
  static const struct video_mode mode_1280x960 = {
  	.pixel_clock = 108000000,
  	.pll_value = 0x2710C805,
  
  	.visible_pixel = 1280,
  	.hsync_start = 1376,
  	.hsync_end = 1488,
  	.line_length = 1800,	/* 16.67us */
  
  	.visible_lines = 960,
  	.vsync_start = 961,
  	.vsync_end = 964,
  	.picture_length = 1000,	/* 16.67ms */
  
  	.sync_pol = HSYNC_HIGH_POL | VSYNC_HIGH_POL,
  };
  
  /**
   * 1280x1024 @ 60Hz (VESA) hsync: 64.0kHz
   * Mode for modern 5:4 flat screens
   * Copied from X11:
   * ModeLine "1280x1024" 108.0 1280 1328 1440 1688 1024 1025 1028 1066 +hsync +vsync
   */
  static const struct video_mode mode_1280x1024 = {
  	.pixel_clock = 108000000,
  	.pll_value = 0x2710C805,
  
  	.visible_pixel = 1280,
  	.hsync_start = 1328,
  	.hsync_end = 1440,
  	.line_length = 1688,	/* 15.6us */
  
  	.visible_lines = 1024,
  	.vsync_start = 1025,
  	.vsync_end = 1028,
  	.picture_length = 1066,
  
  	.sync_pol = HSYNC_HIGH_POL | VSYNC_HIGH_POL,
  };
  
  /**
   * List of supported common modes
   */
  static const struct video_mode *modes[] = {
  	&mode_640x480,	/* CONFIG_GX1_VIDEOMODE = 0 */
  	&mode_800x600,	/* CONFIG_GX1_VIDEOMODE = 1 */
  	&mode_1024x768,	/* CONFIG_GX1_VIDEOMODE = 2 */
  	&mode_1280x960,	/* CONFIG_GX1_VIDEOMODE = 3 */
  	&mode_1280x1024	/* CONFIG_GX1_VIDEOMODE = 4 */
  };
  
  /* make a sanity check at buildtime */
  #if CONFIG_GX1_VIDEOMODE > 4
  # error Requested video mode is unknown!
  #endif
  
  /**
   * Setup the pixel PLL in the companion chip
   * @param[in] base register's base address
   * @param[in] pll_val pll register value to be set
   *
   * The PLL to program here is located in the CS5530
   */
  static void cs5530_set_clock_frequency(u32 io_base, unsigned long pll_val)
  {
  	unsigned long reg;
  
  	/* disable the PLL first, reset and power it down */
  	reg = read32(io_base+CS5530_DOT_CLK_CONFIG) & ~0x20;
  	reg |= 0x80000100;
  	write32(io_base+CS5530_DOT_CLK_CONFIG, reg);
  
  	/* write the new PLL setting */
  	reg |= (pll_val & ~0x80000920);
  	write32(io_base+CS5530_DOT_CLK_CONFIG, reg);
  
  	mdelay(1);	/* wait for control voltage to be 0V */
  
  	/* enable the PLL */
  	reg |= 0x00000800;
  	write32(io_base+CS5530_DOT_CLK_CONFIG, reg);
  
  	/* clear reset */
  	reg &= ~0x80000000;
  	write32(io_base+CS5530_DOT_CLK_CONFIG, reg);
  
  	/* clear bypass */
  	reg &= ~0x00000100;
  	write32(io_base+CS5530_DOT_CLK_CONFIG, reg);
  }
  
  /**
   * Setup memory layout
   * @param[in] gx_base GX register area
   * @param[in] mode Data about the video mode to setup
   *
   * Memory layout must be setup in Geode GX1's chipset.
   * Note: This routine assumes unlocked DC registers.
   * Note: Using compressed buffer is not supported yet!
   * (makes more sense later, but not while booting)
   *
   * At this point a check is missed if the requested video
   * mode is possible with the provided video memory.
   * Check if symbol CONFIG_VIDEO_MB is at least:
   * - 1 (=1MiB) for VGA and SVGA
   * - 2 (=2MiB) for XGA
   * - 4 (=4MiB) for SXGA
   */
  static void dc_setup_layout(u32 gx_base, const struct video_mode *mode)
  {
  	u32 base = 0x00000000;
  
  	write32(gx_base + DC_FB_ST_OFFSET, base);
  
  	base += (COLOUR_DEPTH>>3) * mode->visible_pixel * mode->visible_lines;
  
  	write32(gx_base + DC_CB_ST_OFFSET, base);
  	write32(gx_base + DC_CURS_ST_OFFSET, base);
  	write32(gx_base + DC_VID_ST_OFFSET, base);
  	write32(gx_base + DC_LINE_DELTA, ((COLOUR_DEPTH>>3) * mode->visible_pixel) >> 2);
  	write32(gx_base + DC_BUF_SIZE, ((COLOUR_DEPTH>>3) * mode->visible_pixel) >> 3);
  }
  
  /**
   * Setup the HSYNC/VSYNC, active video timing
   * @param[in] gx_base GX register area
   * @param[in] mode Data about the video mode to setup
   *
   * Sync signal generation is done in Geode GX1's chipset.
   * Note: This routine assumes unlocked DC registers
   *
   * |<------------------------- htotal ----------------------------->|
   * |<------------ hactive -------------->|                          |
   * |                       hblankstart-->|                          |
   * |                                                    hblankend-->|
   * |                                hsyncstart-->|                  |
   * |                                           hsyncend-->|         |
   * |#####################################___________________________| RGB data
   * |______________________________________________---------_________| HSYNC
   *
   * |<------------------------- vtotal ----------------------------->|
   * |<------------ vactive -------------->|                          |
   * |                       vblankstart-->|                          |
   * |                                                    vblankend-->|
   * |                                vsyncstart-->|                  |
   * |                                           vsyncend-->|         |
   * |#####################################___________________________| line data
   * |______________________________________________---------_________| YSYNC
   */
  static void dc_setup_timing(u32 gx_base, const struct video_mode *mode)
  {
  	u32 hactive, hblankstart, hsyncstart, hsyncend, hblankend, htotal;
  	u32 vactive, vblankstart, vsyncstart, vsyncend, vblankend, vtotal;
  
  	hactive = mode->visible_pixel & 0x7FF;
  	hblankstart = hactive;
  	hsyncstart = mode->hsync_start & 0x7FF;
  	hsyncend =  mode->hsync_end & 0x7FF;
  	hblankend = mode->line_length & 0x7FF;
  	htotal = hblankend;
  
  	vactive = mode->visible_lines & 0x7FF;
  	vblankstart = vactive;
  	vsyncstart = mode->vsync_start & 0x7FF;
  	vsyncend =  mode->vsync_end & 0x7FF;
  	vblankend = mode->picture_length & 0x7FF;
  	vtotal = vblankend;
  
  	/* row description */
  	write32(gx_base + DC_H_TIMING_1, (hactive - 1) | ((htotal - 1) << 16));
  	/* horizontal blank description */
  	write32(gx_base + DC_H_TIMING_2, (hblankstart - 1) | ((hblankend - 1) << 16));
  	/* horizontal sync description */
  	write32(gx_base + DC_H_TIMING_3, (hsyncstart - 1) | ((hsyncend - 1) << 16));
  	write32(gx_base + DC_FP_H_TIMING, (hsyncstart - 1) | ((hsyncend - 1) << 16));
  
  	/* line description */
  	write32(gx_base + DC_V_TIMING_1, (vactive - 1) | ((vtotal - 1) << 16));
  	/* vertical blank description */
  	write32(gx_base + DC_V_TIMING_2, (vblankstart - 1) | ((vblankend - 1) << 16));
  	/* vertical sync description */
  	write32(gx_base + DC_V_TIMING_3, (vsyncstart - 1) | ((vsyncend - 1) << 16));
  	write32(gx_base + DC_FP_V_TIMING, (vsyncstart - 2) | ((vsyncend - 2) << 16));
  }
  
  /**
   * Setup required internals to bring the mode up and running
   * @param[in] gx_base GX register area
   * @param[in] mode Data about the video mode to setup
   *
   * Must be setup in Geode GX1's chipset.
   * Note: This routine assumes unlocked DC registers.
   */
  static void cs5530_activate_mode(u32 gx_base, const struct video_mode *mode)
  {
  	write32(gx_base + DC_GENERAL_CFG, 0x00000080);
  	mdelay(1);
  	dc_setup_layout(gx_base,mode);
  	dc_setup_timing(gx_base,mode);
  
  	write32(gx_base + DC_GENERAL_CFG, 0x2000C581);
  	write32(gx_base + DC_TIMING_CFG, 0x0000002F);
  	write32(gx_base + DC_OUTPUT_CFG, 0x00003004);
  }
  
  /**
   * Activate the current mode to be "visible" outside
   * @param[in] gx_base GX register area
   * @param[in] mode Data about the video mode to setup
   *
   * As we now activate the interface this must be done
   * in the CS5530
   */
  static void cs5530_activate_video(u32 io_base, const struct video_mode *mode)
  {
  	u32 val;
  
  	val = (u32)mode->sync_pol << 8;
  	write32(io_base + CS5530_DISPLAY_CONFIG, val | 0x0020002F);
  }
  
  #if CONFIG_SPLASH_GRAPHIC
  
  /*
   * This bitmap file must provide:
   * int width: pixel count in one line
   * int height: line count
   * int colours: ount of used colour
   * unsigned long colour_map[]: RGB 565 colours to be used
   * unsigned char bitmap[]: index per pixel into colour_map[], width*height pixels
   */
  #include "bitmap.c"
  
  /*
   * show a boot splash screen in the right lower corner of the screen
   * swidth: screen width in pixel
   * sheight: screen height in lines
   * pitch: line pitch in bytes
   * base: screen base address
   *
   * This routine assumes we are using a 16 bit colour depth!
   */
  static void show_boot_splash_16(u32 swidth, u32 sheight, u32 pitch,void *base)
  {
  	int word_count,i;
  	unsigned short *adr;
  	u32 xstart,ystart,x,y;
  	/*
  	 * fill the screen with the colour of the
  	 * left top pixel in the graphic
  	 */
  	word_count = pitch * sheight;
  	adr = (unsigned short*)base;
  	for (i = 0; i < word_count; i++, adr++)
  		*adr = colour_map[bitmap[0]];
  
  	/*
  	 * paint the splash
  	 */
  	xstart = swidth-width;
  	ystart = sheight-height;
  	for (y = 0; y < height; y++) {
  		adr=(unsigned short*)(base + pitch*(y+ystart) + 2 * xstart);
  		for (x = 0; x < width; x++) {
  			*adr=(unsigned short)colour_map[(int)bitmap[x + y * width]];
  			adr++;
  		}
  	}
  }
  #else
  # define show_boot_splash_16(w, x, y , z)
  #endif
  
  /**
   * coreboot management part
   * @param[in] dev Info about the PCI device to initialise
   */
  static void cs5530_vga_init(device_t dev)
  {
  	const struct video_mode *mode;
  	u32 io_base, gx_base;
  
  	io_base = pci_read_config32(dev, 0x10);
  	gx_base = GX_BASE;
  	mode = modes[CONFIG_GX1_VIDEOMODE];
  
  	printk(BIOS_DEBUG, "Setting up video mode %dx%d with %d Hz clock\n",
  		mode->visible_pixel, mode->visible_lines, mode->pixel_clock);
  
  	cs5530_set_clock_frequency(io_base, mode->pll_value);
  
  	write32(gx_base + DC_UNLOCK, DC_UNLOCK_MAGIC);
  
  	show_boot_splash_16(mode->visible_pixel, mode->visible_lines,
  		mode->visible_pixel * (COLOUR_DEPTH>>3), (void*)(GX_BASE + 0x800000));
  
  	cs5530_activate_mode(gx_base, mode);
  
  	cs5530_activate_video(io_base, mode);
  	write32(gx_base + DC_UNLOCK, 0x00000000);
  }
  
  static struct device_operations vga_ops = {
  	.read_resources   = pci_dev_read_resources,
  	.set_resources    = pci_dev_set_resources,
  	.enable_resources = pci_dev_enable_resources,
  	.init             = cs5530_vga_init,
  	.enable           = NULL, /* not required */
  };
  
  static const struct pci_driver vga_pci_driver __pci_driver = {
  	.ops 	= &vga_ops,
  	.vendor = PCI_VENDOR_ID_CYRIX,
  	.device = PCI_DEVICE_ID_CYRIX_5530_VIDEO,
  };
  
  #endif /* #if CONFIG_GX1_VIDEO */
  