mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-25 14:10:43 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
564 lines
14 KiB
C
564 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2009
|
|
* Vipin Kumar, ST Microelectronics, vipin.kumar@st.com.
|
|
*/
|
|
|
|
#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_MTD_NOR_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];
|
|
|
|
/* data structure to maintain flash ids from different vendors */
|
|
struct flash_device {
|
|
char *name;
|
|
u8 erase_cmd;
|
|
u32 device_id;
|
|
u32 pagesize;
|
|
unsigned long sectorsize;
|
|
unsigned long size_in_bytes;
|
|
};
|
|
|
|
#define FLASH_ID(n, es, id, psize, ssize, size) \
|
|
{ \
|
|
.name = n, \
|
|
.erase_cmd = es, \
|
|
.device_id = id, \
|
|
.pagesize = psize, \
|
|
.sectorsize = ssize, \
|
|
.size_in_bytes = size \
|
|
}
|
|
|
|
/*
|
|
* List of supported flash devices.
|
|
* Currently the erase_cmd field is not used in this driver.
|
|
*/
|
|
static struct flash_device flash_devices[] = {
|
|
FLASH_ID("st m25p16" , 0xd8, 0x00152020, 0x100, 0x10000, 0x200000),
|
|
FLASH_ID("st m25p32" , 0xd8, 0x00162020, 0x100, 0x10000, 0x400000),
|
|
FLASH_ID("st m25p64" , 0xd8, 0x00172020, 0x100, 0x10000, 0x800000),
|
|
FLASH_ID("st m25p128" , 0xd8, 0x00182020, 0x100, 0x40000, 0x1000000),
|
|
FLASH_ID("st m25p05" , 0xd8, 0x00102020, 0x80 , 0x8000 , 0x10000),
|
|
FLASH_ID("st m25p10" , 0xd8, 0x00112020, 0x80 , 0x8000 , 0x20000),
|
|
FLASH_ID("st m25p20" , 0xd8, 0x00122020, 0x100, 0x10000, 0x40000),
|
|
FLASH_ID("st m25p40" , 0xd8, 0x00132020, 0x100, 0x10000, 0x80000),
|
|
FLASH_ID("st m25p80" , 0xd8, 0x00142020, 0x100, 0x10000, 0x100000),
|
|
FLASH_ID("st m45pe10" , 0xd8, 0x00114020, 0x100, 0x10000, 0x20000),
|
|
FLASH_ID("st m45pe20" , 0xd8, 0x00124020, 0x100, 0x10000, 0x40000),
|
|
FLASH_ID("st m45pe40" , 0xd8, 0x00134020, 0x100, 0x10000, 0x80000),
|
|
FLASH_ID("st m45pe80" , 0xd8, 0x00144020, 0x100, 0x10000, 0x100000),
|
|
FLASH_ID("sp s25fl004" , 0xd8, 0x00120201, 0x100, 0x10000, 0x80000),
|
|
FLASH_ID("sp s25fl008" , 0xd8, 0x00130201, 0x100, 0x10000, 0x100000),
|
|
FLASH_ID("sp s25fl016" , 0xd8, 0x00140201, 0x100, 0x10000, 0x200000),
|
|
FLASH_ID("sp s25fl032" , 0xd8, 0x00150201, 0x100, 0x10000, 0x400000),
|
|
FLASH_ID("sp s25fl064" , 0xd8, 0x00160201, 0x100, 0x10000, 0x800000),
|
|
FLASH_ID("mac 25l512" , 0xd8, 0x001020C2, 0x010, 0x10000, 0x10000),
|
|
FLASH_ID("mac 25l1005" , 0xd8, 0x001120C2, 0x010, 0x10000, 0x20000),
|
|
FLASH_ID("mac 25l2005" , 0xd8, 0x001220C2, 0x010, 0x10000, 0x40000),
|
|
FLASH_ID("mac 25l4005" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000),
|
|
FLASH_ID("mac 25l4005a" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000),
|
|
FLASH_ID("mac 25l8005" , 0xd8, 0x001420C2, 0x010, 0x10000, 0x100000),
|
|
FLASH_ID("mac 25l1605" , 0xd8, 0x001520C2, 0x100, 0x10000, 0x200000),
|
|
FLASH_ID("mac 25l1605a" , 0xd8, 0x001520C2, 0x010, 0x10000, 0x200000),
|
|
FLASH_ID("mac 25l3205" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000),
|
|
FLASH_ID("mac 25l3205a" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000),
|
|
FLASH_ID("mac 25l6405" , 0xd8, 0x001720C2, 0x100, 0x10000, 0x800000),
|
|
FLASH_ID("wbd w25q128" , 0xd8, 0x001840EF, 0x100, 0x10000, 0x1000000),
|
|
};
|
|
|
|
/*
|
|
* 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 int smi_wait_xfer_finish(int timeout)
|
|
{
|
|
ulong start = get_timer(0);
|
|
|
|
while (get_timer(start) < timeout) {
|
|
if (readl(&smicntl->smi_sr) & TFF)
|
|
return 0;
|
|
|
|
/* Try after 10 ms */
|
|
udelay(10);
|
|
};
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
if (smi_wait_xfer_finish(XFER_FINISH_TOUT))
|
|
return -EIO;
|
|
|
|
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];
|
|
int value;
|
|
int i;
|
|
|
|
value = smi_read_id(info, banknum);
|
|
|
|
if (value < 0) {
|
|
printf("Flash id could not be read\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Matches chip-id to entire list of 'serial-nor flash' ids */
|
|
for (i = 0; i < ARRAY_SIZE(flash_devices); i++) {
|
|
if (flash_devices[i].device_id == value) {
|
|
info->size = flash_devices[i].size_in_bytes;
|
|
info->flash_id = value;
|
|
info->start[0] = base;
|
|
info->sector_count =
|
|
info->size/flash_devices[i].sectorsize;
|
|
|
|
return info->size;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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 int smi_read_sr(int bank)
|
|
{
|
|
u32 ctrlreg1, val;
|
|
|
|
/* 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);
|
|
|
|
if (smi_wait_xfer_finish(XFER_FINISH_TOUT))
|
|
return -1;
|
|
|
|
val = readl(&smicntl->smi_sr);
|
|
|
|
/* Restore the CTRL REG1 state */
|
|
writel(ctrlreg1, &smicntl->smi_cr1);
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* 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 sr;
|
|
ulong start = get_timer(0);
|
|
|
|
/* One chip guarantees max 5 msec wait here after page writes,
|
|
but potentially three seconds (!) after page erase. */
|
|
while (get_timer(start) < timeout) {
|
|
sr = smi_read_sr(bank);
|
|
if ((sr >= 0) && (!(sr & WIP_BIT)))
|
|
return 0;
|
|
|
|
/* Try again after 10 usec */
|
|
udelay(10);
|
|
} while (timeout--);
|
|
|
|
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;
|
|
u32 start;
|
|
int timeout = WMODE_TOUT;
|
|
int sr;
|
|
|
|
/* 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);
|
|
|
|
if (smi_wait_xfer_finish(XFER_FINISH_TOUT))
|
|
return -1;
|
|
|
|
/* Restore the CTRL REG1 state */
|
|
writel(ctrlreg1, &smicntl->smi_cr1);
|
|
|
|
start = get_timer(0);
|
|
while (get_timer(start) < timeout) {
|
|
sr = smi_read_sr(bank);
|
|
if ((sr >= 0) && (sr & (1 << (bank + WM_SHIFT))))
|
|
return 0;
|
|
|
|
/* Try again after 10 usec */
|
|
udelay(10);
|
|
};
|
|
|
|
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);
|
|
|
|
/* 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);
|
|
if (smi_wait_xfer_finish(XFER_FINISH_TOUT))
|
|
return -EIO;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* smi_write - Write to SMI flash
|
|
* @src_addr: source buffer
|
|
* @dst_addr: destination buffer
|
|
* @length: length to write in bytes
|
|
* @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)
|
|
{
|
|
u8 *src_addr8 = (u8 *)src_addr;
|
|
u8 *dst_addr8 = (u8 *)dst_addr;
|
|
int banknum;
|
|
int i;
|
|
|
|
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 */
|
|
for (i = 0; i < length; i += 4) {
|
|
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;
|
|
}
|
|
|
|
if (length < 4) {
|
|
int k;
|
|
|
|
/*
|
|
* Handle special case, where length < 4 (redundant env)
|
|
*/
|
|
for (k = 0; k < length; k++)
|
|
*dst_addr8++ = *src_addr8++;
|
|
} else {
|
|
/* Normal 32bit write */
|
|
*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, 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;
|
|
}
|
|
|
|
if (info->size >= 0x100000)
|
|
printf(" Size: %ld MB in %d Sectors\n",
|
|
info->size >> 20, info->sector_count);
|
|
else
|
|
printf(" Size: %ld KB in %d Sectors\n",
|
|
info->size >> 10, 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 ((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
|