/*
 * aty.c: Console support for ATI/mach64 display adaptor cards.
 *
 * Copyright (C) 1997 Michael AK Tesch
 *  written with much help from Jon Howell
 *  changes to support the vt chip set by harry ac eaton
 *  gt chipset support, scrollback console by anthony tong <atong@uiuc.edu>
 * 
 * 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.
 */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/vc_ioctl.h>
#include <linux/pci.h>
#include <linux/bios32.h>
#include <linux/nvram.h>
#include <linux/selection.h>
#include <linux/vt_kern.h>
#include <asm/prom.h>
#include <asm/io.h>
#include <asm/pci-bridge.h>
#include "pmac-cons.h"
#include "aty.h"

struct aty_cmap_regs {
	unsigned char windex;
	unsigned char lut;
	unsigned char mask;
	unsigned char rindex;
	unsigned char cntl;
};

typedef struct aty_regvals {
	int offset[3];		/* first pixel address */

	int crtc_h_sync_strt_wid[3];	/* depth dependant */
	int crtc_gen_cntl[3];
	int mem_cntl[3];

	int crtc_h_tot_disp;	/* mode dependant */
	int crtc_v_tot_disp;
	int crtc_v_sync_strt_wid;
	int crtc_off_pitch;

	unsigned char clock_val[2];	/* vals for 20 and 21 */
} aty_regvals;

struct rage_regvals {
	int h_total, h_sync_start, h_sync_width;
	int v_total, v_sync_start, v_sync_width;
	int h_sync_neg, v_sync_neg;
};

static int aty_vram_reqd(int vmode, int cmode);
static aty_regvals *get_aty_struct(void);

static unsigned char *frame_buffer;
static int total_vram;		/* total amount of video memory, bytes */
static int chip_type;		/* what chip type was detected */

static unsigned long ati_regbase;
static struct aty_cmap_regs *aty_cmap_regs;

#if 0
/* this array contains the number of bytes/line for each mode and color depth */
static int pitch[20][3] = {
	{512, 1024, 2048},		/* mode 1 */
	{512, 1024, 2048},		/* mode 2 */
	{640, 1024, 2048},		/* mode 3 */
	{640, 1024, 2048},		/* mode 4 */
	{640, 1280, 2560},		/* mode 5 */
	{640, 1280, 2560},		/* mode 6 */
	{640, 1280, 2560},		/* mode 7 */
	{800, 1600, 3200},		/* mode 8 */
	{768, 1536, 2072},		/* mode 9 */
	{800, 1600, 3200},		/* mode 10 */
	{800, 1600, 3200},		/* mode 11 */
	{800, 1600, 3200},		/* mode 12 */
	{832, 1664, 3328},		/* mode 13 */
	{1024, 2048, 4096},		/* mode 14 */
	{1024, 2048, 4096},		/* mode 15 */
	{1024, 2048, 4096},		/* mode 16 */
	{1024, 2048, 4096},		/* mode 17 */
	{1152, 2304, 4608},		/* mode 18 */
	{1280, 2560, 5120},		/* mode 19 */
	{1280, 2560, 5120}		/* mode 20 */
};
#endif

#include "ati-gx.h"
#include "ati-gt.h"
#include "ati-vt.h"

static struct aty_regvals *aty_gt_reg_init[20] = {
	NULL, NULL, NULL, NULL,
	&aty_gt_reg_init_5,
	&aty_gt_reg_init_6,
	NULL, NULL,
	&aty_gt_reg_init_9,
	&aty_gt_reg_init_10,
	&aty_gt_reg_init_11,
	&aty_gt_reg_init_12,
	&aty_gt_reg_init_13,
	&aty_gt_reg_init_14,
	&aty_gt_reg_init_15,
	NULL,
	&aty_gt_reg_init_17,
	&aty_gt_reg_init_18,
	NULL,
	&aty_gt_reg_init_20
};

static struct aty_regvals *aty_gx_reg_init[20] = {
	NULL, NULL, NULL, NULL,
	&aty_gx_reg_init_6,
	&aty_gx_reg_init_6,
	NULL, NULL, NULL, NULL, NULL, NULL,
	&aty_gx_reg_init_13,
	&aty_gx_reg_init_14,
	&aty_gx_reg_init_15,
	NULL,
	&aty_gx_reg_init_17,
	NULL, NULL,
	&aty_gx_reg_init_20
};

static struct aty_regvals *aty_vt_reg_init[21] = {
	NULL, NULL, NULL, NULL,
	&aty_vt_reg_init_5,
	&aty_vt_reg_init_6,
	NULL, NULL, NULL,
	&aty_vt_reg_init_10,
	&aty_vt_reg_init_11,
	&aty_vt_reg_init_12,
	&aty_vt_reg_init_13,
	&aty_vt_reg_init_14,
	&aty_vt_reg_init_15,
	NULL,
	&aty_vt_reg_init_17,
	&aty_vt_reg_init_18,
	&aty_vt_reg_init_19,
	&aty_vt_reg_init_20
};

static inline int
aty_vram_reqd(int vmode, int cmode)
{
	return vmode_attrs[vmode - 1].vres * 
		(vmode_attrs[vmode - 1].hres << cmode);
}

extern inline unsigned aty_ld_le32(volatile unsigned long addr)
{
	register unsigned long temp = ati_regbase,val;

	asm("lwbrx %0,%1,%2": "=r"(val):"r"(addr), "r"(temp));
	return val;
}

extern inline void aty_st_le32(volatile unsigned long addr, unsigned val)
{
	register unsigned long temp = ati_regbase;
	asm("stwbrx %0,%1,%2": : "r"(val), "r"(addr), "r"(temp):"memory");
}

extern inline unsigned char aty_ld_8(volatile unsigned long addr)
{
	return *(char *) ((long) addr + (long) ati_regbase);
}

extern inline void aty_st_8(volatile unsigned long addr, unsigned char val)
{
	*(unsigned char *) (addr + (unsigned long) ati_regbase) = val;
}

static void aty_st_514(int offset, char val)
{
	aty_WaitQueue(5);
	aty_st_8(DAC_CNTL, 1);
	aty_st_8(DAC_W_INDEX, offset & 0xff);	/* right addr byte */
	aty_st_8(DAC_DATA, (offset >> 8) & 0xff);	/* left addr byte */
	eieio();
	aty_st_8(DAC_MASK, val);
	eieio();
	aty_st_8(DAC_CNTL, 0);
}

static void
aty_st_pll(int offset, char val)
{
	aty_WaitQueue(3);
	aty_st_8(CLOCK_CNTL + 1, (offset << 2) | PLL_WR_EN);	/* write addr byte */
	eieio();
	aty_st_8(CLOCK_CNTL + 2, val);	/* write the register value */
	eieio();
	aty_st_8(CLOCK_CNTL + 1, (offset << 2) & ~PLL_WR_EN);
}

#if 0 // unused
static char
aty_ld_pll(int offset)
{
	aty_WaitQueue(2);
	aty_st_8(CLOCK_CNTL + 1, offset << 2);
	eieio();
	return aty_ld_8(CLOCK_CNTL + 2);
}
#endif

unsigned char
aty_ld_514(int offset)
{
/* do the same thing as aty_st_514, just read the DAC_MASK instead of writing */
	char val;

	aty_WaitQueue(5);
	aty_st_8(DAC_CNTL, 1);
	aty_st_8(DAC_W_INDEX, offset & 0xff);	/* right addr byte */
	aty_st_8(DAC_DATA, (offset >> 8) & 0xff);	/* left addr byte */
	val = aty_ld_8(DAC_MASK);
	eieio();
	aty_st_8(DAC_CNTL, 0);
	return val;
}

void
aty_set_palette(unsigned char red[], unsigned char green[],
		unsigned char blue[], int index, int ncolors)
{
	int i, scale;

	aty_WaitQueue(2);

	i = aty_ld_8(DAC_CNTL) & 0xfc;
	if (chip_type == MACH64_GT_ID)
		i |= 0x2;		/*DAC_CNTL|0x2 turns off the extra brightness for gt*/
	aty_st_8(DAC_CNTL, i);
	aty_st_8(DAC_REGS + DAC_MASK, 0xff);
	eieio();
	scale = (chip_type != MACH64_GX_ID)
		? ((color_mode == CMODE_16) ? 3 : 0) : 0;

	for (i = 0; i < ncolors; ++i) {
		aty_WaitQueue(4);
		aty_cmap_regs->windex = (index + i) << scale; eieio();
		aty_cmap_regs->lut = red[i];	eieio();
		aty_cmap_regs->lut = green[i];	eieio();
		aty_cmap_regs->lut = blue[i];	eieio();
	}
}

static aty_regvals
*get_aty_struct()
{
	int v = video_mode - 1;

	switch (chip_type) {
	case MACH64_GT_ID:
		return aty_gt_reg_init[v];
		break;
	case MACH64_VT_ID:
		return aty_vt_reg_init[v];
		break;
	default: /* default to MACH64_GX_ID */
		return aty_gx_reg_init[v];
		break;
	}
}

static int
read_aty_sense(void)
{
	int sense, i;

	aty_st_le32(MON_SENSE, 0x31003100);	/* drive outputs high */
	__delay(200);
	aty_st_le32(MON_SENSE, 0);		/* turn off outputs */
	__delay(2000);
	i = aty_ld_le32(MON_SENSE);		/* get primary sense value */
	sense = ((i & 0x3000) >> 3) | (i & 0x100);

	/* drive each sense line low in turn and collect the other 2 */
	aty_st_le32(MON_SENSE, 0x20000000);	/* drive A low */
	__delay(2000);
	i = aty_ld_le32(MON_SENSE);
	sense |= ((i & 0x1000) >> 7) | ((i & 0x100) >> 4);
	aty_st_le32(MON_SENSE, 0x20002000);	/* drive A high again */
	__delay(200);

	aty_st_le32(MON_SENSE, 0x10000000);	/* drive B low */
	__delay(2000);
	i = aty_ld_le32(MON_SENSE);
	sense |= ((i & 0x2000) >> 10) | ((i & 0x100) >> 6);
	aty_st_le32(MON_SENSE, 0x10001000);	/* drive B high again */
	__delay(200);

	aty_st_le32(MON_SENSE, 0x01000000);	/* drive C low */
	__delay(2000);
	sense |= (aty_ld_le32(MON_SENSE) & 0x3000) >> 12;
	aty_st_le32(MON_SENSE, 0);		/* turn off outputs */

	return sense;
}

void
map_aty_display(struct device_node *dp)
{
	struct aty_regvals *init;
	int i, sense;
	unsigned long addr;
	unsigned char bus, devfn;
	unsigned short cmd;

	if (dp->next != 0)
	printk("Warning: only using first ATI card detected\n");
	if (dp->n_addrs != 1 && dp->n_addrs != 3)
	printk("Warning: expecting 1 or 3 addresses for ATY (got %d)",
	       dp->n_addrs);

	ati_regbase = (int) ioremap((0x7ffc00 + dp->addrs[0].address), 0x1000);
	aty_cmap_regs = (struct aty_cmap_regs *) (ati_regbase + 0xC0);

	/* enable memory-space accesses using config-space command register */
	if (pci_device_loc(dp, &bus, &devfn) == 0) {
		pcibios_read_config_word(bus, devfn, PCI_COMMAND, &cmd);
		if (cmd != 0xffff) {
			cmd |= PCI_COMMAND_MEMORY;
			pcibios_write_config_word(bus, devfn, PCI_COMMAND, cmd);
		}
	}
	chip_type = (aty_ld_le32(CONFIG_CHIP_ID) & CFG_CHIP_TYPE);

	i = aty_ld_le32(MEM_CNTL);
	if (chip_type != MACH64_GT_ID)
		switch (i & MEM_SIZE_ALIAS) {
		case MEM_SIZE_512K:
			total_vram = 0x80000;
			break;
		case MEM_SIZE_1M:
			total_vram = 0x100000;
			break;
		case MEM_SIZE_2M:
			total_vram = 0x200000;
			break;
		case MEM_SIZE_4M:
			total_vram = 0x400000;
			break;
		case MEM_SIZE_6M:
			total_vram = 0x600000;
			break;
		case MEM_SIZE_8M:
			total_vram = 0x800000;
			break;
		default:
			total_vram = 0x80000;
		}
	else
		switch (i & 0xF) { /* 0xF used instead of MEM_SIZE_ALIAS */
		case MEM_SIZE_512K:
			total_vram = 0x80000;
			break;
		case MEM_SIZE_1M:
			total_vram = 0x100000;
			break;
		case MEM_SIZE_2M_GTB:
			total_vram = 0x200000;
			break;
		case MEM_SIZE_4M_GTB:
			total_vram = 0x400000;
			break;
		case MEM_SIZE_6M_GTB:
			total_vram = 0x600000;
			break;
		case MEM_SIZE_8M_GTB:
			total_vram = 0x800000;
			break;
		default:
			total_vram = 0x80000;
		}

#if 1
	printk("aty_display_init: node = %p, addrs = ", dp->node);
	printk(" %x(%x)", dp->addrs[0].address, dp->addrs[0].size);
	printk(", intrs =");
	for (i = 0; i < dp->n_intrs; ++i)
	printk(" %x", dp->intrs[i]);
	printk("\nregbase: %x pci loc: %x:%x total_vram: %x cregs: %x\n", (int) ati_regbase,
		bus, devfn, total_vram, (int) aty_cmap_regs);
#endif
	/* Map in frame buffer */
	addr = dp->addrs[0].address;

	/* use the big-endian aperture (??) */
	addr += 0x800000;
	frame_buffer = ioremap(addr, 0x800000);

	sense = read_aty_sense();
	printk("monitor sense = %x\n", sense);
	if (video_mode == VMODE_NVRAM) {
		video_mode = nvram_read_byte(NV_VMODE);
		init = get_aty_struct();
		if (video_mode <= 0 || video_mode > VMODE_MAX || init == 0)
			video_mode = VMODE_CHOOSE;
	}
	if (video_mode == VMODE_CHOOSE)
		video_mode = map_monitor_sense(sense);

	init = get_aty_struct();
	if (!init)
		video_mode = VMODE_640_480_60;

	/*
	 * Reduce the pixel size if we don't have enough VRAM.
	 */

	if (color_mode == CMODE_NVRAM)
		color_mode = nvram_read_byte(NV_CMODE);
	if (color_mode < CMODE_8 || color_mode > CMODE_32)
		color_mode = CMODE_8;

	while (aty_vram_reqd(video_mode, color_mode) > total_vram) {
		while (color_mode > CMODE_8
			&& aty_vram_reqd(video_mode, color_mode) > total_vram)
			--color_mode;
		/*
		 * adjust the video mode smaller if there still is not enough VRAM
		 */
		if (aty_vram_reqd(video_mode, color_mode) > total_vram) {
			do {
				video_mode--;
				init = get_aty_struct();
			} while ((init == 0) && (video_mode > VMODE_640_480_60));
		}
	}

	if (chip_type == MACH64_GT_ID && (aty_ld_le32(CONFIG_STAT0) & 7) == 5
			&& init->crtc_gen_cntl[1] == 0) {
		video_mode = 6; color_mode = 0;
	}
	return;
}

void
RGB514_Program(int cmode)
{
	typedef struct {
		char pixel_dly;
		char misc2_cntl;
		char pixel_rep;
		char pixel_cntl_index;
		char pixel_cntl_v1;
	} RGB514_DAC_Table;

	static RGB514_DAC_Table RGB514DAC_Tab[8] = {
		{0, 0x41, 0x03, 0x71, 0x45},	// 8bpp
		{0, 0x45, 0x04, 0x0c, 0x01},	// 555
		{0, 0x45, 0x06, 0x0e, 0x00},	// XRGB
	};
	RGB514_DAC_Table *pDacProgTab;

	pDacProgTab = &RGB514DAC_Tab[cmode];

	aty_st_514(0x90, 0x00);
	aty_st_514(0x04, pDacProgTab->pixel_dly);
	aty_st_514(0x05, 0x00);

	aty_st_514(0x2, 0x1);
	aty_st_514(0x71, pDacProgTab->misc2_cntl);
	aty_st_514(0x0a, pDacProgTab->pixel_rep);

	aty_st_514(pDacProgTab->pixel_cntl_index, pDacProgTab->pixel_cntl_v1);
}

void
aty_init()
{
	int i, hres;
	struct aty_regvals *init = get_aty_struct();
	int vram_type = aty_ld_le32(CONFIG_STAT0) & 7;

	if (init == 0) /* paranoia, shouldn't get here */
		panic("aty: display mode %d not supported", video_mode);

	n_scanlines = vmode_attrs[video_mode - 1].vres;
	hres = vmode_attrs[video_mode - 1].hres;
	pixel_size = 1 << color_mode;
	line_pitch = vmode_attrs[video_mode - 1].hres << color_mode;
	row_pitch = line_pitch * 16;

	/* clear FIFO errors */
	aty_st_le32(BUS_CNTL, aty_ld_le32(BUS_CNTL) | BUS_HOST_ERR_ACK
		| BUS_FIFO_ERR_ACK);

	/* Reset engine */
	i = aty_ld_le32(GEN_TEST_CNTL);
	aty_st_le32(GEN_TEST_CNTL, i & ~GUI_ENGINE_ENABLE);
	eieio();
	aty_WaitIdleEmpty();
	aty_st_le32(GEN_TEST_CNTL, i | GUI_ENGINE_ENABLE);
	aty_WaitIdleEmpty();

	if ( chip_type != MACH64_GT_ID ) {
		i = aty_ld_le32(CRTC_GEN_CNTL);
		aty_st_le32(CRTC_GEN_CNTL, i | CRTC_EXT_DISP_EN);
	}

	if ( chip_type == MACH64_GX_ID ) { 
		i = aty_ld_le32(GEN_TEST_CNTL);
		aty_st_le32(GEN_TEST_CNTL, i | GEN_OVR_OUTPUT_EN );
	}

	switch (chip_type) {
	case MACH64_VT_ID:
		aty_st_pll(PLL_MACRO_CNTL, 0xb5);
		aty_st_pll(PLL_REF_DIV, 0x2d);
		aty_st_pll(PLL_GEN_CNTL, 0x14);
		aty_st_pll(MCLK_FB_DIV, 0xbd);
		aty_st_pll(PLL_VCLK_CNTL, 0x0b);
		aty_st_pll(VCLK_POST_DIV, init->clock_val[0]);
		aty_st_pll(VCLK0_FB_DIV, init->clock_val[1]);
		aty_st_pll(VCLK1_FB_DIV, 0xd6);
		aty_st_pll(VCLK2_FB_DIV, 0xee);
		aty_st_pll(VCLK3_FB_DIV, 0xf8);
		aty_st_pll(PLL_XCLK_CNTL, 0x0);
		aty_st_pll(PLL_TEST_CTRL, 0x0);
		aty_st_pll(PLL_TEST_COUNT, 0x0);
		break;
	case MACH64_GT_ID:
		if (vram_type == 5) {
			aty_st_pll(0, 0xcd);
			aty_st_pll(PLL_MACRO_CNTL, 
				  video_mode >= VMODE_1024_768_60? 0xd3: 0xd5);
			aty_st_pll(PLL_REF_DIV, 0x21);
			aty_st_pll(PLL_GEN_CNTL, 0x44);
			aty_st_pll(MCLK_FB_DIV, 0xe8);
			aty_st_pll(PLL_VCLK_CNTL, 0x03);
			aty_st_pll(VCLK_POST_DIV, init->offset[0]);
			aty_st_pll(VCLK0_FB_DIV, init->offset[1]);
			aty_st_pll(VCLK1_FB_DIV, 0x8e);
			aty_st_pll(VCLK2_FB_DIV, 0x9e);
			aty_st_pll(VCLK3_FB_DIV, 0xc6);
			aty_st_pll(PLL_XCLK_CNTL, init->offset[2]);
			aty_st_pll(12, 0xa6);
			aty_st_pll(13, 0x1b);
		} else {
			aty_st_pll(PLL_MACRO_CNTL, 0xd5);
			aty_st_pll(PLL_REF_DIV, 0x21);
			aty_st_pll(PLL_GEN_CNTL, 0xc4); 
			aty_st_pll(MCLK_FB_DIV, 0xda);
			aty_st_pll(PLL_VCLK_CNTL, 0x03);
			/* offset actually holds clock values */
			aty_st_pll(VCLK_POST_DIV, init->offset[0]);
			aty_st_pll(VCLK0_FB_DIV, init->offset[1]);
			aty_st_pll(VCLK1_FB_DIV, 0x8e);
			aty_st_pll(VCLK2_FB_DIV, 0x9e);
			aty_st_pll(VCLK3_FB_DIV, 0xc6);
			aty_st_pll(PLL_TEST_CTRL, 0x0);
			aty_st_pll(PLL_XCLK_CNTL, init->offset[2]);
			aty_st_pll(12, 0xa0);
			aty_st_pll(13, 0x1b);
		}
		break;
	default:
		RGB514_Program(color_mode);
		aty_WaitIdleEmpty();
		aty_st_514(0x06, 0x02);
		aty_st_514(0x10, 0x01);
		aty_st_514(0x70, 0x01);
		aty_st_514(0x8f, 0x1f);
		aty_st_514(0x03, 0x00);
		aty_st_514(0x05, 0x00);
		aty_st_514(0x20, init->clock_val[0]);
		aty_st_514(0x21, init->clock_val[1]);
		break;
	}

	aty_ld_8(DAC_REGS);	/* clear counter */
	aty_WaitIdleEmpty();

	aty_st_le32(CRTC_H_TOTAL_DISP, init->crtc_h_tot_disp);
	aty_st_le32(CRTC_H_SYNC_STRT_WID, init->crtc_h_sync_strt_wid[color_mode]);
	aty_st_le32(CRTC_V_TOTAL_DISP, init->crtc_v_tot_disp);
	aty_st_le32(CRTC_V_SYNC_STRT_WID, init->crtc_v_sync_strt_wid);

	aty_st_8(CLOCK_CNTL, 0);
	aty_st_8(CLOCK_CNTL, CLOCK_STROBE);

	aty_st_le32(CRTC_VLINE_CRNT_VLINE, 0);

	if (chip_type == MACH64_GT_ID) {
		aty_st_le32(BUS_CNTL, 0x7b23a040);

		/* we calculate this so we can use a scrollback buffer.
		 * this should theoretically work with other ati's
		 * OFF_PITCH == (((hres + 7) & 0xfff8) >> 3) << 22 
		 */
		ati_set_origin(0);

		/* need to set DSP values !! assume sdram */
		i = init->crtc_gen_cntl[0] - (0x100000 * color_mode);
		if ( vram_type == 5 )
			i = init->crtc_gen_cntl[1] - (0x100000 * color_mode);
		aty_st_le32(DSP_CONFIG, i); 

		i = aty_ld_le32(MEM_CNTL) & MEM_SIZE_ALIAS;
		if ( vram_type == 5 ) {
			i |= ((1 * color_mode) << 26) | 0x4215b0;
			aty_st_le32(DSP_ON_OFF,sgram_dsp[video_mode-1][color_mode]);

		//aty_st_le32(CLOCK_CNTL,8192);
		} else {
			i |= ((1 * color_mode) << 26) | 0x300090;
			aty_st_le32(DSP_ON_OFF, init->mem_cntl[color_mode]);
		}

		aty_st_le32(MEM_CNTL, i);
		aty_st_le32(EXT_MEM_CNTL, 0x5000001);

		/* if (total_vram > 0x400000)	
			i |= 0x538; this not been verified on > 4Megs!! */
	} else {
		aty_st_le32(CRTC_OFF_PITCH, init->crtc_off_pitch);

/* The magic constant below translates into: 
 * 5   = No RDY delay, 1 wait st for mem write, increment during burst transfer
 * 9   = DAC access delayed, 1 wait state for DAC
 * 0   = Disables interupts for FIFO errors
 * e   = Allows FIFO to generate 14 wait states before generating error
 * 1   = DAC snooping disabled, ROM disabled
 * 0   = ROM page at 0 (disabled so doesn't matter)
 * f   = 15 ROM wait states (disabled so doesn't matter) 
 * f   = 15 BUS wait states (I'm not sure this applies to PCI bus types)
 * at some point it would be good to experiment with bench marks to see if
 * we can gain some speed by fooling with the wait states etc.
 */
		if (chip_type == MACH64_VT_ID)
			aty_st_le32(BUS_CNTL, 0x680000f9);
		else
			aty_st_le32(BUS_CNTL, 0x590e10ff);

		switch (total_vram) {
		case 0x00100000:
			aty_st_le32(MEM_CNTL, vt_mem_cntl[0][color_mode]);
			break;
		case 0x00200000:
			aty_st_le32(MEM_CNTL, vt_mem_cntl[1][color_mode]);
			break;
		case 0x00400000:
			aty_st_le32(MEM_CNTL, vt_mem_cntl[2][color_mode]);
			break;
		default:
			i = aty_ld_le32(MEM_CNTL) & 0x000F;
			aty_st_le32(MEM_CNTL, (init->mem_cntl[color_mode] & 0xFFFFFFF0) | i);
		}
	}
/* These magic constants are harder to figure out
 * on the vt chipset bit 2 set makes the screen brighter
 * and bit 15 makes the screen black! But nothing else
 * seems to matter for the vt DAC_CNTL
 */
	switch (chip_type) {
	case MACH64_GT_ID:
		i = 0x86010102;
		break;
	case MACH64_VT_ID:
		i = 0x87010184;
		break;
	default:
		i = 0x47012100;
		break;
	}

	aty_st_le32(DAC_CNTL, i);
	aty_st_8(DAC_MASK, 0xff);

	switch (color_mode) {
	case CMODE_16:
		i = CRTC_PIX_WIDTH_15BPP; break;
	/*case CMODE_24: */
	case CMODE_32:
		i = CRTC_PIX_WIDTH_32BPP; break;
	case CMODE_8:
	default:
		i = CRTC_PIX_WIDTH_8BPP; break;
	}

	if (chip_type != MACH64_GT_ID) {
		aty_st_le32(CRTC_INT_CNTL, 0x00000002);
		aty_st_le32(GEN_TEST_CNTL, GUI_ENGINE_ENABLE | BLOCK_WRITE_ENABLE);	/* gui_en block_en */
		i |= init->crtc_gen_cntl[color_mode];
	}
	/* Gentlemen, start your crtc engine */
	aty_st_le32(CRTC_GEN_CNTL, CRTC_EXT_DISP_EN | CRTC_EXT_EN | i);
	pmac_init_palette();	/* Initialize colormap */

	/* clear screen */
	fb_start = frame_buffer + (((n_scanlines % 16) * line_pitch) >> 1);
	memsetw((unsigned short *) fb_start, 0, total_vram);

	display_info.height = n_scanlines;
	display_info.width = hres;
	display_info.depth = pixel_size << 3;
	display_info.pitch = line_pitch;
	display_info.mode = video_mode;
	strncpy(display_info.name, "ATY Mach64", sizeof(display_info.name));
	switch ( chip_type ) {
	case MACH64_GX_ID: strcat(display_info.name,"GX");
		break;
	case MACH64_VT_ID: strcat(display_info.name,"VT");
		break;
	case MACH64_GT_ID: strcat(display_info.name,"GT");
		break;
	default: strcat(display_info.name,"unknown");
		break;
	}
	display_info.fb_address = (chip_type != MACH64_GT_ID) ?
		(unsigned long) frame_buffer + init->offset[color_mode] :
		(unsigned long) frame_buffer;
	display_info.cmap_adr_address = (unsigned long) &aty_cmap_regs->windex;
	display_info.cmap_data_address = (unsigned long) &aty_cmap_regs->lut;
	display_info.disp_reg_address = ati_regbase;
}

int
aty_setmode(struct vc_mode *mode, int doit)
{
	int cmode,old_vmode=video_mode;
	struct aty_regvals *init;

#if 1
	if (mode->mode == 21) {
		printk("hace: about to set 0x%x to 0x%x\n", mode->depth, mode->pitch & 0xff);
		aty_st_8(mode->depth, mode->pitch & 0xff);
		return 0;
	}
	if (mode->mode == 0) {
		printk("hace: 0x%x contains 0x%x\n", mode->depth, aty_ld_8(mode->depth));
		return 0;
	}
#endif

	if (mode->mode <= 0 || mode->mode > VMODE_MAX )
		return -EINVAL;

	switch (mode->depth) {
	case 24:
	case 32:
		cmode = CMODE_32;
		break;
	case 16:
		cmode = CMODE_16;
		break;
	case 8:
	case 0:		/* (default) */
		cmode = CMODE_8;
		break;
	default:
		return -EINVAL;
	}
	if (aty_vram_reqd(mode->mode, cmode) > total_vram)
		return -EINVAL;

	video_mode = mode->mode;
	init = get_aty_struct();

	/* Check if we know about the wanted video mode */
	if ( init == 0 || init->crtc_h_sync_strt_wid[cmode] == 0
	   || (chip_type != MACH64_GT_ID && init->crtc_gen_cntl[cmode] == 0)
	   || (chip_type == MACH64_GT_ID && (aty_ld_le32(CONFIG_STAT0) & 7) == 5
	   && init->crtc_gen_cntl[1] == 0)) {
			video_mode = old_vmode;
			return -EINVAL;
	}

	if (doit) {
		video_mode = mode->mode;
		color_mode = cmode;
		hide_cursor();
		aty_init();
	}
	return 0;
}

void
aty_set_blanking(int blank_mode)
{
	char gen_cntl;

	gen_cntl = aty_ld_8(CRTC_GEN_CNTL);
	if (blank_mode & VESA_VSYNC_SUSPEND)
		gen_cntl |= 0x8;
	if (blank_mode & VESA_HSYNC_SUSPEND)
		gen_cntl |= 0x4;
	if ((blank_mode & VESA_POWERDOWN) == VESA_POWERDOWN)
		gen_cntl |= 0x40;
	if (blank_mode == VESA_NO_BLANKING)
		gen_cntl &= ~(0x4c);
	aty_st_8(CRTC_GEN_CNTL, gen_cntl);
}

/* handle video scrollback; offset is in # of characters */
void
ati_set_origin(unsigned short offset)
{
	register int x = (vmode_attrs[video_mode - 1].hres + 7) & 0xfff8,
		lines = offset / video_num_columns, reg;

	reg = ((x >> 3) << 22) |	/* calculate pitch */
	  ((lines * video_font_height * x * (1<<color_mode)) >> 3); /*offset*/

	aty_st_le32(CRTC_OFF_PITCH, reg);
	aty_st_le32(DST_OFF_PITCH, reg);
	aty_st_le32(SRC_OFF_PITCH, reg);

#if 0
	fb_start = display_info.fb_address = 
		(unsigned long) frame_buffer + ((lines-2) * 
		vmode_attrs[video_mode-1].hres) << (4 + color_mode);
#endif
}

int
ati_vram(void)
{
	return total_vram;
}

