/*******************************************************************************
Copyright (C) Marvell International Ltd. and its affiliates

This software file (the "File") is owned and distributed by Marvell 
International Ltd. and/or its affiliates ("Marvell") under the following
alternative licensing terms.  Once you have made an election to distribute the
File under one of the following license alternatives, please (i) delete this
introductory statement regarding license alternatives, (ii) delete the two
license alternatives that you have not elected to use and (iii) preserve the
Marvell copyright notice above.

********************************************************************************
Marvell Commercial License Option

If you received this File from Marvell and you have entered into a commercial
license agreement (a "Commercial License") with Marvell, the File is licensed
to you under the terms of the applicable Commercial License.

********************************************************************************
Marvell GPL License Option

If you received this File from Marvell, you may opt to use, redistribute and/or 
modify this File in accordance with the terms and conditions of the General 
Public License Version 2, June 1991 (the "GPL License"), a copy of which is 
available along with the File in the license.txt file or by writing to the Free 
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 or 
on the worldwide web at http://www.gnu.org/licenses/gpl.txt. 

THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED 
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY 
DISCLAIMED.  The GPL License provides additional details about this warranty 
disclaimer.
********************************************************************************/

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <linux/version.h>
#include "mvSysHwConfig.h"
#include "boardEnv/mvBoardEnvLib.h"
#include "ctrlEnv/sys/mvCpuIf.h"
#include "ctrlEnv/mvUnitMap.h"

#define MTD_FLASH_MAP_DEBUG

#ifdef MTD_FLASH_MAP_DEBUG
#define DB(x)	x
#else
#define DB(x)
#endif

#define MTD_MAX_FLASH_NUMBER	4
#define MTD_DUMMY_BANK_WIDTH	2

struct maps_init_info
{
	struct map_info mapInfo;
	char ** mtdDrv;
	struct mtd_info * mtdInfo;
	char name[32];
};

static struct maps_init_info maps[MTD_MAX_FLASH_NUMBER];
static unsigned int mapsNum = 0;

#if defined (CONFIG_MTD_CFI) || defined (CONFIG_MTD_JEDECPROBE)
static char * cfiDev = "cfi_flash";
static char * cfiMtdList[] = { "cfi_probe", NULL };
#endif

#ifdef CONFIG_MV_INCLUDE_SFLASH_MTD
static char * sflashDev = "spi_flash";
static char * sflashMtdList[] = {"sflash", NULL};
#endif

#ifdef CONFIG_MTD_PARTITIONS
static struct mtd_partition *mtd_parts;
static int                   mtd_parts_nb;
static const char *part_probes[] __initdata = {"cmdlinepart", NULL};
#endif /* CONFIG_MTD_PARTITIONS */

static int flashInfoFill(void)
{
	int expectedDevs = 0;
	int devs, i;

	/* clear the whole array */
	memset((void*)maps, 0x0, sizeof(maps));

#if defined (CONFIG_MTD_CFI) || defined (CONFIG_MTD_JEDECPROBE)
	/* gather the CFI and JEDEC NOR flash devices information */
	devs = mvBoardGetDevicesNumber(BOARD_DEV_NOR_FLASH);

	for(i=0; i<devs; i++) {
		if (expectedDevs >= MTD_MAX_FLASH_NUMBER) {
			printk(KERN_NOTICE "\nERROR: %s - Exceeded MAX MTD flash devices number", __FUNCTION__);
			break;
		}
		maps[expectedDevs].mtdDrv = cfiMtdList;	
		sprintf(maps[expectedDevs].name, "%s_%d", cfiDev, i);
		maps[expectedDevs].mapInfo.name = maps[expectedDevs].name;
		maps[expectedDevs].mapInfo.phys = mvBoardGetDeviceBaseAddr(i, BOARD_DEV_NOR_FLASH);
		maps[expectedDevs].mapInfo.size = mvBoardGetDeviceWinSize(i, BOARD_DEV_NOR_FLASH);
		maps[expectedDevs].mapInfo.bankwidth = (mvBoardGetDeviceBusWidth(i, BOARD_DEV_NOR_FLASH) / 8);
		
		if ((maps[expectedDevs].mapInfo.phys != 0xFFFFFFFF) && 
		    (maps[expectedDevs].mapInfo.size != 0xFFFFFFFF)) {
			DB(printk("\nINFO: Found %s %d - base 0x%08x, size 0x%x, bus %d", maps[expectedDevs].mapInfo.name, i,
			   (unsigned int)maps[expectedDevs].mapInfo.phys, (unsigned int)maps[expectedDevs].mapInfo.size, maps[expectedDevs].mapInfo.bankwidth));
			++expectedDevs;
		} else {
			printk(KERN_NOTICE "\nERROR: %s - Failed to get Device Base address and Size (%s %d)", __FUNCTION__, maps[expectedDevs].mapInfo.name, i);
		}
	}
#endif
	
#ifdef CONFIG_MV_INCLUDE_SFLASH_MTD
	/* gather the SPI flash devices information */
	devs = mvBoardGetDevicesNumber(BOARD_DEV_SPI_FLASH);

	for(i=0; i<devs; i++) {
		if (expectedDevs >= MTD_MAX_FLASH_NUMBER) {
			printk(KERN_NOTICE "\nERROR: %s - Exceeded MAX MTD flash devices number", __FUNCTION__);
			break;
		}
		maps[expectedDevs].mtdDrv = sflashMtdList;
		maps[expectedDevs].mapInfo.name = sflashDev;
		maps[expectedDevs].mapInfo.phys = mvBoardGetDeviceBaseAddr(i, BOARD_DEV_SPI_FLASH);
		maps[expectedDevs].mapInfo.size = mvBoardGetDeviceWinSize(i, BOARD_DEV_SPI_FLASH);
		maps[expectedDevs].mapInfo.bankwidth = MTD_DUMMY_BANK_WIDTH;

		if ((maps[expectedDevs].mapInfo.phys != 0xFFFFFFFF) && 
		    (maps[expectedDevs].mapInfo.size != 0xFFFFFFFF)) {
			DB(printk("\nINFO: Found %s %d - base 0x%08x, size 0x%x", maps[expectedDevs].mapInfo.name, i,
						(unsigned int)maps[expectedDevs].mapInfo.phys,
						(unsigned int)maps[expectedDevs].mapInfo.size));
			++expectedDevs;
		} else {
			printk(KERN_NOTICE "\nERROR: %s - Failed to get Device Base address and Size (%s %d)",
					__FUNCTION__, maps[expectedDevs].mapInfo.name, i);
		}
	}
#endif

	DB(printk("\nINFO: %s - Found %d Flash Devices", __FUNCTION__, expectedDevs));
	return expectedDevs;
}

static int __init flashProbe(char ** mtdDrv, struct map_info * map, struct mtd_info ** mtd)
{
	if ((mtdDrv == NULL) || (map == NULL) || (mtd == NULL)) {
		printk(KERN_NOTICE "\nERROR: NULL pointer parameter at %s entry", __FUNCTION__);
		return -EINVAL;
	}

	/* remap the physical address to a virtual address */
	map->virt = ioremap(map->phys, map->size);
	if (!map->virt) {
		printk(KERN_NOTICE "\nERROR: Failed to ioremap Flash device at physical base 0x%x.", (unsigned int)map->phys);
		return -EIO;
	}
	
	DB(printk("\nINFO: Io remapped successfully - phy addr = 0x%08x, virt addr = 0x%08x",
				(unsigned int)map->phys, (unsigned int)map->virt));

	simple_map_init(map);

	*mtd = NULL;
	for(; (!(*mtd) && *mtdDrv); mtdDrv++) {
		DB(printk("\nINFO: Using %s to probe %s at address 0x%08x, size 0x%x, width %dm",
					*mtdDrv, map->name, (unsigned int)map->phys,
					(unsigned int)map->size, map->bankwidth));
		if ((*mtd = do_map_probe(*mtdDrv, map))) {
			DB(printk(" - detected OK"));
			/*map->size = (*mtd)->size;*/
			(*mtd)->owner = THIS_MODULE;

#ifdef CONFIG_MTD_PARTITIONS
			mtd_parts_nb = parse_mtd_partitions(*mtd, part_probes, &mtd_parts, 0);

			if (mtd_parts_nb > 0) {
				add_mtd_partitions (*mtd, mtd_parts, mtd_parts_nb);
				return 0;
			}
#endif

			if (/*add_mtd_device(*mtd) != 0*/mtd_device_register(*mtd, NULL,0)){
				printk(KERN_NOTICE "\nERROR: %s - Failed to add the mtd device", __FUNCTION__);
				iounmap((void *)map->virt);
				map->virt = 0;
				return -ENXIO;
			}

			return 0;
		} else {
			DB(printk(" - Not detected"));
		}
	}
 
	iounmap((void *)map->virt);
	map->virt = 0;
	return -ENXIO;
}
	
static int __init flash_map_init(void)
{	
	int i;

	if(mvUnitMapIsMine(SPI) == MV_FALSE)
		return 0;

	mapsNum = flashInfoFill();
	DB(printk("\nINFO: flash_map_init - detected %d devices\n",  mapsNum));

	for (i=0; i<mapsNum; i++) {
		DB(printk("MTD: Initialize the %s device at address 0x%08x\n", maps[i].mapInfo.name, (unsigned int)maps[i].mapInfo.phys));
		if (flashProbe(maps[i].mtdDrv, &maps[i].mapInfo, &maps[i].mtdInfo) == 0) {
			DB(printk(" - OK.\n"));
		} else {
			maps[i].mtdInfo = NULL;
			DB(printk(" - FAILED!\n"));
		}
	}

	return 0;
}

static void __exit flash_map_exit(void)
{
	int i;

	for (i=0; i<mapsNum; i++) {
		if (maps[i].mtdInfo) {
			//del_mtd_device(maps[i].mtdInfo);
			  mtd_device_unregister(maps[i].mtdInfo);
			map_destroy(maps[i].mtdInfo);
		}

		if (maps[i].mapInfo.virt) {
			iounmap((void *)maps[i].mapInfo.virt);
			maps[i].mapInfo.virt = 0;
		}
	}
}

module_init(flash_map_init);
module_exit(flash_map_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MTD map driver for Marvell platforms");
