diff options
Diffstat (limited to 'drivers/mtd/st_smi.c')
| -rw-r--r-- | drivers/mtd/st_smi.c | 519 | 
1 files changed, 519 insertions, 0 deletions
| diff --git a/drivers/mtd/st_smi.c b/drivers/mtd/st_smi.c new file mode 100644 index 000000000..db08ab97e --- /dev/null +++ b/drivers/mtd/st_smi.c @@ -0,0 +1,519 @@ +/* + * (C) Copyright 2009 + * Vipin Kumar, ST Microelectronics, vipin.kumar@st.com. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 <flash.h> +#include <linux/err.h> +#include <linux/mtd/st_smi.h> + +#include <asm/io.h> +#include <asm/arch/hardware.h> + +#if !defined(CONFIG_SYS_NO_FLASH) + +static struct smi_regs *const smicntl = +    (struct smi_regs * const)CONFIG_SYS_SMI_BASE; +static ulong bank_base[CONFIG_SYS_MAX_FLASH_BANKS] = +    CONFIG_SYS_FLASH_ADDR_BASE; +flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; + +#define ST_M25Pxx_ID		0x00002020 + +static struct flash_dev flash_ids[] = { +	{0x10, 0x10000, 2},	/* 64K Byte */ +	{0x11, 0x20000, 4},	/* 128K Byte */ +	{0x12, 0x40000, 4},	/* 256K Byte */ +	{0x13, 0x80000, 8},	/* 512K Byte */ +	{0x14, 0x100000, 16},	/* 1M Byte */ +	{0x15, 0x200000, 32},	/* 2M Byte */ +	{0x16, 0x400000, 64},	/* 4M Byte */ +	{0x17, 0x800000, 128},	/* 8M Byte */ +	{0x18, 0x1000000, 64},	/* 16M Byte */ +	{0x00,} +}; + +/* + * smi_wait_xfer_finish - Wait until TFF is set in status register + * @timeout:	 timeout in milliseconds + * + * Wait until TFF is set in status register + */ +static void smi_wait_xfer_finish(int timeout) +{ +	while (timeout--) { +		if (readl(&smicntl->smi_sr) & TFF) +			break; +		udelay(1000); +	} +} + +/* + * smi_read_id - Read flash id + * @info:	 flash_info structure pointer + * @banknum:	 bank number + * + * Read the flash id present at bank #banknum + */ +static unsigned int smi_read_id(flash_info_t *info, int banknum) +{ +	unsigned int value; + +	writel(readl(&smicntl->smi_cr1) | SW_MODE, &smicntl->smi_cr1); +	writel(READ_ID, &smicntl->smi_tr); +	writel((banknum << BANKSEL_SHIFT) | SEND | TX_LEN_1 | RX_LEN_3, +	       &smicntl->smi_cr2); + +	smi_wait_xfer_finish(XFER_FINISH_TOUT); + +	value = (readl(&smicntl->smi_rr) & 0x00FFFFFF); + +	writel(readl(&smicntl->smi_sr) & ~TFF, &smicntl->smi_sr); +	writel(readl(&smicntl->smi_cr1) & ~SW_MODE, &smicntl->smi_cr1); + +	return value; +} + +/* + * flash_get_size - Detect the SMI flash by reading the ID. + * @base:	 Base address of the flash area bank #banknum + * @banknum:	 Bank number + * + * Detect the SMI flash by reading the ID. Initializes the flash_info structure + * with size, sector count etc. + */ +static ulong flash_get_size(ulong base, int banknum) +{ +	flash_info_t *info = &flash_info[banknum]; +	struct flash_dev *dev; +	unsigned int value; +	unsigned int density; +	int i; + +	value = smi_read_id(info, banknum); +	density = (value >> 16) & 0xff; + +	for (i = 0, dev = &flash_ids[0]; dev->density != 0x0; +	     i++, dev = &flash_ids[i]) { +		if (dev->density == density) { +			info->size = dev->size; +			info->sector_count = dev->sector_count; +			break; +		} +	} + +	if (dev->density == 0x0) +		return 0; + +	info->flash_id = value & 0xffff; +	info->start[0] = base; + +	return info->size; +} + +/* + * smi_read_sr - Read status register of SMI + * @bank:	 bank number + * + * This routine will get the status register of the flash chip present at the + * given bank + */ +static unsigned int smi_read_sr(int bank) +{ +	u32 ctrlreg1; + +	/* store the CTRL REG1 state */ +	ctrlreg1 = readl(&smicntl->smi_cr1); + +	/* Program SMI in HW Mode */ +	writel(readl(&smicntl->smi_cr1) & ~(SW_MODE | WB_MODE), +	       &smicntl->smi_cr1); + +	/* Performing a RSR instruction in HW mode */ +	writel((bank << BANKSEL_SHIFT) | RD_STATUS_REG, &smicntl->smi_cr2); + +	smi_wait_xfer_finish(XFER_FINISH_TOUT); + +	/* Restore the CTRL REG1 state */ +	writel(ctrlreg1, &smicntl->smi_cr1); + +	return readl(&smicntl->smi_sr); +} + +/* + * smi_wait_till_ready - Wait till last operation is over. + * @bank:	 bank number shifted. + * @timeout:	 timeout in milliseconds. + * + * This routine checks for WIP(write in progress)bit in Status register(SMSR-b0) + * The routine checks for #timeout loops, each at interval of 1 milli-second. + * If successful the routine returns 0. + */ +static int smi_wait_till_ready(int bank, int timeout) +{ +	int count; +	unsigned int sr; + +	/* One chip guarantees max 5 msec wait here after page writes, +	   but potentially three seconds (!) after page erase. */ +	for (count = 0; count < timeout; count++) { + +		sr = smi_read_sr(bank); +		if (sr < 0) +			break; +		else if (!(sr & WIP_BIT)) +			return 0; + +		/* Try again after 1m-sec */ +		udelay(1000); +	} +	printf("SMI controller is still in wait, timeout=%d\n", timeout); +	return -EIO; +} + +/* + * smi_write_enable - Enable the flash to do write operation + * @bank:	 bank number + * + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. + */ +static int smi_write_enable(int bank) +{ +	u32 ctrlreg1; +	int timeout = WMODE_TOUT; + +	/* Store the CTRL REG1 state */ +	ctrlreg1 = readl(&smicntl->smi_cr1); + +	/* Program SMI in H/W Mode */ +	writel(readl(&smicntl->smi_cr1) & ~SW_MODE, &smicntl->smi_cr1); + +	/* Give the Flash, Write Enable command */ +	writel((bank << BANKSEL_SHIFT) | WE, &smicntl->smi_cr2); + +	smi_wait_xfer_finish(XFER_FINISH_TOUT); + +	/* Restore the CTRL REG1 state */ +	writel(ctrlreg1, &smicntl->smi_cr1); + +	while (timeout--) { +		if (smi_read_sr(bank) & (1 << (bank + WM_SHIFT))) +			break; +		udelay(1000); +	} + +	if (timeout) +		return 0; + +	return -1; +} + +/* + * smi_init - SMI initialization routine + * + * SMI initialization routine. Sets SMI control register1. + */ +void smi_init(void) +{ +	/* Setting the fast mode values. SMI working at 166/4 = 41.5 MHz */ +	writel(HOLD1 | FAST_MODE | BANK_EN | DSEL_TIME | PRESCAL4, +	       &smicntl->smi_cr1); +} + +/* + * smi_sector_erase - Erase flash sector + * @info:	 flash_info structure pointer + * @sector:	 sector number + * + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. + */ +static int smi_sector_erase(flash_info_t *info, unsigned int sector) +{ +	int bank; +	unsigned int sect_add; +	unsigned int instruction; + +	switch (info->start[0]) { +	case SMIBANK0_BASE: +		bank = BANK0; +		break; +	case SMIBANK1_BASE: +		bank = BANK1; +		break; +	case SMIBANK2_BASE: +		bank = BANK2; +		break; +	case SMIBANK3_BASE: +		bank = BANK3; +		break; +	default: +		return -1; +	} + +	sect_add = sector * (info->size / info->sector_count); +	instruction = ((sect_add >> 8) & 0x0000FF00) | SECTOR_ERASE; + +	writel(readl(&smicntl->smi_sr) & ~(ERF1 | ERF2), &smicntl->smi_sr); + +	if (info->flash_id == ST_M25Pxx_ID) { +		/* Wait until finished previous write command. */ +		if (smi_wait_till_ready(bank, CONFIG_SYS_FLASH_ERASE_TOUT)) +			return -EBUSY; + +		/* Send write enable, before erase commands. */ +		if (smi_write_enable(bank)) +			return -EIO; + +		/* Put SMI in SW mode */ +		writel(readl(&smicntl->smi_cr1) | SW_MODE, &smicntl->smi_cr1); + +		/* Send Sector Erase command in SW Mode */ +		writel(instruction, &smicntl->smi_tr); +		writel((bank << BANKSEL_SHIFT) | SEND | TX_LEN_4, +		       &smicntl->smi_cr2); +		smi_wait_xfer_finish(XFER_FINISH_TOUT); + +		if (smi_wait_till_ready(bank, CONFIG_SYS_FLASH_ERASE_TOUT)) +			return -EBUSY; + +		/* Put SMI in HW mode */ +		writel(readl(&smicntl->smi_cr1) & ~SW_MODE, +		       &smicntl->smi_cr1); + +		return 0; +	} else { +		/* Put SMI in HW mode */ +		writel(readl(&smicntl->smi_cr1) & ~SW_MODE, +		       &smicntl->smi_cr1); +		return -EINVAL; +	} +} + +/* + * smi_write - Write to SMI flash + * @src_addr:	 source buffer + * @dst_addr:	 destination buffer + * @length:	 length to write in words + * @bank:	 bank base address + * + * Write to SMI flash + */ +static int smi_write(unsigned int *src_addr, unsigned int *dst_addr, +		     unsigned int length, ulong bank_addr) +{ +	int banknum; + +	switch (bank_addr) { +	case SMIBANK0_BASE: +		banknum = BANK0; +		break; +	case SMIBANK1_BASE: +		banknum = BANK1; +		break; +	case SMIBANK2_BASE: +		banknum = BANK2; +		break; +	case SMIBANK3_BASE: +		banknum = BANK3; +		break; +	default: +		return -1; +	} + +	if (smi_wait_till_ready(banknum, CONFIG_SYS_FLASH_WRITE_TOUT)) +		return -EBUSY; + +	/* Set SMI in Hardware Mode */ +	writel(readl(&smicntl->smi_cr1) & ~SW_MODE, &smicntl->smi_cr1); + +	if (smi_write_enable(banknum)) +		return -EIO; + +	/* Perform the write command */ +	while (length--) { +		if (((ulong) (dst_addr) % SFLASH_PAGE_SIZE) == 0) { +			if (smi_wait_till_ready(banknum, +						CONFIG_SYS_FLASH_WRITE_TOUT)) +				return -EBUSY; + +			if (smi_write_enable(banknum)) +				return -EIO; +		} + +		*dst_addr++ = *src_addr++; + +		if ((readl(&smicntl->smi_sr) & (ERF1 | ERF2))) +			return -EIO; +	} + +	if (smi_wait_till_ready(banknum, CONFIG_SYS_FLASH_WRITE_TOUT)) +		return -EBUSY; + +	writel(readl(&smicntl->smi_sr) & ~(WCF), &smicntl->smi_sr); + +	return 0; +} + +/* + * write_buff - Write to SMI flash + * @info:	 flash info structure + * @src:	 source buffer + * @dest_addr:	 destination buffer + * @length:	 length to write in words + * + * Write to SMI flash + */ +int write_buff(flash_info_t *info, uchar *src, ulong dest_addr, ulong length) +{ +	return smi_write((unsigned int *)src, (unsigned int *)dest_addr, +		  (length + 3) / 4, info->start[0]); +} + +/* + * flash_init - SMI flash initialization + * + * SMI flash initialization + */ +unsigned long flash_init(void) +{ +	unsigned long size = 0; +	int i, j; + +	smi_init(); + +	for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) { +		flash_info[i].flash_id = FLASH_UNKNOWN; +		size += flash_info[i].size = flash_get_size(bank_base[i], i); +	} + +	for (j = 0; j < CONFIG_SYS_MAX_FLASH_BANKS; j++) { +		for (i = 1; i < flash_info[j].sector_count; i++) +			flash_info[j].start[i] = +			    flash_info[j].start[i - 1] + +			    flash_info->size / flash_info->sector_count; + +	} + +	return size; +} + +/* + * flash_print_info - Print SMI flash information + * + * Print SMI flash information + */ +void flash_print_info(flash_info_t *info) +{ +	int i; +	if (info->flash_id == FLASH_UNKNOWN) { +		puts("missing or unknown FLASH type\n"); +		return; +	} +	printf("  Size: %ld MB in %d Sectors\n", +	       info->size >> 20, info->sector_count); + +	puts("  Sector Start Addresses:"); +	for (i = 0; i < info->sector_count; ++i) { +#ifdef CONFIG_SYS_FLASH_EMPTY_INFO +		int size; +		int erased; +		u32 *flash; + +		/* +		 * Check if whole sector is erased +		 */ +		size = (info->size) / (info->sector_count); +		flash = (u32 *) info->start[i]; +		size = size / sizeof(int); + +		while ((size--) && (*flash++ == ~0)) +			; + +		size++; +		if (size) +			erased = 0; +		else +			erased = 1; + +		if ((i % 5) == 0) +			printf("\n"); + +		printf(" %08lX%s%s", +		       info->start[i], +		       erased ? " E" : "  ", info->protect[i] ? "RO " : "   "); +#else +		if ((i % 5) == 0) +			printf("\n   "); +		printf(" %08lX%s", +		       info->start[i], info->protect[i] ? " (RO)  " : "     "); +#endif +	} +	putc('\n'); +	return; +} + +/* + * flash_erase - Erase SMI flash + * + * Erase SMI flash + */ +int flash_erase(flash_info_t *info, int s_first, int s_last) +{ +	int rcode = 0; +	int prot = 0; +	flash_sect_t sect; + +	if (info->flash_id != ST_M25Pxx_ID) { +		puts("Can't erase unknown flash type - aborted\n"); +		return 1; +	} + +	if ((s_first < 0) || (s_first > s_last)) { +		puts("- no sectors to erase\n"); +		return 1; +	} + +	for (sect = s_first; sect <= s_last; ++sect) { +		if (info->protect[sect]) +			prot++; +	} +	if (prot) { +		printf("- Warning: %d protected sectors will not be erased!\n", +		       prot); +	} else { +		putc('\n'); +	} + +	for (sect = s_first; sect <= s_last; sect++) { +		if (info->protect[sect] == 0) { +			if (smi_sector_erase(info, sect)) +				rcode = 1; +			else +				putc('.'); +		} +	} +	puts(" done\n"); +	return rcode; +} +#endif |