summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nor/cfi_flash_amd.c
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2012-04-19 20:22:00 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2013-02-14 23:37:53 +0100
commit2749fbac48374b5f5cedda31aa85cca5f199536c (patch)
tree332a305b39d27b1d5717be2cd12c02ef597b452b /drivers/mtd/nor/cfi_flash_amd.c
parent1bd90ff5a11fdb79eb865173d2bf4b5e25d95679 (diff)
downloadbarebox-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.c268
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,
+};
+