From patchwork Fri Oct 7 17:25:08 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergey Kubushyn X-Patchwork-Id: 679661 X-Patchwork-Delegate: sbabic@denx.de Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 3srGcm6JmWz9t1F for ; Sat, 8 Oct 2016 04:25:27 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 28AD5A75D6; Fri, 7 Oct 2016 19:25:24 +0200 (CEST) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id FaYdernKHqec; Fri, 7 Oct 2016 19:25:24 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id BC7F0A7549; Fri, 7 Oct 2016 19:25:23 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id AD032A7549 for ; Fri, 7 Oct 2016 19:25:18 +0200 (CEST) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id xXsl2kfNBixO for ; Fri, 7 Oct 2016 19:25:18 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from mail.koi8.net (home.koi8.net [174.71.238.107]) by theia.denx.de (Postfix) with ESMTPS id 1616CA7534 for ; Fri, 7 Oct 2016 19:25:14 +0200 (CEST) Received: from maverick.koi8.net (maverick.koi8.net [192.168.168.3]) by mail.koi8.net (8.15.2/8.15.2) with ESMTP id u97HP8a2025492; Fri, 7 Oct 2016 10:25:09 -0700 Date: Fri, 7 Oct 2016 10:25:08 -0700 (PDT) From: Sergey Kubushyn To: U-Boot list Message-ID: User-Agent: Alpine 2.20 (LFD 67 2015-01-07) MIME-Version: 1.0 Subject: [U-Boot] [PATCH 1/1]: nand bootupdate for i.MX6 -- REBASED X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" Here it is agains your latest u-boot-imx master. Won't post that description again to lower the noise level, just a diff itself. Signed-off-by: Sergey Kubushyn --- arch/arm/include/asm/imx-common/mxs_nand.h | 37 ++ cmd/Kconfig | 10 + cmd/nand.c | 61 +++ drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/mxs_nand.c | 7 +- drivers/mtd/nand/mxs_nand_bootupdate.c | 556 +++++++++++++++++++++ 6 files changed, 669 insertions(+), 3 deletions(-) --- ****************************************************************** * KSI@home KOI8 Net < > The impossible we do immediately. * * Las Vegas NV, USA < > Miracles require 24-hour notice. * ****************************************************************** diff --git a/arch/arm/include/asm/imx-common/mxs_nand.h b/arch/arm/include/asm/imx-common/mxs_nand.h new file mode 100644 index 0000000..7826a9f --- /dev/null +++ b/arch/arm/include/asm/imx-common/mxs_nand.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 PHYTEC Messtechnik GmbH, + * Author: Stefan Christ + * + * 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. + */ + +#ifndef __NAND_MXS_H +#define __NAND_MXS_H + +/* + * Functions are definied in drivers/mtd/nand/nand_mxs.c. They are used to + * calculate the ECC Strength, BadBlockMarkerByte and BadBlockMarkerStartBit + * which are placed into the FCB structure. The i.MX6 ROM needs these + * parameters to read the firmware from NAND. + * + * The parameters depends on the pagesize and oobsize of NAND chips and are + * different for each combination. To avoid placing hardcoded values in the bbu + * update handler code, the generic calculation from the driver code is used. + */ + +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, + uint32_t page_oob_size); + +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd); + +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd); + +#endif /* __NAND_MXS_H */ diff --git a/cmd/Kconfig b/cmd/Kconfig index 86554ea..6ef7253 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -571,6 +571,16 @@ config CMD_TIME help Run commands and summarize execution time. +config CMD_NAND_BOOTUPDATE + bool "Update NAND bootloader on iMX6 and its brethen" + depends on ARCH_MX6 && NAND_BOOT && CMD_NAND + help + Having iMX6 NAND U-Boot image in memory creates all necessary + structures and writes those where they belong along with that + U-Boot image so system is able to boot off of raw NAND. Kinda + like kobs-ng utility but simpler. + + # TODO: rename to CMD_SLEEP config CMD_MISC bool "sleep" diff --git a/cmd/nand.c b/cmd/nand.c index c16ec77..c25f1ef 100644 --- a/cmd/nand.c +++ b/cmd/nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, struct mtd_device **dev, u8 *part_num, struct part_info **part); #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE +/* This comes from a separate file in drivers/mtd/nand */ +int mxs_do_nand_bootupdate(ulong addr, size_t len); +#endif + static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) { @@ -372,6 +377,9 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) loff_t off, size, maxsize; char *cmd, *s; struct mtd_info *mtd; +#ifdef CONFIG_CMD_NAND_BOOTUPDATE + size_t cnt; +#endif #ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; #else @@ -777,6 +785,48 @@ static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE + if (strncmp(cmd, "bootupdate", 10) == 0) { + + if (argc < 3) { + /* All default values */ + addr = getenv_ulong("fileaddr", 16, 0UL); + cnt = getenv_ulong("filesize", 16, 0UL); + } + + if (argc == 3) { + /* 'addr' only, file size from environment */ + if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL) + addr = getenv_ulong("fileaddr", 16, 0UL); + + cnt = getenv_ulong("filesize", 16, 0UL); + } + + if (argc > 3) { + /* 'addr', 'size', and possibly more */ + if ((addr = simple_strtoul(argv[2], NULL, 16)) == 0UL) + addr = getenv_ulong("fileaddr", 16, 0UL); + + if ((cnt = simple_strtoul(argv[3], NULL, 16)) == 0UL) + cnt = getenv_ulong("filesize", 16, 0UL); + } + + + if (addr == 0 || cnt == 0) { + puts("Invalid arguments to nand bootupdate!\n"); + return 1; + } + + if (mxs_do_nand_bootupdate(addr, cnt)) { + puts("NAND bootupdate failed!\n"); + return 1; + } + + puts("NAND bootupdate successful\n"); + return 0; + } +#endif + usage: return CMD_RET_USAGE; } @@ -798,6 +848,17 @@ static char nand_help_text[] = " 'addr', skipping bad blocks and dropping any pages at the end\n" " of eraseblocks that contain only 0xFF\n" #endif +#ifdef CONFIG_CMD_NAND_BOOTUPDATE + "nand bootupdate - [addr] [size]\n" + " write U-Boot into NAND in board/SoC specific manner creating all\n" + " required headers and other bits and pieces as required for the\n" + " system to be able to boot off of NAND. 'addr' is the address\n" + " where U-Boot image has been loaded at, 'size' is its size.\n" + " If any of 'addr'/'size' is missing it is taken from environment\n" + " for the last file loaded. U-Boot image must be of a proper type\n" + " for the target platform (only IMX image supported at the moment)\n" + " binary without U-Boot image headers (e.g. u-boot.imx file.)\n" +#endif "nand erase[.spread] [clean] off size - erase 'size' bytes " "from offset 'off'\n" " With '.spread', erase enough for given file size, otherwise,\n" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 1df9273..b1b98dc 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_CMD_NAND_BOOTUPDATE) += mxs_nand_bootupdate.o else # minimal SPL drivers diff --git a/drivers/mtd/nand/mxs_nand.c b/drivers/mtd/nand/mxs_nand.c index 9200544..333ee09 100644 --- a/drivers/mtd/nand/mxs_nand.c +++ b/drivers/mtd/nand/mxs_nand.c @@ -26,6 +26,7 @@ #include #include #include +#include #define MXS_NAND_DMA_DESCRIPTOR_COUNT 4 @@ -150,7 +151,7 @@ static uint32_t mxs_nand_aux_status_offset(void) return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3; } -static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, +uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size, uint32_t page_oob_size) { int ecc_strength; @@ -226,14 +227,14 @@ static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size, return block_mark_bit_offset; } -static uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3; } -static uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) +uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd) { uint32_t ecc_strength; ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize); diff --git a/drivers/mtd/nand/mxs_nand_bootupdate.c b/drivers/mtd/nand/mxs_nand_bootupdate.c new file mode 100644 index 0000000..9442553 --- /dev/null +++ b/drivers/mtd/nand/mxs_nand_bootupdate.c @@ -0,0 +1,556 @@ +/* + * mxs_nand_bootupdate.c - write U-Boot to MXS NAND to make it bootable + * + * Copyright (c) 2016 Sergey Kubushyn + * + * Most of the source shamelesly stolen from barebox. + * + * Here is the original copyright: + * + *=== Cut === + * Copyright (C) 2014 Sascha Hauer, Pengutronix + * + * 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. + *=== Cut === + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef CONFIG_CMD_MTDPARTS +#error "CONFIG_CMD_MTDPARTS is not set, mtd partition support missing" +#endif + +static const char *uboot_tgt = "nand0,0"; + +/* partition handling routines */ +int mtdparts_init(void); +int id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num); +int find_dev_and_part(const char *id, struct mtd_device **dev, + u8 *part_num, struct part_info **part); + +struct dbbt_block { + uint32_t Checksum; /* reserved on i.MX6 */ + uint32_t FingerPrint; + uint32_t Version; + uint32_t numberBB; /* reserved on i.MX6 */ + uint32_t DBBTNumOfPages; +}; + +struct fcb_block { + uint32_t Checksum; /* First fingerprint in first byte */ + uint32_t FingerPrint; /* 2nd fingerprint at byte 4 */ + uint32_t Version; /* 3rd fingerprint at byte 8 */ + uint8_t DataSetup; + uint8_t DataHold; + uint8_t AddressSetup; + uint8_t DSAMPLE_TIME; + /* These are for application use only and not for ROM. */ + uint8_t NandTimingState; + uint8_t REA; + uint8_t RLOH; + uint8_t RHOH; + uint32_t PageDataSize; /* 2048 for 2K pages, 4096 for 4K pages */ + uint32_t TotalPageSize; /* 2112 for 2K pages, 4314 for 4K pages */ + uint32_t SectorsPerBlock; /* Number of 2K sections per block */ + uint32_t NumberOfNANDs; /* Total Number of NANDs - not used by ROM */ + uint32_t TotalInternalDie; /* Number of separate chips in this NAND */ + uint32_t CellType; /* MLC or SLC */ + uint32_t EccBlockNEccType; /* Type of ECC, can be one of BCH-0-20 */ + uint32_t EccBlock0Size; /* Number of bytes for Block0 - BCH */ + uint32_t EccBlockNSize; /* Block size in bytes for all blocks other than Block0 - BCH */ + uint32_t EccBlock0EccType; /* Ecc level for Block 0 - BCH */ + uint32_t MetadataBytes; /* Metadata size - BCH */ + uint32_t NumEccBlocksPerPage; /* Number of blocks per page for ROM use - BCH */ + uint32_t EccBlockNEccLevelSDK; /* Type of ECC, can be one of BCH-0-20 */ + uint32_t EccBlock0SizeSDK; /* Number of bytes for Block0 - BCH */ + uint32_t EccBlockNSizeSDK; /* Block size in bytes for all blocks other than Block0 - BCH */ + uint32_t EccBlock0EccLevelSDK; /* Ecc level for Block 0 - BCH */ + uint32_t NumEccBlocksPerPageSDK;/* Number of blocks per page for SDK use - BCH */ + uint32_t MetadataBytesSDK; /* Metadata size - BCH */ + uint32_t EraseThreshold; /* To set into BCH_MODE register */ + uint32_t BootPatch; /* 0 for normal boot and 1 to load patch starting next to FCB */ + uint32_t PatchSectors; /* Size of patch in sectors */ + uint32_t Firmware1_startingPage;/* Firmware image starts on this sector */ + uint32_t Firmware2_startingPage;/* Secondary FW Image starting Sector */ + uint32_t PagesInFirmware1; /* Number of sectors in firmware image */ + uint32_t PagesInFirmware2; /* Number of sector in secondary FW image */ + uint32_t DBBTSearchAreaStartAddress; /* Page address where dbbt search area begins */ + uint32_t BadBlockMarkerByte; /* Byte in page data that have manufacturer marked bad block marker, */ + /* this will be swapped with metadata[0] to complete page data. */ + uint32_t BadBlockMarkerStartBit;/* For BCH ECC sizes other than 8 and 16 the bad block marker does not */ + /* start at 0th bit of BadBlockMarkerByte. This field is used to get to */ + /* the start bit of bad block marker byte with in BadBlockMarkerByte */ + uint32_t BBMarkerPhysicalOffset;/* FCB value that gives byte offset for bad block marker on physical NAND page */ + uint32_t BCHType; + + uint32_t TMTiming2_ReadLatency; + uint32_t TMTiming2_PreambleDelay; + uint32_t TMTiming2_CEDelay; + uint32_t TMTiming2_PostambleDelay; + uint32_t TMTiming2_CmdAddPause; + uint32_t TMTiming2_DataPause; + uint32_t TMSpeed; + uint32_t TMTiming1_BusyTimeout; + + uint32_t DISBBM; /* the flag to enable (1)/disable(0) bi swap */ + uint32_t BBMarkerPhysicalOffsetInSpareData; /* The swap position of main area in spare area */ +}; + +struct fw_write_data { + int fw1_blk; + int fw2_blk; + int part_blks; + void *buf; + size_t len; +}; + +#define BF_VAL(v, bf) (((v) & bf##_MASK) >> bf##_OFFSET) +#define GETBIT(v,n) (((v) >> (n)) & 0x1) + + +static uint8_t calculate_parity_13_8(uint8_t d) +{ + uint8_t p = 0; + + p |= (GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 3) ^ GETBIT(d, 2)) << 0; + p |= (GETBIT(d, 7) ^ GETBIT(d, 5) ^ GETBIT(d, 4) ^ GETBIT(d, 2) ^ GETBIT(d, 1)) << 1; + p |= (GETBIT(d, 7) ^ GETBIT(d, 6) ^ GETBIT(d, 5) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 2; + p |= (GETBIT(d, 7) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 0)) << 3; + p |= (GETBIT(d, 6) ^ GETBIT(d, 4) ^ GETBIT(d, 3) ^ GETBIT(d, 2) ^ GETBIT(d, 1) ^ GETBIT(d, 0)) << 4; + return p; +} + +static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) +{ + int i; + uint8_t *src = _src; + uint8_t *ecc = _ecc; + + for (i = 0; i < size; i++) + ecc[i] = calculate_parity_13_8(src[i]); +} + +static uint32_t calc_chksum(void *buf, size_t size) +{ + u32 chksum = 0; + u8 *bp = buf; + size_t i; + + for (i = 0; i < size; i++) + chksum += bp[i]; + + return ~chksum; +} + + +static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) +{ + struct mtd_oob_ops ops; + ssize_t ret; + + ops.mode = MTD_OPS_RAW; + ops.ooboffs = 0; + ops.datbuf = buf; + ops.len = mtd->writesize; + ops.oobbuf = buf + mtd->writesize; + ops.ooblen = mtd->oobsize; + ret = mtd_write_oob(mtd, offset, &ops); + + return ret; +} + +static int fcb_create(struct fcb_block *fcb, struct mtd_info *mtd) +{ + fcb->FingerPrint = 0x20424346; + fcb->Version = 0x01000000; + fcb->PageDataSize = mtd->writesize; + fcb->TotalPageSize = mtd->writesize + mtd->oobsize; + fcb->SectorsPerBlock = mtd->erasesize / mtd->writesize; + + /* Divide ECC strength by two and save the value into FCB structure. */ + fcb->EccBlock0EccType = + mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1; + + fcb->EccBlockNEccType = fcb->EccBlock0EccType; + + /* Also hardcoded in kobs-ng */ + fcb->EccBlock0Size = 0x00000200; + fcb->EccBlockNSize = 0x00000200; + fcb->DataSetup = 80; + fcb->DataHold = 60; + fcb->AddressSetup = 25; + fcb->DSAMPLE_TIME = 6; + fcb->MetadataBytes = 10; + + /* DBBT search area starts at second page on first block */ + fcb->DBBTSearchAreaStartAddress = 1; + + fcb->BadBlockMarkerByte = mxs_nand_mark_byte_offset(mtd); + fcb->BadBlockMarkerStartBit = mxs_nand_mark_bit_offset(mtd); + + fcb->BBMarkerPhysicalOffset = mtd->writesize; + + fcb->NumEccBlocksPerPage = mtd->writesize / fcb->EccBlock0Size - 1; + + fcb->Checksum = calc_chksum((void *)fcb + 4, sizeof(*fcb) - 4); + + return 0; +} + +/* Erase entire U-Boot partition */ +static int mxs_nand_uboot_erase(struct mtd_info *mtd, struct part_info *part) +{ + uint64_t offset = 0; + struct erase_info erase; + int len = part->size; + int ret; + + while (len > 0) { + pr_debug("erasing at 0x%08llx\n", offset); + if (mtd_block_isbad(mtd, offset)) { + pr_debug("erase skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + continue; + } + + memset(&erase, 0, sizeof(erase)); + erase.addr = offset; + erase.len = mtd->erasesize; + + ret = mtd_erase(mtd, &erase); + if (ret) + return ret; + + offset += mtd->erasesize; + len -= mtd->erasesize; + } + + return 0; +} + +/* Write the U-Boot proper (2 copies) to where it belongs. */ +/* This is U-Boot binary image itself, no FCB/DBBT here yet. */ +static int mxs_nand_uboot_write_fw(struct mtd_info *mtd, struct fw_write_data *fw) +{ + uint64_t offset; + int ret; + int blk; + size_t dummy; + size_t bytes_left; + int chunk; + void *p; + + bytes_left = fw->len; + p = fw->buf; + blk = fw->fw1_blk; + offset = blk * mtd->erasesize; + + while (bytes_left > 0) { + chunk = min(bytes_left, mtd->erasesize); + + pr_debug("fw1: writing %p at 0x%08llx, left 0x%08x\n", + p, offset, bytes_left); + + if (mtd_block_isbad(mtd, offset)) { + pr_debug("fw1: write skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + blk++; + continue; + } + + if (blk >= fw->fw2_blk) + return -ENOSPC; + + ret = mtd_write(mtd, offset, chunk, &dummy, p); + if (ret) + return ret; + + offset += chunk; + bytes_left -= chunk; + p += chunk; + blk++; + } + + bytes_left = fw->len; + p = fw->buf; + blk = fw->fw2_blk; + offset = blk * mtd->erasesize; + + while (bytes_left > 0) { + chunk = min(bytes_left, mtd->erasesize); + + pr_debug("fw2: writing %p at 0x%08llx, left 0x%08x\n", + p, offset, bytes_left); + + if (mtd_block_isbad(mtd, offset)) { + pr_debug("fw2: write skip block @ 0x%08llx\n", offset); + offset += mtd->erasesize; + blk++; + continue; + } + + if (blk >= fw->part_blks) + return -ENOSPC; + + ret = mtd_write(mtd, offset, chunk, &dummy, p); + if (ret) + return ret; + + offset += chunk; + bytes_left -= chunk; + p += chunk; + blk++; + } + + return 0; +} + +static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) +{ + int n; + int n_bad_blocks = 0; + uint32_t *bb = buf + 0x8; + uint32_t *n_bad_blocksp = buf + 0x4; + + for (n = 0; n < num_blocks; n++) { + loff_t offset = n * mtd->erasesize; + if (mtd_block_isbad(mtd, offset)) { + n_bad_blocks++; + *bb = n; + bb++; + } + } + + *n_bad_blocksp = n_bad_blocks; + + return n_bad_blocks; +} + +/********************************************************************/ +/* This is where it is all done. Takes pointer to a U-Boot image in */ +/* RAM and image size, creates FCB/DBBT and writes everything where */ +/* it belongs into NAND. Image must be an IMX image built for NAND. */ +/********************************************************************/ +static int mxs_nand_uboot_update(const void *img, size_t len) +{ + int i, ret; + + size_t dummy; + loff_t offset = 0; + + void *fcb_raw_page; + void *dbbt_page; + void *dbbt_data_page; + void *ecc; + + uint32_t num_blocks_fcb_dbbt; + uint32_t num_blocks_fw; + + struct mtd_info *mtd; + struct fcb_block *fcb; + struct dbbt_block *dbbt; + + struct mtd_device *dev; + struct part_info *part; + u8 pnum; + + struct fw_write_data fw; + + if ((mtdparts_init() == 0) && + (find_dev_and_part(uboot_tgt, &dev, &pnum, &part) == 0)) { + if (dev->id->type != MTD_DEV_TYPE_NAND) { + puts("Not a NAND device\n"); + return -ENODEV; + } + } + + nand_curr_device = dev->id->num; + +#ifdef CONFIG_SYS_NAND_SELECT_DEVICE + board_nand_select_device(nand_info[nand_curr_device].priv, nand_curr_device); +#endif + + /* Get a pointer to mtd_info for selected device */ + + mtd = get_mtd_device_nm("nand0"); /* We always boot off of nand0 */ + + if (IS_ERR(mtd)) { + /* Should not happen */ + puts("No nand0 device...\n"); + return -ENODEV; + } + + put_mtd_device(mtd); + + /* Quick and dirty check if we have 2Mbytes of good blocks in nand0,0 */ + /* Not sure if it is needed at all but won't hurt so here it is... */ + + i = 0; + offset = 0; /* It is the first partition so it starts at block 0 */ + + while (offset < part->size) { + if (!mtd_block_isbad(mtd, offset)) { + i += mtd->erasesize; + } + offset += mtd->erasesize; + } + + if (i < SZ_2M) { + puts("Partition too small for U-Boot!\n"); + return -EINVAL; + } + + /* We will use 4 first blocks for FCB/DBBT copies. */ + /* The rest of partition is split in half and used */ + /* for two U-Boot copies. We don't care if those */ + /* start on good or bad block - bad blocks will be */ + /* skipped on write, ROM boot code will also skip */ + /* bad blocks on bootup when loading U-Boot image. */ + + fw.part_blks = part->size / mtd->erasesize; + num_blocks_fcb_dbbt = 4; + num_blocks_fw = (fw.part_blks - num_blocks_fcb_dbbt) / 2; + fw.fw1_blk = num_blocks_fcb_dbbt; + fw.fw2_blk = fw.fw1_blk + num_blocks_fw; + + /* OK, now create FCB structure for bootROM NAND boot */ + + fcb_raw_page = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + + fcb = fcb_raw_page + 12; + ecc = fcb_raw_page + 512 + 12; + + dbbt_page = kzalloc(mtd->writesize, GFP_KERNEL); + dbbt_data_page = kzalloc(mtd->writesize, GFP_KERNEL); + dbbt = dbbt_page; + + /* Write one additional page to make the ROM happy. */ + /* Maybe the PagesInFirmwarex fields are really the */ + /* number of pages - 1. kobs-ng does the same. */ + + fw.len = ALIGN(len + FLASH_OFFSET_STANDARD + mtd->writesize, mtd->writesize); + fw.buf = kzalloc(fw.len, GFP_KERNEL); + memcpy(fw.buf + FLASH_OFFSET_STANDARD, img, len); + + /* Erase entire partition */ + ret = mxs_nand_uboot_erase(mtd, part); + if (ret) + goto out; + + /* Now write 2 copies of the U-Boot proper to where they belong. */ + /* Headers (FCB, DBBT) will be generated and written after that. */ + ret = mxs_nand_uboot_write_fw(mtd, &fw); + if (ret < 0) + goto out; + + /* Create FCB, calculate ECC (we don't/can't use hardware ECC */ + /* here so we do it ourselves and then write _RAW_ pages. */ + + fcb->Firmware1_startingPage = fw.fw1_blk * mtd->erasesize / mtd->writesize; + fcb->Firmware2_startingPage = fw.fw2_blk * mtd->erasesize / mtd->writesize; + fcb->PagesInFirmware1 = + ALIGN(len + FLASH_OFFSET_STANDARD, mtd->writesize) / mtd->writesize; + fcb->PagesInFirmware2 = fcb->PagesInFirmware1; + + fcb_create(fcb, mtd); + encode_hamming_13_8(fcb, ecc, 512); + + /* + * Set the first and second byte of OOB data to 0xFF, not 0x00. These + * bytes are used as the Manufacturers Bad Block Marker (MBBM). Since + * the FCB is mostly written to the first page in a block, a scan for + * factory bad blocks will detect these blocks as bad, e.g. when + * function nand_scan_bbt() is executed to build a new bad block table. + * We will _NOT_ mark a bad block as good -- we skip the bad blocks. + */ + memset(fcb_raw_page + mtd->writesize, 0xff, 2); + + /* Now create DBBT */ + dbbt->Checksum = 0; + dbbt->FingerPrint = 0x54424244; + dbbt->Version = 0x01000000; + + if ((ret = dbbt_data_create(mtd, dbbt_data_page, fw.part_blks)) < 0) + goto out; + + if (ret > 0) + dbbt->DBBTNumOfPages = 1; + + offset = 0; + + if (mtd_block_isbad(mtd, offset)) { + puts("Block 0 is bad, NAND unusable\n"); + ret = -EIO; + goto out; + } + + /* Write FCB/DBBT to first 4 blocks. Skip bad blocks if any. */ + /* Less than 4 copies will be written if there were BBs !!! */ + for (i = 0; i < 4; i++) { + + if (mtd_block_isbad(mtd, offset)) { + pr_err("Block %d is bad, skipped\n", i); + continue; + } + + + ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i); + if (ret) + goto out; + + ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize, + mtd->writesize, &dummy, dbbt_page); + if (ret) + goto out; + + /* DBBTNumOfPages == 0 if no bad blocks */ + if (dbbt->DBBTNumOfPages > 0) { + ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize * 5, + mtd->writesize, &dummy, dbbt_data_page); + if (ret) + goto out; + } + } + +out: + kfree(dbbt_page); + kfree(dbbt_data_page); + kfree(fcb_raw_page); + kfree(fw.buf); + + return ret; +} + + +int mxs_do_nand_bootupdate(ulong addr, size_t len) +{ + /* KSI: Unlock NAND first if it is locked... */ + + return mxs_nand_uboot_update((const void *)addr, len); +}