diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2012-04-19 20:22:00 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2013-02-14 23:37:53 +0100 |
commit | 2749fbac48374b5f5cedda31aa85cca5f199536c (patch) | |
tree | 332a305b39d27b1d5717be2cd12c02ef597b452b /drivers/mtd/nor/cfi_flash_amd.c | |
parent | 1bd90ff5a11fdb79eb865173d2bf4b5e25d95679 (diff) | |
download | barebox-2749fbac48374b5f5cedda31aa85cca5f199536c.tar.gz barebox-2749fbac48374b5f5cedda31aa85cca5f199536c.tar.xz |
nor flash: integrate into mtd
CFI Flash is currently handled outside the mtd layer which makes it
a special case. Integrate it into mtd so that we get rid of this
special status.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mtd/nor/cfi_flash_amd.c')
-rw-r--r-- | drivers/mtd/nor/cfi_flash_amd.c | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/drivers/mtd/nor/cfi_flash_amd.c b/drivers/mtd/nor/cfi_flash_amd.c new file mode 100644 index 0000000000..45c59b9d01 --- /dev/null +++ b/drivers/mtd/nor/cfi_flash_amd.c @@ -0,0 +1,268 @@ +#include <common.h> +#include <stdio.h> +#include "cfi_flash.h" + +/*----------------------------------------------------------------------- + * Reverse the order of the erase regions in the CFI QRY structure. + * This is needed for chips that are either a) correctly detected as + * top-boot, or b) buggy. + */ +static void cfi_reverse_geometry(struct cfi_qry *qry) +{ + unsigned int i, j; + u32 tmp; + + for (i = 0, j = qry->num_erase_regions - 1; i < j; i++, j--) { + tmp = qry->erase_region_info[i]; + qry->erase_region_info[i] = qry->erase_region_info[j]; + qry->erase_region_info[j] = tmp; + } +} + +static void flash_unlock_seq (struct flash_info *info) +{ + flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_UNLOCK_START); + flash_write_cmd (info, 0, info->addr_unlock2, AMD_CMD_UNLOCK_ACK); +} + +/* + * read jedec ids from device and set corresponding fields in info struct + * + * Note: assume cfi->vendor, cfi->portwidth and cfi->chipwidth are correct + * +*/ +static void amd_read_jedec_ids (struct flash_info *info) +{ + info->cmd_reset = AMD_CMD_RESET; + info->manufacturer_id = 0; + info->device_id = 0; + info->device_id2 = 0; + + /* calculate command offsets as in the Linux driver */ + info->addr_unlock1 = 0x555; + info->addr_unlock2 = 0x2AA; + + /* + * modify the unlock address if we are in compatibility mode + */ + if ( /* x8/x16 in x8 mode */ + ((info->chipwidth == FLASH_CFI_BY8) && + (info->interface == FLASH_CFI_X8X16)) || + /* x16/x32 in x16 mode */ + ((info->chipwidth == FLASH_CFI_BY16) && + (info->interface == FLASH_CFI_X16X32))) + { + info->addr_unlock1 = 0xaaa; + info->addr_unlock2 = 0x555; + } + + flash_write_cmd(info, 0, 0, info->cmd_reset); + flash_unlock_seq(info); + flash_write_cmd(info, 0, info->addr_unlock1, FLASH_CMD_READ_ID); + udelay(1000); /* some flash are slow to respond */ + + info->manufacturer_id = jedec_read_mfr(info); + info->device_id = flash_read_uchar (info, + FLASH_OFFSET_DEVICE_ID); + if (info->device_id == 0x7E) { + /* AMD 3-byte (expanded) device ids */ + info->device_id2 = flash_read_uchar (info, + FLASH_OFFSET_DEVICE_ID2); + info->device_id2 <<= 8; + info->device_id2 |= flash_read_uchar (info, + FLASH_OFFSET_DEVICE_ID3); + } + flash_write_cmd(info, 0, 0, info->cmd_reset); +} + +static int flash_toggle (struct flash_info *info, flash_sect_t sect, uint offset, uchar cmd) +{ + void *addr; + cfiword_t cword; + int retval; + + addr = flash_make_addr (info, sect, offset); + flash_make_cmd (info, cmd, &cword); + if (bankwidth_is_1(info)) { + retval = flash_read8(addr) != flash_read8(addr); + } else if (bankwidth_is_2(info)) { + retval = flash_read16(addr) != flash_read16(addr); + } else if (bankwidth_is_4(info)) { + retval = flash_read32(addr) != flash_read32(addr); + } else if (bankwidth_is_8(info)) { + retval = ( (flash_read32( addr ) != flash_read32( addr )) || + (flash_read32(addr+4) != flash_read32(addr+4)) ); + } else + retval = 0; + + return retval; +} + +/* + * flash_is_busy - check to see if the flash is busy + * This routine checks the status of the chip and returns true if the chip is busy + */ +static int amd_flash_is_busy (struct flash_info *info, flash_sect_t sect) +{ + return flash_toggle (info, sect, 0, AMD_STATUS_TOGGLE); +} + +static int amd_flash_erase_one (struct flash_info *info, long sect) +{ + flash_unlock_seq(info); + flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_ERASE_START); + flash_unlock_seq(info); + flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR); + + return flash_status_check(info, sect, info->erase_blk_tout, "erase"); +} + +static void amd_flash_prepare_write(struct flash_info *info) +{ + flash_unlock_seq(info); + flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_WRITE); +} + +#ifdef CONFIG_CFI_BUFFER_WRITE +static int amd_flash_write_cfibuffer (struct flash_info *info, ulong dest, const uchar * cp, + int len) +{ + flash_sect_t sector; + int cnt; + int retcode; + void *src = (void*)cp; + void *dst = (void *)dest; + cfiword_t cword; + + sector = find_sector (info, dest); + + flash_unlock_seq(info); + flash_make_cmd (info, AMD_CMD_WRITE_TO_BUFFER, &cword); + flash_write_word(info, cword, (void *)dest); + + if (bankwidth_is_1(info)) { + cnt = len; + flash_write_cmd(info, sector, 0, (u32)cnt - 1); + while (cnt-- > 0) { + flash_write8(flash_read8(src), dst); + src += 1, dst += 1; + } + } else if (bankwidth_is_2(info)) { + cnt = len >> 1; + flash_write_cmd(info, sector, 0, (u32)cnt - 1); + while (cnt-- > 0) { + flash_write16(flash_read16(src), dst); + src += 2, dst += 2; + } + } else if (bankwidth_is_4(info)) { + cnt = len >> 2; + flash_write_cmd(info, sector, 0, (u32)cnt - 1); + while (cnt-- > 0) { + flash_write32(flash_read32(src), dst); + src += 4, dst += 4; + } + } else if (bankwidth_is_8(info)) { + cnt = len >> 3; + flash_write_cmd(info, sector, 0, (u32)cnt - 1); + while (cnt-- > 0) { + flash_write64(flash_read64(src), dst); + src += 8, dst += 8; + } + } + + flash_write_cmd (info, sector, 0, AMD_CMD_WRITE_BUFFER_CONFIRM); + retcode = flash_status_check (info, sector, info->buffer_write_tout, + "buffer write"); + return retcode; +} +#else +#define amd_flash_write_cfibuffer NULL +#endif /* CONFIG_CFI_BUFFER_WRITE */ + +static int amd_flash_real_protect (struct flash_info *info, long sector, int prot) +{ + if (info->manufacturer_id != (uchar)ATM_MANUFACT) + return 0; + + if (prot) { + flash_unlock_seq (info); + flash_write_cmd (info, 0, info->addr_unlock1, + ATM_CMD_SOFTLOCK_START); + flash_unlock_seq (info); + flash_write_cmd (info, sector, 0, ATM_CMD_LOCK_SECT); + } else { + flash_write_cmd (info, 0, info->addr_unlock1, + AMD_CMD_UNLOCK_START); + if (info->device_id == ATM_ID_BV6416) + flash_write_cmd (info, sector, 0, + ATM_CMD_UNLOCK_SECT); + } + + return 0; +} + +/* + * Manufacturer-specific quirks. Add workarounds for geometry + * reversal, etc. here. + */ +static void flash_fixup_amd (struct flash_info *info, struct cfi_qry *qry) +{ + /* check if flash geometry needs reversal */ + if (qry->num_erase_regions > 1) { + /* reverse geometry if top boot part */ + if (info->cfi_version < 0x3131) { + /* CFI < 1.1, try to guess from device id */ + if ((info->device_id & 0x80) != 0) + cfi_reverse_geometry(qry); + } else if (flash_read_uchar(info, info->ext_addr + 0xf) == 3) { + /* CFI >= 1.1, deduct from top/bottom flag */ + /* note: ext_addr is valid since cfi_version > 0 */ + cfi_reverse_geometry(qry); + } + } +} + +static void flash_fixup_atmel(struct flash_info *info, struct cfi_qry *qry) +{ + int reverse_geometry = 0; + + /* Check the "top boot" bit in the PRI */ + if (info->ext_addr && !(flash_read_uchar(info, info->ext_addr + 6) & 1)) + reverse_geometry = 1; + + /* AT49BV6416(T) list the erase regions in the wrong order. + * However, the device ID is identical with the non-broken + * AT49BV642D since u-boot only reads the low byte (they + * differ in the high byte.) So leave out this fixup for now. + */ + if (info->device_id == 0xd6 || info->device_id == 0xd2) + reverse_geometry = !reverse_geometry; + + if (reverse_geometry) + cfi_reverse_geometry(qry); +} + +static void amd_flash_fixup(struct flash_info *info, struct cfi_qry *qry) +{ + /* Do manufacturer-specific fixups */ + switch (info->manufacturer_id) { + case 0x0001: + flash_fixup_amd(info, qry); + break; + case 0x001f: + flash_fixup_atmel(info, qry); + break; + } +} + +struct cfi_cmd_set cfi_cmd_set_amd = { + .flash_write_cfibuffer = amd_flash_write_cfibuffer, + .flash_erase_one = amd_flash_erase_one, + .flash_is_busy = amd_flash_is_busy, + .flash_read_jedec_ids = amd_read_jedec_ids, + .flash_prepare_write = amd_flash_prepare_write, + .flash_status_check = flash_generic_status_check, + .flash_real_protect = amd_flash_real_protect, + .flash_fixup = amd_flash_fixup, +}; + |