/*
 * $Id: cfi.c,v 1.6 2003/08/28 06:18:45 telka Exp $
 *
 * Copyright (C) 2002, 2003 ETC s.r.o.
 *
 * 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.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * Written by Marcel Telka <marcel@telka.sk>, 2002, 2003.
 *
 * Documentation:
 * [1] JEDEC Solid State Technology Association, "Common Flash Interface (CFI)",
 *     September 1999, Order Number: JESD68
 * [2] Intel Corporation, "Common Flash Interface (CFI) and Command Sets
 *     Application Note 646", April 2000, Order Number: 292204-004
 *
 */

#include "sysdep.h"

#include <stdint.h>
#include <stdlib.h>
#include <flash/cfi.h>

#include <brux/cfi.h>
#include <brux/bus.h>

void
cfi_array_free( cfi_array_t *cfi_array )
{
	if (!cfi_array)
		return;

	if (cfi_array->cfi_chips) {
		int i;

		for (i = 0; i < cfi_array->bus_width; i++) {
			if (!cfi_array->cfi_chips[i])
				continue;

			free( cfi_array->cfi_chips[i]->cfi.device_geometry.erase_block_regions );
			free( cfi_array->cfi_chips[i] );
		}
		free( cfi_array->cfi_chips );
	}

	free( cfi_array );
}

int
cfi_detect( bus_t *bus, uint32_t adr, cfi_array_t **cfi_array , int isbottom )
{
	unsigned int bw;		/* bus width */
	unsigned int d;			/* data offset */
	int ba;				/* bus width address multiplier */
	int ma;				/* flash mode address multiplier */
	bus_area_t area;

	if (!cfi_array || !bus)
		return -1;		/* invalid parameters */

	*cfi_array = calloc( 1, sizeof (cfi_array_t) );
	if (!*cfi_array)
		return -2;		/* out of memory */

	(*cfi_array)->bus = bus;
	(*cfi_array)->address = adr;
	if (bus_area( bus, adr, &area ) != 0)
		return -8;		/* bus width detection failed */
	bw = area.width;
	if (bw != 8 && bw != 16 && bw != 32)
		return -3;		/* invalid bus width */
	(*cfi_array)->bus_width = ba = bw / 8;
	(*cfi_array)->cfi_chips = calloc( ba, sizeof (cfi_chip_t *) );
	if (!(*cfi_array)->cfi_chips)
		return -2;		/* out of memory */

	for (d = 0; d < bw; d += 8) {
#define	A(off)			(adr + (off) * ba * ma)
#define	D(data)			((data) << d)
#define	gD(data)		(((data) >> d) & 0xFF)
#define	read1(off)		gD(bus_read( bus, A(off) ))
#define	read2(off)		(bus_read_start( bus, A(off) ), gD(bus_read_next( bus, A((off) + 1) )) | gD(bus_read_end( bus )) << 8)
#define	write1(off,data)	bus_write( bus, A(off), D(data) )

		cfi_query_structure_t *cfi;
		uint32_t tmp;
		int ret = -4;		/* CFI not detected (Q) */

		/* detect CFI capable devices - see Table 1 in [1] */
		for (ma = 1; ma <= 4; ma *= 2) {
			write1( CFI_CMD_QUERY_OFFSET, CFI_CMD_QUERY );

			if (read1(CFI_QUERY_ID_OFFSET) == 'Q') {
				ret = -5;	/* CFI not detected (R) */
				if (read1(CFI_QUERY_ID_OFFSET + 1) == 'R')
					break;
			}

			write1( 0, CFI_CMD_READ_ARRAY1 );
		}

		if (ma > 4)
			return ret;	/* CFI not detected (Q or R) */

		if (read1(CFI_QUERY_ID_OFFSET + 2) != 'Y') {
			write1( 0, CFI_CMD_READ_ARRAY1 );
			return -6;	/* CFI not detected (Y) */
		}

		(*cfi_array)->cfi_chips[d / 8] = calloc( 1, sizeof (cfi_chip_t) );
		if (!(*cfi_array)->cfi_chips[d / 8]) {
			write1( 0, CFI_CMD_READ_ARRAY1 );
			return -2;	/* out of memory */
		}
		cfi = &(*cfi_array)->cfi_chips[d / 8]->cfi;

		/* Identification string - see Table 6 in [1] */
		cfi->identification_string.pri_id_code = read2(PRI_VENDOR_ID_OFFSET);
		cfi->identification_string.pri_vendor_tbl = NULL;
		cfi->identification_string.alt_id_code = read2(ALT_VENDOR_ID_OFFSET);
		cfi->identification_string.alt_vendor_tbl = NULL;

		/* System interface information - see Table 7 in [1] */
		tmp = read1(VCC_MIN_WEV_OFFSET);
		cfi->system_interface_info.vcc_min_wev = ((tmp >> 4) & 0xF) * 1000 + (tmp & 0xF) * 100;
		tmp = read1(VCC_MAX_WEV_OFFSET);
		cfi->system_interface_info.vcc_max_wev = ((tmp >> 4) & 0xF) * 1000 + (tmp & 0xF) * 100;
		tmp = read1(VPP_MIN_WEV_OFFSET);
		cfi->system_interface_info.vpp_min_wev = ((tmp >> 4) & 0xF) * 1000 + (tmp & 0xF) * 100;
		tmp = read1(VPP_MAX_WEV_OFFSET);
		cfi->system_interface_info.vpp_max_wev = ((tmp >> 4) & 0xF) * 1000 + (tmp & 0xF) * 100;

		/* TODO: Add out of range checks for timeouts */
		tmp = read1(TYP_SINGLE_WRITE_TIMEOUT_OFFSET);
		cfi->system_interface_info.typ_single_write_timeout = tmp ? (1 << tmp) : 0;

		tmp = read1(TYP_BUFFER_WRITE_TIMEOUT_OFFSET);
		cfi->system_interface_info.typ_buffer_write_timeout = tmp ? (1 << tmp) : 0;

		tmp = read1(TYP_BLOCK_ERASE_TIMEOUT_OFFSET);
		cfi->system_interface_info.typ_block_erase_timeout = tmp ? (1 << tmp) : 0;

		tmp = read1(TYP_CHIP_ERASE_TIMEOUT_OFFSET);
		cfi->system_interface_info.typ_chip_erase_timeout = tmp ? (1 << tmp) : 0;

		tmp = read1(MAX_SINGLE_WRITE_TIMEOUT_OFFSET);
		cfi->system_interface_info.max_single_write_timeout =
				(tmp ? (1 << tmp) : 0) * cfi->system_interface_info.typ_single_write_timeout;

		tmp = read1(MAX_BUFFER_WRITE_TIMEOUT_OFFSET);
		cfi->system_interface_info.max_buffer_write_timeout =
				(tmp ? (1 << tmp) : 0) * cfi->system_interface_info.typ_buffer_write_timeout;

		tmp = read1(MAX_BLOCK_ERASE_TIMEOUT_OFFSET);
		cfi->system_interface_info.max_block_erase_timeout =
				(tmp ? (1 << tmp) : 0) * cfi->system_interface_info.typ_block_erase_timeout;

		tmp = read1(MAX_CHIP_ERASE_TIMEOUT_OFFSET);
		cfi->system_interface_info.max_chip_erase_timeout =
				(tmp ? (1 << tmp) : 0) * cfi->system_interface_info.typ_chip_erase_timeout;

		/* Device geometry - see Table 8 in [1] */
		/* TODO: Add out of range check */
		cfi->device_geometry.device_size = 1 << read1(DEVICE_SIZE_OFFSET);

		cfi->device_geometry.device_interface = read2(FLASH_DEVICE_INTERFACE_OFFSET);

		/* TODO: Add out of range check */
		cfi->device_geometry.max_bytes_write = 1 << read2(MAX_BYTES_WRITE_OFFSET);

		tmp = cfi->device_geometry.number_of_erase_regions = read1(NUMBER_OF_ERASE_REGIONS_OFFSET);

		cfi->device_geometry.erase_block_regions = malloc( tmp * sizeof (cfi_erase_block_region_t) );
		if (!cfi->device_geometry.erase_block_regions) {
			write1( 0, CFI_CMD_READ_ARRAY1 );
			return -2;	/* out of memory */
		}
		
		{
			int a;
			int i;

			for (i = 0, a = ERASE_BLOCK_REGION_OFFSET; i < tmp; i++, a += 4) {
				uint32_t y = read2(a);
				uint32_t z = read2(a + 2) << 8;
				if (z == 0)
					z = 128;
/* isbottom variable means is it needed to reverse regions. AMD/Fugitsu
   flash chips uses "bottom boot block" and "top boot block" types of the same
   flash chip. boot block means region with blocks smaller than main region.
   CFI has no ability to supply what type are used. So, user must choose
   is it needed to swap regions.
 */

				if (isbottom == 1)
				{
					cfi->device_geometry.erase_block_regions[tmp-i-1].erase_block_size = z;
					cfi->device_geometry.erase_block_regions[tmp-i-1].number_of_erase_blocks = y + 1;
				} else
				{
					cfi->device_geometry.erase_block_regions[i].erase_block_size = z;
					cfi->device_geometry.erase_block_regions[i].number_of_erase_blocks = y + 1;
				}
			}
		}

		/* TODO: Intel Primary Algorithm Extended Query Table - see Table 5. in [2] */

		/* Read Array */
		write1( 0, CFI_CMD_READ_ARRAY1 );

#undef A
#undef D
#undef gD
#undef read1
#undef read2
#undef write1

		switch (cfi->device_geometry.device_interface) {
			case CFI_INTERFACE_X8:
				if (ma != 1)
					return -7;		/* error in device detection */
				(*cfi_array)->cfi_chips[d / 8]->width = 1;
				break;
			case CFI_INTERFACE_X16:
				if (ma != 1)
					return -7;		/* error in device detection */
				(*cfi_array)->cfi_chips[d / 8]->width = 2;
				d += 8;
				break;
			case CFI_INTERFACE_X8_X16:
				if (ma != 1 && ma != 2)
					return -7;		/* error in device detection */
				(*cfi_array)->cfi_chips[d / 8]->width = 2 / ma;
				if (ma == 1)
					d += 8;
				break;
			case CFI_INTERFACE_X32:
				if (ma != 1)
					return -7;		/* error in device detection */
				(*cfi_array)->cfi_chips[d / 8]->width = 4;
				d += 24;
				break;
			case CFI_INTERFACE_X16_X32:
				if (ma != 1 && ma != 2)
					return -7;		/* error in device detection */
				(*cfi_array)->cfi_chips[d / 8]->width = 4 / ma;
				if (ma == 1)
					d += 24;
				else
					d += 8;
				break;
			default:
				return -7;		/* error in device detection */
		}
	}

	return 0;
}
