/*
 * $Id: avalanche-flash.c,v 1.1.1.1.6.1 2003/09/03 18:13:29 jaa Exp $
 *
 * Normal mappings of chips in physical memory
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/config.h>
#include <linux/mtd/partitions.h>
#include <asm/avalanche/avalanche_map.h>

#define WINDOW_ADDR CONFIG_MTD_AVALANCHE_START
#define WINDOW_SIZE CONFIG_MTD_AVALANCHE_LEN
#define BUSWIDTH CONFIG_MTD_AVALANCHE_BUSWIDTH

#define AVALANCHE_RAM_START           0x14000000


#define NUM_OF_MTD_DEVICES  2
/*--- #define NUM_OF_MTD_DEVICES  1 ---*/

/* avalanche__partition_info gives details on the logical partitions that splits
 * the flash device into. If the size if zero we use up to the end of
 * the device. */
#define MAX_NUM_PARTITIONS  CONFIG_MTD_AVALANCHE_PARTITIONS


#include <asm/mips-boards/prom.h>

struct avalanche_chip_info
{
    char *name;
    int addr;
    int size;
    int buswidth;
};

struct avalanche_mtd
{
    struct avalanche_chip_info *avalanche_chip_info;
    struct mtd_partition avalanche_partition_info[MAX_NUM_PARTITIONS + 2];
    struct mtd_info *avalanche_mtd_info; 
    struct map_info avalanche_map;
    int num_of_partitions;
};


extern char *prom_getenv(char *name);

static int create_mtd_partitions(void);
static void __exit avalanche_mtd_cleanup(void);
#if NUM_OF_MTD_DEVICES == 2
static int create_mtdram_partitions(char *, int, int, int);
#endif /*--- #if NUM_OF_MTD_DEVICES == 2 ---*/
	



struct avalanche_chip_info avalanche_chip_info[NUM_OF_MTD_DEVICES] = {   
        { "Physically mapped flash", WINDOW_ADDR, WINDOW_SIZE, BUSWIDTH }
#if NUM_OF_MTD_DEVICES == 2
      , { "Physically mapped ram", AVALANCHE_RAM_START, WINDOW_SIZE, BUSWIDTH }
#endif /*--- #if NUM_OF_MTD_DEVICES == 2 ---*/
};                                                                  



struct avalanche_mtd avalanche_mtd[NUM_OF_MTD_DEVICES];


int avalanche_mtd_ready=0;

__u8 avalanche_read8(struct map_info *map, unsigned long ofs)
{
	return __raw_readb(map->map_priv_1 + ofs);
}

__u16 avalanche_read16(struct map_info *map, unsigned long ofs)
{
	return __raw_readw(map->map_priv_1 + ofs);
}

__u32 avalanche_read32(struct map_info *map, unsigned long ofs)
{
	return __raw_readl(map->map_priv_1 + ofs);
}

void avalanche_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
{
	memcpy_fromio(to, map->map_priv_1 + from, len);
}

void avalanche_write8(struct map_info *map, __u8 d, unsigned long adr)
{
	__raw_writeb(d, map->map_priv_1 + adr);
	mb();
}

void avalanche_write16(struct map_info *map, __u16 d, unsigned long adr)
{
	__raw_writew(d, map->map_priv_1 + adr);
	mb();
}

void avalanche_write32(struct map_info *map, __u32 d, unsigned long adr)
{
	__raw_writel(d, map->map_priv_1 + adr);
	mb();
}

void avalanche_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
{
	memcpy_toio(map->map_priv_1 + to, from, len);
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int avalanche_flash_init(struct avalanche_mtd *dev, int is_flash)
{
	struct avalanche_chip_info *chip_info = dev->avalanche_chip_info;
	struct map_info * avalanche_map = &dev->avalanche_map;
	struct mtd_info * avalanche_mtd_info;
    char *env_name = is_flash ? "flashsize" : "memsize";
    char *name = is_flash ? "flash" : "ram";
    char *probe_name = is_flash ? "cfi_probe" : "map_ram";
    char *flash_size_str;
    int flashsize = 0;

    flash_size_str = prom_getenv(env_name);
    if(flash_size_str) {
        flashsize = simple_strtol(flash_size_str, NULL, 0);
        chip_info->size = flashsize;
        printk(KERN_NOTICE "resize flash to 0x%x Bytes\n",(unsigned int) chip_info->size);
    }
        
    avalanche_map->name = chip_info->name;
    avalanche_map->size = chip_info->size;
    avalanche_map->buswidth = chip_info->buswidth;
    avalanche_map->read8 = avalanche_read8;
    avalanche_map->read16 = avalanche_read16;
    avalanche_map->read32 = avalanche_read32;
    avalanche_map->copy_from = avalanche_copy_from;
    if(is_flash) {
        avalanche_map->write8 = avalanche_write8;
        avalanche_map->write16 = avalanche_write16;
        avalanche_map->write32 = avalanche_write32;
        avalanche_map->copy_to = avalanche_copy_to;
    }  else {
        chip_info->addr += flashsize;
        chip_info->size = ((1 << 20) * 32) - flashsize;
        ;
    }

    printk(KERN_NOTICE "avalanche %s device: 0x%x at 0x%x.\n", name, (unsigned int)chip_info->size, (unsigned int)chip_info->addr);
	avalanche_map->map_priv_1 = (unsigned long)ioremap_nocache(chip_info->addr, chip_info->size);

	if (!avalanche_map->map_priv_1) {
		printk("Failed to ioremap\n");
		return -EIO;
	}

	avalanche_mtd_info = do_map_probe(probe_name, avalanche_map);
	if (avalanche_mtd_info) {
	    dev->avalanche_mtd_info = avalanche_mtd_info;	
	    avalanche_mtd_info->module = THIS_MODULE;
	    return 0;	
	}

    printk("registering mtd failed\n");
    return -ENXIO;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int __init avalanche_mtd_init(void)
{      
    int ret, count;

    avalanche_mtd[0].avalanche_chip_info = &avalanche_chip_info[0];
    ret = avalanche_flash_init(&(avalanche_mtd[0]), 1);	
    if(ret !=0)
        printk("failed initializing  flash dev %d\n", 0);
	count = create_mtd_partitions(); 
    avalanche_mtd_ready=1;

#if NUM_OF_MTD_DEVICES == 2  
    {
        char *mtdram = strstr(prom_getcmdline(), "mtdram1");
        char *base_ptr = NULL;
        char *end_ptr = NULL;
        char *p;
        int base, end;
        if(mtdram == NULL) {
            return 0;
        }
        p = strchr(mtdram, '=');
        if(p) {
            base_ptr = ++p;
            p = strchr(mtdram, ',');
            if(p) {
                *p++ = '\0';
                end_ptr = p;
            }
        }
        /*--- printk("[mtdram] env=%s base=%s end=%s\n", mtdram, base_ptr, end_ptr); ---*/

        if ((base_ptr == NULL) || (end_ptr == NULL)) {	
            printk(KERN_ERR "[mtdram] format error: %s\n", mtdram);
            return 0;
        }

        base = simple_strtol(base_ptr, NULL, 0);
        end  = simple_strtol(end_ptr, NULL, 0);

        avalanche_mtd[1].avalanche_chip_info = &avalanche_chip_info[1];
        ret = avalanche_flash_init(&(avalanche_mtd[1]), 0);	
        if(ret) {
            printk("failed initializing  flash dev %d\n", 1);
            return 0;
        }
        create_mtdram_partitions(mtdram, base, end, count); 
    }
#endif /*--- #if NUM_OF_MTD_DEVICES == 2 ---*/

	return 0;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static char *strdup(char *str)
{
	int n = strlen(str)+1;
	char *s = kmalloc(n, GFP_KERNEL);
	if (!s) return NULL;
	return strcpy(s, str);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int avalanche_check_mtd(struct avalanche_mtd *dev, unsigned int flash_base, unsigned int flash_end)
{
	int window_start = dev->avalanche_chip_info->addr;
    int window_size = dev->avalanche_chip_info->size;

	flash_base = (unsigned int)virt_to_bus((char *)flash_base);	
	flash_end = (unsigned int)virt_to_bus((char *)flash_end);

	/*--- printk("flash base = %x, flash_end = %x, window_start = %x, window_end = %x\n",flash_base,flash_end,window_start,window_size); ---*/

	if( (flash_base >= window_start && flash_base <= (window_start + window_size) )  &&
		 (flash_end >= window_start && flash_end <= (window_start + window_size)) )
        		return 1;

	return 0;

}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int avalanche_add_partition(struct avalanche_mtd *dev, char * env_name, unsigned int flash_base, unsigned int flash_end, char *name)
{
	
	int mtd_partition;
	int offset;
	int size;
		
    struct mtd_partition *avalanche_partition_info;
    struct avalanche_chip_info *avalanche_chip_info;
 	struct mtd_info *avalanche_mtd_info;

    printk("[add_partition] %s: 0x%08x - 0x%08x (%s)\n", env_name, flash_base, flash_end, name);

    if( !avalanche_check_mtd(dev,(unsigned int)flash_base, (unsigned int)flash_end)) {
        printk("[add_partition]: check failed\n");
        return -1;
    }

    avalanche_partition_info = dev->avalanche_partition_info;
    avalanche_chip_info      = dev->avalanche_chip_info;
	avalanche_mtd_info       = dev->avalanche_mtd_info;

	offset = virt_to_bus((unsigned char *)flash_base) - avalanche_chip_info->addr;
	size = flash_end - flash_base;
	
	printk("Found a %s image (0x%x), with size (0x%x).\n",env_name, offset, size);

	/* Setup the partition info. We duplicate the env_name for the partiton name */
	mtd_partition = dev->num_of_partitions;
	avalanche_partition_info[mtd_partition].name = strdup(env_name);
	avalanche_partition_info[mtd_partition].offset = offset;
	avalanche_partition_info[mtd_partition].size = size;
	avalanche_partition_info[mtd_partition].mask_flags = 0;
	add_mtd_partitions(avalanche_mtd_info, &avalanche_partition_info[mtd_partition], 1);
	dev->num_of_partitions++;
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int find_quashfs_partitions(unsigned int flash_base, unsigned int flash_end) {
    unsigned int search;
    printk("search for Squash Fs image: 0x%x - 0x%x\n", flash_base, flash_end);
    for(search = flash_base; search <= flash_end ; search += 256) {
        unsigned int *i = (unsigned int *)(((search) & 0x1FFFFFFF) | 0xA0000000);
        if(*i == SQUASHFS_MAGIC) {
            printk("SquashFs image found at 0x%x\n", search);
            return search;
        }
    }
    printk("SquashFs image not found\n");
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int create_mtdram_partitions(char *env_ptr, int base, int end, int pat_offset) {
    char *base_ptr = NULL;
    char *end_ptr = NULL;
    char *p;
    int start;
    char Buffer[20];
    extern unsigned int mtd_root_minor;

    struct _avm_mtd {
        unsigned int start, end;
    } avm_mtd[2];
    memset(avm_mtd, 0, sizeof(avm_mtd));

    /*--- printk("[mtdram] base=%x end=%x\n", base, end); ---*/

    avm_mtd[0].start = avm_mtd[1].start = base;
    avm_mtd[0].end   = avm_mtd[1].end   = end;

    start = find_quashfs_partitions(avm_mtd[0].start, avm_mtd[0].end);
    if(start) {
        avm_mtd[0].start = avm_mtd[1].end = start;
    }

    sprintf(Buffer, "mtd%u", pat_offset);
    if(avalanche_add_partition(&(avalanche_mtd[1]), Buffer, avm_mtd[0].start, avm_mtd[0].end, "filesystem"))
        return 0;
    mtd_root_minor = pat_offset + 0;
    printk("[mtdram] set root to 1f:%u\n", mtd_root_minor);
    sprintf(Buffer, "mtd%u", pat_offset + 1);
    if(avalanche_add_partition(&(avalanche_mtd[1]), Buffer, avm_mtd[1].start, avm_mtd[1].end, "kernel"))
        return 1;
    return 2;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int create_mtd_partitions(void) {
	char *env_ptr;
	char *base_ptr;
	char *end_ptr;
    int num_of_partitions = 0;
    unsigned int jffs_size = 0;
    char	env_name[20];
	unsigned int _MAX_NUM_PARTITIONS = MAX_NUM_PARTITIONS;
    extern unsigned int mtd_root_minor;
    unsigned int i;

    struct _avm_mtd {
        unsigned int start, end;
    } avm_mtd[_MAX_NUM_PARTITIONS];
    memset(avm_mtd, 0, sizeof(avm_mtd));

    enum _avm_mtd_type {
        _5_mtd_Xmb,
        _5_2_mtd_Xmb,
        _4_1_mtd_Xmb,
        _5_0_mtd_Xmb
    } avm_mtd_type;

    /*--------------------------------------------------------------------------------------*\
     * read mtd's from ROM
    \*--------------------------------------------------------------------------------------*/
    for(i = 0 ; i < _MAX_NUM_PARTITIONS ; i++) {
        sprintf(env_name, "mtd%1u", i);
        printk("Looking for mtd device :%s:\n", env_name);
        env_ptr = prom_getenv(env_name);
        if(env_ptr == NULL) {
            printk("mtd %s not found\n", env_name);
            /* No more partitions to find */
            break;
        }
        base_ptr = strtok(env_ptr, ",");
        end_ptr = strtok(NULL, ",");
        if ((base_ptr == NULL) || (end_ptr == NULL)) {	
            printk("ERROR: Invalid %s='%s' start(%s),end(%s).\n", 
                env_name,
                env_ptr,
                (base_ptr == NULL)  ? "(null)" : base_ptr,
                (end_ptr == NULL) ? "(null)" : end_ptr);
        }
        avm_mtd[i].start = simple_strtol(base_ptr, NULL, 0);
        avm_mtd[i].end   = simple_strtol(end_ptr, NULL, 0);
    }

    /*--------------------------------------------------------------------------------------*\
     * find memory layout type
    \*--------------------------------------------------------------------------------------*/
    if((avm_mtd[0].start == avm_mtd[0].end) && ((avm_mtd[0].start & ((8 << 20) - 1)) == 0)) {
        if((avm_mtd[5].start == avm_mtd[5].end) && (avm_mtd[5].start != 0)) {
            avm_mtd_type = _5_0_mtd_Xmb;
        } else {
            avm_mtd_type = _4_1_mtd_Xmb;
        }
    } else {
        /*----------------------------------------------------------------------------------*\
            wenn am begin der filesystem partition ein squashfs header steht dann haben 
            wir _5mtd_Xmb. andernfalls ein zusammengefasses kernel und filesystem image
        \*----------------------------------------------------------------------------------*/
        if(find_quashfs_partitions(avm_mtd[0].start, avm_mtd[0].start)) {
            avm_mtd_type = _5_mtd_Xmb;
        } else {
            avm_mtd_type = _5_2_mtd_Xmb;
        }
    }

    /*--------------------------------------------------------------------------------------*\
    \*--------------------------------------------------------------------------------------*/
    for(i = 0 ; i < _MAX_NUM_PARTITIONS ; i++) {
        printk("flash: mtd[%u] 0x%08X - 0x%08X\n", i, avm_mtd[i].start, avm_mtd[i].end);
    }

    /*--------------------------------------------------------------------------------------*\
     * create virtual mtd images
    \*--------------------------------------------------------------------------------------*/
    mtd_root_minor = 0;  /*--- this is root ---*/
    switch(avm_mtd_type) {
        case _5_mtd_Xmb:
            printk("[_5_mtd_Xmb]:\n");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd0", avm_mtd[0].start, avm_mtd[0].end, "filesystem");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd1", avm_mtd[1].start, avm_mtd[1].end, "kernel");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd2", avm_mtd[2].start, avm_mtd[2].end, "urlader");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd3", avm_mtd[3].start, avm_mtd[3].end, "tffs");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd4", avm_mtd[4].start, avm_mtd[4].end, "tffs");
            i = find_quashfs_partitions(avm_mtd[1].start, avm_mtd[1].end);
            if(i) 
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd5", i, avm_mtd[1].end, "hidden filesystem");
	        num_of_partitions = 5;
            break;
        case _5_2_mtd_Xmb:
            printk("[_5_2_mtd_Xmb]:\n");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd0", avm_mtd[0].start, avm_mtd[0].end, "filesystem (for update)");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd1", avm_mtd[1].start, avm_mtd[1].end, "kernel");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd2", avm_mtd[2].start, avm_mtd[2].end, "urlader");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd3", avm_mtd[3].start, avm_mtd[3].end, "tffs");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd4", avm_mtd[4].start, avm_mtd[4].end, "tffs");
            i = find_quashfs_partitions(avm_mtd[1].start, avm_mtd[1].end);
            if(i) {
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd5", i, avm_mtd[1].end, "hidden filesystem (not used)");
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd6", i, avm_mtd[0].end, "root filesystem");
                mtd_root_minor = 6;  /*--- this is root ---*/
            } else {
                panic("avalanche-flash: memory layout _5_2_mtd_Xmb no root fs found\n");
            }
	        num_of_partitions = 7;
            break;
        case _4_1_mtd_Xmb:
            printk("[_4_1_mtd_Xmb]:\n");
            i = find_quashfs_partitions(avm_mtd[1].start, avm_mtd[1].end);
            if(i) {
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd0", i, avm_mtd[1].end, "root filesystem");
            } else {
                char *mtdram = strstr(prom_getcmdline(), "mtdram1");
                if(mtdram == NULL)
                    panic("avalanche-flash: memory layout _4_1_mtd_Xmb no root fs found\n");
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd0", avm_mtd[1].end, avm_mtd[1].end, "root filesystem");
                printk("[mtd] root filesystem found in flash, try ram\n");
            }
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd1", avm_mtd[1].start, avm_mtd[1].end, "kernel");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd2", avm_mtd[2].start, avm_mtd[2].end, "urlader");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd3", avm_mtd[3].start, avm_mtd[3].end, "tffs");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd4", avm_mtd[4].start, avm_mtd[4].end, "tffs");
	        num_of_partitions = 5;
            break;
        case _5_0_mtd_Xmb:
            printk("[_5_0_mtd_Xmb]:\n");
            i = find_quashfs_partitions(avm_mtd[1].start, avm_mtd[1].end);
            if(i) {
                env_ptr = prom_getenv("jffs2_size");
                if(env_ptr) {
                    jffs_size = simple_strtol(env_ptr, NULL, 0);
                } else {
                    jffs_size = 0;
                }
                if(jffs_size == 0) {
                    jffs_size = (3 * (1 << 20)) / (1 << 16); /* 3 MByte in 64 KByte einheiten */
                }
                avalanche_add_partition(&(avalanche_mtd[0]), "mtd0", i, avm_mtd[1].end - ((1 << 16) * jffs_size), "root filesystem"); /* root fs */
                mtd_root_minor = 0;  /*--- this is root ---*/
            } else {
                panic("avalanche-flash: memory layout _5_0_mtd_Xmb no root fs found\n");
            }
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd1", avm_mtd[1].start, avm_mtd[1].end, "kernel");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd2", avm_mtd[2].start, avm_mtd[2].end, "urlader");
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd3", avm_mtd[3].start, avm_mtd[3].end, "tffs"); 
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd4", avm_mtd[4].start, avm_mtd[4].end, "tffs");  
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd5", avm_mtd[1].end - ((1 << 16) * jffs_size), avm_mtd[1].end, "jffs2");  
            avalanche_add_partition(&(avalanche_mtd[0]), "mtd6", avm_mtd[1].start, avm_mtd[1].end - ((1 << 16) * jffs_size), "update without jffs2");  /* kernel - jffs */
	        num_of_partitions = 7;
            break;
        default:
            panic("avalanche-flash: no valid memory layout found\n");
            break;
    }
    return num_of_partitions;
}

static void __exit avalanche_mtd_cleanup(void)
{
	int i;

	avalanche_mtd_ready=0;
	for(i=0;i <NUM_OF_MTD_DEVICES; i++)
	{
		struct mtd_info *avalanche_mtd_info = avalanche_mtd[i].avalanche_mtd_info;		
		struct map_info *avalanche_map = &avalanche_mtd[i].avalanche_map;		

		if (avalanche_mtd_info) {
			del_mtd_partitions(avalanche_mtd_info);
			del_mtd_device(avalanche_mtd_info);
			map_destroy(avalanche_mtd_info);
		}

		if (avalanche_map->map_priv_1) {
			iounmap((void *)avalanche_map->map_priv_1);
			avalanche_map->map_priv_1 = 0;
		}
	}
}

module_init(avalanche_mtd_init);
module_exit(avalanche_mtd_cleanup);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Avalanche CFI map driver");
