/* RX On-chip data flash driver
 * Copyright 2015 Yoshinori Sato <ysato@users.sorceforge.jp>
 *
 * 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
 *
 */

#include <common.h>
#include <environment.h>
#include <flash.h>
#include <asm/io.h>
#include "rx64mflash.h"

#define ID_DATA		0x52584454
#define ID_CODE		0x52584344

flash_info_t flash_info[] = {
	{
		.size = 0x0200,
		.sector_count = 128,
		.flash_id = ID_DATA,
	},
	{
		.size = 0x8000,
		.sector_count = 126,
		.flash_id = ID_CODE,
	},
	{
		.size = 0x2000,
		.sector_count = 8,
		.flash_id = ID_CODE,
		.start[0] = 0xffff0000,
		.protect[0] = 1,
		.start[1] = 0xffff2000,
		.protect[1] = 1,
		.start[2] = 0xffff4000,
		.protect[2] = 1,
		.start[3] = 0xffff6000,
		.protect[3] = 1,
		.start[4] = 0xffff8000,
		.protect[4] = 1,
		.start[5] = 0xffffa000,
		.protect[5] = 1,
		.start[6] = 0xffffc000,
		.protect[6] = 1,
		.start[7] = 0xffffe000,
		.protect[7] = 1,
	},
};

#if defined(CONFIG_ENV_IS_IN_FLASH) || defined(CONFIG_ENV_ADDR_REDUND) || (CONFIG_SYS_MONITOR_BASE >= CONFIG_SYS_FLASH_BASE)
flash_info_t *flash_get_info(ulong base)
{
	flash_info_t *info;

	info = &flash_info[0];
	if (info->size && info->start[0] <= base &&
	    base <= info->start[0] + info->size - 1)
		return info;
	else
		return NULL;
}
#endif

unsigned long flash_sector_size(flash_info_t *info, flash_sect_t sect)
{
	return info->size;
}

int flash_real_protect(flash_info_t * info, long sector, int prot)
{
	int r = 0;

	if (info->flash_id == ID_CODE)
		r = rx_flash_protect_c(sector, prot);
	if (r == 0)
		info->protect[sector] = prot;
	return r;
}

static int flash_erase_page_d(unsigned int addr)
{
	int offset;

	rx_flash_pe_mode(0x0080);
	for (offset = 0; offset < 8; offset++) {
		__raw_writel(addr + offset * 0x40, FSADDR);
		__raw_writeb(0x20, FACICMD);
		__raw_writeb(0xd0, FACICMD);
		if (rx_flash_wait_frdy() < 0) {
			rx_flash_abort();
			return ERR_TIMOUT;
		}
	}
	rx_flash_normal_mode();
	return 0;
}

int flash_erase(flash_info_t *info, int s_first, int s_last)
{
	int i;

	if (info->flash_id == ID_DATA) {
		for (i = s_first; i <= s_last; i++) 
			if (flash_erase_page_d(info->start[i]) < 0)
				return ERR_TIMOUT;
	} else {
		for (i = s_first; i <= s_last; i++) {
			if (rx_flash_erase_page_c(info->start[i]) < 0)
				return ERR_TIMOUT;
		}
	}		
	return 0;
}

void flash_print_info(flash_info_t *info)
{
	int i;

	printf("RX On-chip %s flash\n",
	       (info->flash_id == ID_DATA)?"DATA":"CODE");
	for(i = 0; i < info->sector_count; i++) {
		printf("Sector %d: %ldKB (%08lx - %08lx)\n",
		       i, info->size / 1024,
		       info->start[i], info->start[i] + info->size - 1);
	}
}

static int write_d(uchar *src, ulong addr, ulong cnt)
{
	ulong dst;
	int i;

	rx_flash_pe_mode(0x0080);
	dst = addr & ~0x0000003;
	while(cnt > 0) {
		__raw_writel(dst, FSADDR);
		__raw_writeb(0xe8, FACICMD);
		__raw_writeb(2, FACICMD);
		for (i = 0; i < 2; i++) {
			if (dst < addr || cnt == 0) {
				__raw_writew(0xffff, FACICMD);
			} else {
				__raw_writew(*(unsigned short *)src, FACICMD);
				cnt -= 2;
				src += 2;
				dst += 2;
			}
		}
		__raw_writeb(0xd0, FACICMD);
		if (rx_flash_wait_frdy() < 0) {
			rx_flash_abort();
			return ERR_TIMOUT;
		}
	}
	rx_flash_normal_mode();
	return 0;
}

int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt)
{
	if (info->flash_id == ID_DATA) {
		if (write_d(src, addr, cnt) < 0)
			return ERR_TIMOUT;
	} else {
		if (rx_flash_write_c(src, addr, cnt) < 0)
			return ERR_TIMOUT;
	}		
	return 0;
}

unsigned char _iram_text[], _iram_text_end[], _iram_start[];

unsigned long flash_init(void)
{
	int i;

	/* setup lowlevel driver */
	memcpy(_iram_text, _iram_start, _iram_text_end - _iram_text);
	for (i = 0; i < 128; i++) {
		flash_info[0].start[i] = 0x00100000 + i * 0x200;
		flash_info[0].protect[i] = 1;
	}
	for (i = 0; i < 126; i++) {
		flash_info[1].start[i] = 0xffc00000 + i * 0x8000;
		flash_info[1].protect[i] = 1;
	}
	rx_fcu_init();

	return CONFIG_SYS_FLASH_SIZE + 0x10000;
}
