/**********************************************
 * pcmcia.c 
 *
 * Author: Matthew Millman
 *
 * Written from the PC Card '95 spec
 *
 * After writing this driver I find a chapter which explains
 * how the code should be written (functions, etc)
 *
 * whoops..... oh well.
 */

#include <common.h>

/**********************************************
 * Macros
 */

#define CISTPL_VERS_1		0x15
#define CISTPL_CONFIG		0x1A
#define CISTPL_CFTABLE_ENTRY	0x1B
#define CISTPL_END		0xFF

#define TPCE_FS_POWER		0x03
#define TPCE_FS_TIMING		0x04
#define TPCE_FS_IO		0x08
#define TPCE_FS_IRQ		0x10
#define TPCE_FS_MEM		0x60
#define TPCE_FS_MISC		0x80

#define TPCE_INDX_INT		0x80
#define TPCE_INDX_DFL		0x40

#define TPCE_TD_WAIT		0x03
#define TPCE_TD_RDY		0x1C

/**********************************************
 * Globals
 */

u16 cfgreg_offset;

/**********************************************
 * Structures
 */

typedef struct 
{
	u8 tpcc_sz;
	u8 tpcc_last;
	u8 tpcc_radr_l;
	u8 tpcc_radr_h;
	u8 tpcc_rmsk;
} cistpl_config;

typedef struct
{
	u8 tpce_indx;
	u8 tpce_if;
	u8 tpce_fs;
} cistpl_cftable_entry_hdr;

typedef struct
{
	u8 tpll_v1_major;
	u8 tpll_v1_minor;
} cistpl_vers_1;

/**********************************************
 * Function prototypes
 */
 
void pcmcia_at8948_parse_power(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);
void pcmcia_at8948_parse_timing(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);
void pcmcia_at8948_parse_io(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);
void pcmcia_at8948_parse_irq(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);
void pcmcia_at8948_parse_mem(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);
void pcmcia_at8948_parse_misc(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset);

		
/**********************************************
 * pcmcia_at8948_attrib_read()
 *
 * Author: Matthew Millman
 *
 * Read data from the attribute memory - accounts for the fact
 * that we use 8-Bit mode
 */

void pcmcia_at8948_attrib_read(void *data, u32 *offset, u32 len)
{
	int	i;
	
	i = 0;
	
	while (len--)
	{
		*(u8 *)(data + i++)  = *((u8 *)CFG_PCMCIA_ATTRIB_ADDR + (*offset * 2));
		//printf("data: 0x%02X 0x%02X addr 0x%08X offset %u\n", *(u8 *)data, *((u8 *)CFG_PCMCIA_ATTRIB_ADDR + (*offset * 2)), ((u8 *)CFG_PCMCIA_ATTRIB_ADDR + (*offset * 2)), *offset);
		(*offset)++;
		//printf("*** offset: %u\n", *offset);
	}
}

/**********************************************
 * pcmcia_at8948_read_string()
 *
 * Author: Matthew Millman
 *
 * Read a string from a tuple
 */

int pcmcia_at8948_read_string(char *data, u32 *offset, u32 max)
{
	int 	i;
	char	read_data;
	
	i = 0;
	
	do
	{
		if ((i + 1) >= max)
		{
			*(char *)(data + i++) = 0;
			return i;
		}
		
		pcmcia_at8948_attrib_read(&read_data, offset, 1);
		
		if (read_data == 0xFF)
		{
			return 0;
		}
		
		*(char *)(data + i++)  = read_data;
		
	} while (read_data);
	
	return i;
		
}

/**********************************************
 * pcmcia_at8948_init()
 *
 * Author: Matthew Millman
 *
 * Start the PCMCIA identification process
 */

void pcmcia_at8948_init(void)
{
	u8 tuple_type;
	u32 offset;
	int i;
	
	offset = 0;
	i = 0;
	
	pcmcia_at8948_attrib_read(&tuple_type, &offset, 1);
	
	while (tuple_type != CISTPL_END && i < 100)
	{	
		u8 				next_offset;
		u8				iospace_attr;
		u8				irq_attr;
		u8				memspace_attr;
		cistpl_config 			config;
		cistpl_cftable_entry_hdr	cfg_entry_hdr;
		cistpl_vers_1			vers_1;
		char				buf[128];
		
		printf("Tuple: 0x%02X @ 0x%04X\n", tuple_type, (offset - 1) * 2);
		printf("---------------------------------------------------\n");
		
		pcmcia_at8948_attrib_read(&next_offset, &offset, 1);
		
		switch (tuple_type)
		{
			case CISTPL_CONFIG: 
				pcmcia_at8948_attrib_read(&config, &offset, sizeof(config));
				cfgreg_offset = (u16)((config.tpcc_radr_l & 0xFF) | config.tpcc_radr_h << 8);
				
				printf("Tuple Type: Config\n");
				
				printf("  Config register size: 0x%02X\n", config.tpcc_sz);
				printf("  Config registers offset: 0x%04X\n", cfgreg_offset);
				printf("  ...... Absolute address: 0x%08X\n", (u32)CFG_PCMCIA_ATTRIB_ADDR + (u16)cfgreg_offset);
				printf("  Config register mask: 0x%02X\n", config.tpcc_rmsk);
				
				break;
			case CISTPL_CFTABLE_ENTRY:
				pcmcia_at8948_attrib_read(&cfg_entry_hdr.tpce_indx, &offset, 1);
			
				printf("Tuple Type: Configuration table entry\n");
				
				printf("  Configuration identifier: 0x%02X\n", (cfg_entry_hdr.tpce_indx & 0x3F));
				
				if (cfg_entry_hdr.tpce_indx & TPCE_INDX_DFL)
				{
					printf("  Contains default values\n");
				}	
				
				if (cfg_entry_hdr.tpce_indx & TPCE_INDX_INT)
				{
					printf("  Has IF Field\n");
					pcmcia_at8948_attrib_read(&cfg_entry_hdr.tpce_if, &offset, 1);
				}	
							
				pcmcia_at8948_attrib_read(&cfg_entry_hdr.tpce_fs, &offset, 1);
				
				//printf("cfg_entry_hdr.tpce_fs: 0x%02X\n", cfg_entry_hdr.tpce_fs);
				
				if (cfg_entry_hdr.tpce_fs & TPCE_FS_POWER)
				{
					printf("  Has power field\n");
					pcmcia_at8948_parse_power(&cfg_entry_hdr, &offset);
				}

				if (cfg_entry_hdr.tpce_fs & TPCE_FS_TIMING)
				{
					printf("  Has timing field\n");
					pcmcia_at8948_parse_timing(&cfg_entry_hdr, &offset);
				}
		
				if (cfg_entry_hdr.tpce_fs & TPCE_FS_IO)
				{
					printf("  Has IO field\n");
					pcmcia_at8948_parse_io(&cfg_entry_hdr, &offset);
				}
			
				if (cfg_entry_hdr.tpce_fs & TPCE_FS_IRQ)
				{
					printf("  Has IRQ field\n");
					pcmcia_at8948_parse_irq(&cfg_entry_hdr, &offset);
				}
				
				if (cfg_entry_hdr.tpce_fs & TPCE_FS_MEM) /* Bit 5 or 6 */
				{
					printf("  Has mem field (bad!)\n");
					return;
					pcmcia_at8948_parse_irq(&cfg_entry_hdr, &offset);
				}
				
				if (cfg_entry_hdr.tpce_fs & TPCE_FS_MISC)
				{
					pcmcia_at8948_parse_misc(&cfg_entry_hdr, &offset);
				}
				break;
			case CISTPL_VERS_1:
				pcmcia_at8948_attrib_read(&vers_1, &offset, sizeof(vers_1));
				printf("Tuple Type: Version (1)\n");
				printf("  Info: ");
				
				while (pcmcia_at8948_read_string(buf, &offset, sizeof(buf)))
				{
					printf("%s ", buf);
				}
				printf("\n");
				
				printf("  Version: %u.%u\n", vers_1.tpll_v1_major, vers_1.tpll_v1_minor);
				
				break;
			default:
				offset += next_offset;
				break;		
		}
		printf("\n\n");
		//offset++;
		
		//printf("jump: 0x%02X\n", next_offset);
		
		pcmcia_at8948_attrib_read(&tuple_type, &offset, 1);
		
		i++;
		
	}
}

/**********************************************
 * pcmcia_at8948_parse_power()
 *
 * Author: Matthew Millman
 *
 * We don't even need the power information but it still has
 * To be parsed in order to get the other information out of the tuple
 */
 
void pcmcia_at8948_parse_power(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	u8 power_def;
	u8 num_loops;
	u8 curr_loop;
	
	if ((cfg_entry_hdr->tpce_fs & TPCE_FS_POWER) == 0x1)
	{
		num_loops = 1;
	}
	else if ((cfg_entry_hdr->tpce_fs & TPCE_FS_POWER) == 0x2)
	{
		num_loops = 2;
	}
	else if ((cfg_entry_hdr->tpce_fs & TPCE_FS_POWER) == 0x3)
	{
		num_loops = 3;
	}
		
	for (curr_loop = 0; curr_loop < num_loops; curr_loop++)
	{
		u8 b;
		u8 i;
		u8 power_def_param;
		
		pcmcia_at8948_attrib_read(&power_def, offset, 1);
		//printf("power_def: 0x%02X\n", power_def);
		
		for (b = power_def, i = 0; i < 8; i++, b >>= 1)
		{
			if (b & 1)
			{
				//printf("power_def loop %u\n", (b & 1));
			
				pcmcia_at8948_attrib_read(&power_def_param, offset, 1);
			
				while (power_def_param & 0x80) /* EXT */
				{
					//printf("extra power info\n");
					pcmcia_at8948_attrib_read(&power_def_param, offset, 1);
				}
			}
		}
	}
}

/**********************************************
 * pcmcia_at8948_parse_timing()
 *
 * Author: Matthew Millman
 *
 */
 
void pcmcia_at8948_parse_timing(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	u8 timing_def;
	u8 tmp;
	
	pcmcia_at8948_attrib_read(&timing_def, offset, 1);
	
	//printf("timing exists: 0x%02X\n", timing_def);
	
	if ((timing_def & TPCE_TD_WAIT) != TPCE_TD_WAIT)
	{
		//printf("\twait existed\n");
		pcmcia_at8948_attrib_read(&tmp, offset, 1); /* WAIT Exists, swallow it */
	}
	if ((timing_def & TPCE_TD_RDY) != TPCE_TD_RDY)
	{
		//printf("\tready existed\n");
		pcmcia_at8948_attrib_read(&tmp, offset, 1); /* READY Exists, swallow it */
	}
	
}

/**********************************************
 * pcmcia_at8948_parse_io()
 *
 * Author: Matthew Millman
 *
 */
 
void pcmcia_at8948_parse_io(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	u8 io_def;
	u8 io_def_range;
	u8 i;
	u8 j;
	u8 num_ranges;
	
	u8 length_size;
	u8 addr_size;
	
	pcmcia_at8948_attrib_read(&io_def, offset, 1);
	
	printf("    IO Definition byte: 0x%02X\n", io_def);
	
	if (io_def & 0x80) /* IO Range */
	{
		u32 descriptor_addr;
		u32 descriptor_len;
		u8 u8_tmp;
		u32 u32_tmp;
		
		descriptor_addr = 0;
		descriptor_len = 0;
		
		pcmcia_at8948_attrib_read(&io_def_range, offset, 1);
		
		num_ranges = (io_def_range & 0x0F);
		num_ranges++; /* 0000 Bitpattern = 1 range */
		
		addr_size = ((io_def_range >> 4) & 0x03);
		length_size = ((io_def_range >> 6) & 0x03);
		
		for (i = 0; i < num_ranges; i++)
		{
			for (j = 0; j < addr_size; j++)
			{
				pcmcia_at8948_attrib_read(&u8_tmp, offset, 1);
				//printf("addr: 0x%02X\n", u8_tmp);
				u32_tmp = u8_tmp;
				u32_tmp <<= (8 * j);
				descriptor_addr |= u32_tmp;
			}
			for (j = 0; j < length_size; j++)
			{
				pcmcia_at8948_attrib_read(&u8_tmp, offset, 1);
				//printf("len: 0x%02X\n", u8_tmp);
				u32_tmp = u8_tmp;
				u32_tmp <<= (8 * j);
				descriptor_len |= u32_tmp;
			}
			
			printf("    IO Addr:   0x%08X\n", descriptor_addr);
			printf("    IO Length: 0x%08X\n", descriptor_len);
		}
	}
	
}

/**********************************************
 * pcmcia_at8948_parse_irq()
 *
 * Author: Matthew Millman
 *
 */
 
void pcmcia_at8948_parse_irq(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	u8 irq_def;
	u8 irqs_1;
	u8 irqs_2;
	
	pcmcia_at8948_attrib_read(&irq_def, offset, 1);
	
	if (irq_def & 0x10) /* Support multiple interrupts */
	{
		pcmcia_at8948_attrib_read(&irqs_1, offset, 1);
		pcmcia_at8948_attrib_read(&irqs_2, offset, 1);
		
		printf("    Supports multiple IRQs: 0=0x%2X 1=0x%02X 2=0x%02X\n", irq_def, irqs_1, irqs_2);
	}
	else
		printf("    Supports single IRQ: 0=0x%2X\n", irq_def);
}

/**********************************************
 * pcmcia_at8948_parse_mem()
 *
 * Author: Matthew Millman
 *
 */
 
void pcmcia_at8948_parse_mem(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	
}

/**********************************************
 * pcmcia_at8948_parse_misc()
 *
 * Author: Matthew Millman
 *
 */
 
void pcmcia_at8948_parse_misc(cistpl_cftable_entry_hdr *cfg_entry_hdr, u32 *offset)
{
	
}