/* $NetBSD: radeonfb_bios.c,v 1.1.12.1 2006/12/30 20:48:48 yamt Exp $ */

/*-
 * Copyright (c) 2006 Itronix Inc.
 * All rights reserved.
 *
 * Written by Garrett D'Amore for Itronix Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of Itronix Inc. may not be used to endorse
 *    or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */ 

/*
 * ATI Technologies Inc. ("ATI") has not assisted in the creation of, and
 * does not endorse, this software.  ATI will not be responsible or liable
 * for any actual or alleged damage or loss caused by or in connection with
 * the use of or reliance on this software.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <asm/page.h>
#include <fnmatch.h>

#define RADEON_MEM_SDRAM_MODE_REG           0x0158
#	define RADEON_SDRAM_MODE_MASK       0xffff0000
#	define RADEON_B3MEM_RESET_MASK      0x6fffffff

/*
 * Globals for the entire BIOS.
 */
#define	ROM_HEADER_OFFSET		0x48
#define	MAX_REVISION			0x10
#define	SINGLE_TABLE_REVISION		0x09
#define	MIN_OFFSET			0x60

/*
 * Offsets of specific tables.
 */
#define	RAGE_REGS1_OFFSET		0x0c
#define	RAGE_REGS2_OFFSET		0x4e
#define	DYN_CLOCK_OFFSET		0x52
#define	PLL_INIT_OFFSET			0x46
#define	MEM_CONFIG_OFFSET		0x48

/*
 * Values related to generic intialization tables.
 */
#define	TABLE_ENTRY_FLAG_MASK		0xe000
#define	TABLE_ENTRY_INDEX_MASK		0x1fff
#define	TABLE_ENTRY_COMMAND_MASK	0x00ff

#define	TABLE_FLAG_WRITE_INDEXED	0x0000
#define	TABLE_FLAG_WRITE_DIRECT		0x2000
#define	TABLE_FLAG_MASK_INDEXED		0x4000
#define	TABLE_FLAG_MASK_DIRECT		0x6000
#define	TABLE_FLAG_DELAY		0x8000
#define	TABLE_FLAG_SCOMMAND		0xa000

#define	TABLE_SCOMMAND_WAIT_MC_BUSY_MASK	0x03
#define	TABLE_SCOMMAND_WAIT_MEM_PWRUP_COMPLETE	0x08

/*
 * PLL initialization block values.
 */
#define	PLL_FLAG_MASK			0xc0
#define	PLL_INDEX_MASK			0x3f

#define	PLL_FLAG_WRITE			0x00
#define	PLL_FLAG_MASK_BYTE		0x40
#define	PLL_FLAG_WAIT			0x80

#define	PLL_WAIT_150MKS				1
#define	PLL_WAIT_5MS				2
#define	PLL_WAIT_MC_BUSY_MASK			3
#define	PLL_WAIT_DLL_READY_MASK			4
#define	PLL_WAIT_CHK_SET_CLK_PWRMGT_CNTL24	5


#define BIOS16(offset)	(*((unsigned short *)(bios + (offset))))
#define BIOS32(offset)	(*((unsigned int *)(bios + (offset))))
#define BIOS8(offset)   (*((unsigned char *)(bios + (offset))))

struct rb_table;

static void rb_validate(unsigned char *bios, struct rb_table *);
static int rb_find_asic_table(unsigned char *bios, struct rb_table *);
static int rb_find_mem_reset_table(unsigned char *bios,
    struct rb_table *);
static int rb_find_short_mem_reset_table(unsigned char *bios,
    struct rb_table *);
static int rb_load_init_block(unsigned char *bios, struct rb_table *);
static int rb_load_pll_block(unsigned char *bios, struct rb_table *);
static int rb_reset_sdram(unsigned char *bios, struct rb_table *);

static void rb_wait_mc_busy_mask(unsigned char *bios, int);
static void rb_wait_mem_pwrup_complete(unsigned char *bios, int);
static void rb_wait_dll_ready_mask(unsigned char *bios, int);
static void rb_wait_chk_set_clk_pwrmgt_cntl24(unsigned char *bios);

/*
 * Generic structure describing the tables.
 */
struct rb_table {
	const unsigned char	*name;
	int			offset;
	struct rb_table 	*parent;

	/* validate that the table looks sane */
	void	(*validate)(unsigned char *bios, struct rb_table *);

	/* find looks for the table relative to its "parent" */
	int	(*find)(unsigned char *bios, struct rb_table *);
};

/*
 * Instances of specific tables.
 */
static struct rb_table rb_rage_regs1_table = {
	"rage_regs_1",			/* name */
	RAGE_REGS1_OFFSET,		/* offset */
	NULL,				/* parent */
	rb_validate,			/* validate */
	NULL,				/* find */
};

static struct rb_table rb_rage_regs2_table = {
	"rage_regs_2",			/* name */
	RAGE_REGS2_OFFSET,		/* offset */
	NULL,				/* parent */
	rb_validate,			/* validate */
	NULL,				/* find */
};

static struct rb_table rb_dyn_clock_table = {
	"dyn_clock",			/* name */
	DYN_CLOCK_OFFSET,		/* offset */
	NULL,				/* parent */
	rb_validate,			/* validate */
	NULL,				/* find */
};

static struct rb_table rb_pll_init_table = {
	"pll_init",			/* name */
	PLL_INIT_OFFSET,		/* offset */
	NULL,				/* parent */
	rb_validate,			/* validate */
	NULL,				/* find */
};

static struct rb_table rb_mem_config_table = {
	"mem_config",			/* name */
	MEM_CONFIG_OFFSET,		/* offset */
	NULL,				/* parent */
	rb_validate,			/* validate */
	NULL,				/* find */
};

static struct rb_table rb_mem_reset_table = {
	"mem_reset",			/* name */
	0,				/* offset */
	&rb_mem_config_table,		/* parent */
	NULL,				/* validate */
	rb_find_mem_reset_table,	/* find */
};

static struct rb_table rb_short_mem_reset_table = {
	"short_mem_reset",		/* name */
	0,				/* offset */
	&rb_mem_config_table,		/* parent */
	NULL,				/* validate */
	rb_find_short_mem_reset_table,	/* find */
};

static struct rb_table rb_rage_regs3_table = {
	"rage_regs_3",			/* name */
	0,				/* offset */
	&rb_rage_regs2_table,		/* parent */
	NULL,				/* validate */
	rb_find_asic_table,		/* find */
};

static struct rb_table rb_rage_regs4_table = {
	"rage_regs_4",			/* name */
	0,				/* offset */
	&rb_rage_regs3_table,		/* parent */
	NULL,				/* validate */
	rb_find_asic_table,		/* find */
};

static struct rb_table *rb_tables[] = {
	&rb_rage_regs1_table,
	&rb_rage_regs2_table,
	&rb_dyn_clock_table,
	&rb_pll_init_table,
	&rb_mem_config_table,
	&rb_mem_reset_table,
	&rb_short_mem_reset_table,
	&rb_rage_regs3_table,
	&rb_rage_regs4_table,
	NULL
};

void
rb_validate(unsigned char *bios, struct rb_table *tp)
{
	unsigned char	rev;

	rev = BIOS8(tp->offset - 1);

	if (rev > MAX_REVISION) {
		printf("bad rev 0x%x of %s\n", rev, tp->name);
		tp->offset = 0;
		return;
	}

	if (tp->offset < MIN_OFFSET) {
		printf("wrong pointer to %s!\n", tp->name);
		tp->offset = 0;
		return;
	}
}

int
rb_find_asic_table(unsigned char *bios, struct rb_table *tp)
{
	int		offset;
	unsigned char	c;

	if ((offset = tp->offset) != 0) {
		while ((c = BIOS8(offset + 1)) != 0) {
			if (c & 0x40)
				offset += 10;
			else if (c & 0x80)
				offset += 4;
			else
				offset += 6;
		}
		return offset + 2;
	}
	return 0;
}

int
rb_find_mem_reset_table(unsigned char *bios, struct rb_table *tp)
{
	int		offset;

	if ((offset = tp->offset) != 0) {
		while (BIOS8(offset))
			offset++;
		offset++;
		return offset + 2;	/* skip table revision and mask */
	}
	return 0;
}

int
rb_find_short_mem_reset_table(unsigned char *bios, struct rb_table *tp)
{

	if ((tp->offset != 0) && (BIOS8(tp->offset - 2) <= 64))
		return (tp->offset + BIOS8(tp->offset - 3));

	return 0;
}

/* helper commands */
void
rb_wait_mc_busy_mask(unsigned char *bios, int count)
{
	printf("WAIT_MC_BUSY_MASK: %d \n", count);
//	while (count--) {
//		if (!(radeonfb_getpll(sc, RADEON_CLK_PWRMGT_CNTL) &
//			RADEON_MC_BUSY_MASK))
//			break;
//	}
	printf("%d\n", count);
}

void
rb_wait_mem_pwrup_complete(unsigned char *bios, int count)
{
	printf("WAIT_MEM_PWRUP_COMPLETE: %d \n", count);
//	while (count--) {
//		if ((radeonfb_getindex(sc, RADEON_MEM_STR_CNTL) &
//			RADEON_MEM_PWRUP_COMPLETE) ==
//		    RADEON_MEM_PWRUP_COMPLETE)
//			break;
//	}
	printf("%d\n", count);
}

void
rb_wait_dll_ready_mask(unsigned char *bios, int count)
{
	printf("WAIT_DLL_READY_MASK: %d \n", count);
//	while (count--) {
//		if (radeonfb_getpll(sc, RADEON_CLK_PWRMGT_CNTL) &
//		    RADEON_DLL_READY_MASK)
//			break;
//	}
	printf("%d\n", count);
}

void
rb_wait_chk_set_clk_pwrmgt_cntl24(unsigned char *bios)
{
//	unsigned int	pmc;
	printf("WAIT CHK_SET_CLK_PWRMGT_CNTL24\n");
#if 0
	pmc = radeonfb_getpll(sc, RADEON_CLK_PWRMGT_CNTL);

	if (pmc & RADEON_CLK_PWRMGT_CNTL24) {
		radeonfb_maskpll(sc, RADEON_MCLK_CNTL, 0xFFFF0000,
		    RADEON_SET_ALL_SRCS_TO_PCI);
		delay(10000);
		radeonfb_putpll(sc, RADEON_CLK_PWRMGT_CNTL,
		    pmc & ~RADEON_CLK_PWRMGT_CNTL24);
		delay(10000);
	}
#endif
}

/*
 * Block initialization routines.  These take action based on data in
 * the tables.
 */
int
rb_load_init_block(unsigned char *bios, struct rb_table *tp)
{
	int	offset;
	int	value;

	if ((tp == NULL) || ((offset = tp->offset) == 0))
		return 1;

	printf("load_init_block processing %s\n", tp->name);
	while ((value = BIOS16(offset)) != 0) {
		int	flag = value & TABLE_ENTRY_FLAG_MASK;
		int	index = value & TABLE_ENTRY_INDEX_MASK;
		int	command = value & TABLE_ENTRY_COMMAND_MASK;
		int	ormask;
		int	andmask;
		int	count;

		offset += 2;

		switch (flag) {
		case TABLE_FLAG_WRITE_INDEXED:
			printf("WRITE INDEXED: 0x%x 0x%x\n",
				    index, (unsigned int)BIOS32(offset));
			//radeonfb_putindex(sc, index, GETBIOS32(sc, offset));
			offset += 4;
			break;

		case TABLE_FLAG_WRITE_DIRECT:
			printf("WRITE DIRECT: 0x%x 0x%x\n",
				    index, (unsigned int)BIOS32(offset));
			//radeonfb_put32(sc, index, GETBIOS32(sc, offset));
			offset += 4;
			break;

		case TABLE_FLAG_MASK_INDEXED:
			andmask = BIOS32(offset);
			offset += 4;
			ormask = BIOS32(offset);
			offset += 4;
			printf("MASK INDEXED: 0x%x 0x%x 0x%x\n",
				    index, andmask, ormask);
			//radeonfb_maskindex(sc, index, andmask, ormask);
			break;

		case TABLE_FLAG_MASK_DIRECT:
			andmask = BIOS32(offset);
			offset += 4;
			ormask = BIOS32(offset);
			offset += 4;
			printf("MASK DIRECT: 0x%x 0x%x 0x%x\n",
				    index, andmask, ormask);
			//radeonfb_mask32(sc, index,  andmask, ormask);
			break;

		case TABLE_FLAG_DELAY:
			/* in the worst case, this would be 16msec */
			count = BIOS16(offset);
			printf("DELAY: %d\n", count);
			//delay(count);
			offset += 2;
			break;

		case TABLE_FLAG_SCOMMAND:
			printf("SCOMMAND 0x%x\n", command); 
			switch (command) {

			case TABLE_SCOMMAND_WAIT_MC_BUSY_MASK:
				count = BIOS16(offset);
				rb_wait_mc_busy_mask(bios, count);
				break;

			case TABLE_SCOMMAND_WAIT_MEM_PWRUP_COMPLETE:
				count = BIOS16(offset);
				rb_wait_mem_pwrup_complete(bios, count);
				break;

			}
			offset += 2;
			break;
		}
	}
	return 0;
}

int
rb_load_pll_block(unsigned char *bios, struct rb_table *tp)
{
	int	offset;
	unsigned char		index;
	unsigned char		shift;
	unsigned int	andmask;
	unsigned int	ormask;

	if ((tp == NULL) || ((offset = tp->offset) == 0))
		return 1;

	printf("load_pll_block processing %s\n", tp->name);
	while ((index = BIOS8(offset)) != 0) {
		offset++;

		switch (index & PLL_FLAG_MASK) {
		case PLL_FLAG_WAIT:
			switch (index & PLL_INDEX_MASK) {
			case PLL_WAIT_150MKS:
				printf("delay: 150 mks\n");
				//delay(150);
				break;
			case PLL_WAIT_5MS:
				/* perhaps this should be tsleep? */
				//delay(5000);
				printf("delay: 5 ms\n");
				break;

			case PLL_WAIT_MC_BUSY_MASK:
				rb_wait_mc_busy_mask(bios, 1000);
				break;

			case PLL_WAIT_DLL_READY_MASK:
				rb_wait_dll_ready_mask(bios, 1000);
				break;

			case PLL_WAIT_CHK_SET_CLK_PWRMGT_CNTL24:
				rb_wait_chk_set_clk_pwrmgt_cntl24(bios);
				break;
			}
			break;
			
		case PLL_FLAG_MASK_BYTE:
			shift = BIOS8(offset) * 8;
			offset++;

			andmask =
			    (((unsigned int)BIOS8(offset)) << shift) |
			    ~((unsigned int)0xff << shift);
			offset++;

			ormask = ((unsigned int)BIOS8(offset)) << shift;
			offset++;

			printf("PLL_MASK_BYTE %u %u 0x%x 0x%x\n", index, 
				    shift, andmask, ormask);
			//radeonfb_maskpll(sc, index, andmask, ormask);
			break;

		case PLL_FLAG_WRITE:
			printf("PLL_WRITE %u 0x%x\n", index,
				    BIOS32(offset));
			//radeonfb_putpll(sc, index, GETBIOS32(sc, offset));
			offset += 4;
			break;
		}
	}

	return 0;
}

int
rb_reset_sdram(unsigned char *bios, struct rb_table *tp)
{
	int offset;
	unsigned char	index;

	if ((tp == NULL) || ((offset = tp->offset) == 0))
		return 1;

	printf("reset_sdram processing %s\n", tp->name);

	while ((index = BIOS8(offset)) != 0xff) {
		offset++;
		if (index == 0x0f) {
			rb_wait_mem_pwrup_complete(bios, 20000);
		} else {
			unsigned int	ormask;

			ormask = BIOS16(offset);
			offset += 2;

			printf("INDEX reg RADEON_MEM_SDRAM_MODE_REG 0x%x 0x%x\n",
				    RADEON_SDRAM_MODE_MASK, ormask);
			//radeonfb_maskindex(sc, RADEON_MEM_SDRAM_MODE_REG,
			//    RADEON_SDRAM_MODE_MASK, ormask);

			ormask = (unsigned int)index << 24;
			printf("INDEX reg RADEON_MEM_SDRAM_MODE_REG 0x%x 0x%x\n",
				    RADEON_B3MEM_RESET_MASK, ormask);
			//radeonfb_maskindex(sc, RADEON_MEM_SDRAM_MODE_REG,
			//    RADEON_B3MEM_RESET_MASK, ormask);
		}
	}
	return 0;
}

/*
 * Master entry point to parse and act on table data.
 */
int
radeonfb_bios_init(const char * file)
{
#define _64K (64*1024)
	unsigned char bios[_64K];
	int fd, atom, hdr;
	int		revision;
	int		scratch;
	int			i;
	struct rb_table		*tp;

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		perror("can't open rom file");
		return 1;
	}
	memset(bios, 0, _64K);
	read(fd, bios, _64K);
	close(fd);

	hdr = BIOS16(0x48);

	scratch = BIOS16(ROM_HEADER_OFFSET);
	revision = BIOS8(scratch);
	printf("Bios Rev: %d\n", revision);

	atom = (BIOS8(hdr+4)   == 'A' &&
		BIOS8(hdr+5) == 'T' &&
		BIOS8(hdr+6) == 'O' &&
		BIOS8(hdr+7) == 'M') ||
		(BIOS8(hdr+4)   == 'M' &&
		 BIOS8(hdr+5) == 'O' &&
		 BIOS8(hdr+6) == 'T' &&
		 BIOS8(hdr+7) == 'A');
	if (atom) {
		perror("atom bios not supported");
		return 1;
	}

	/* First parse pass -- locate tables  */
	for (i = 0; (tp = rb_tables[i]) != NULL; i++) {

		printf("parsing table %s\n", tp->name);

		if (tp->offset != 0) {
			int	temp, offset;;

			temp = BIOS16(ROM_HEADER_OFFSET);
			offset = BIOS16(temp + tp->offset);
			if (offset)
				tp->offset = offset;
			    
		} else {
			tp->offset = tp->find(bios, tp->parent);
		}

		if (tp->validate)
			tp->validate(bios, tp);

		if (revision > SINGLE_TABLE_REVISION)
			break;
	}

	if (rb_rage_regs3_table.offset + 1 == rb_pll_init_table.offset) {
		rb_rage_regs3_table.offset = 0;
		rb_rage_regs4_table.offset = 0;
	}

	if (rb_rage_regs1_table.offset)
		rb_load_init_block(bios, &rb_rage_regs1_table);

	if (revision < SINGLE_TABLE_REVISION) {
		if (rb_pll_init_table.offset)
			rb_load_pll_block(bios, &rb_pll_init_table);
		if (rb_rage_regs2_table.offset)
			rb_load_init_block(bios, &rb_rage_regs2_table);
		if (rb_rage_regs4_table.offset)
			rb_load_init_block(bios, &rb_rage_regs4_table);
		if (rb_mem_reset_table.offset)
			rb_reset_sdram(bios, &rb_mem_reset_table);
		if (rb_rage_regs3_table.offset)
			rb_load_init_block(bios, &rb_rage_regs3_table);
		if (rb_dyn_clock_table.offset)
			rb_load_pll_block(bios, &rb_dyn_clock_table);
	}

	printf("BIOS parse done\n");
	return 0;
}

int main(int argc,char *argv[]) 
{
    if(argc == 2) {
	radeonfb_bios_init(argv[1]);
	return 0;
   }

    return 1;
}


