uboot/drivers/dfu/dfu_nand.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * dfu_nand.c -- DFU for NAND routines.
   4 *
   5 * Copyright (C) 2012-2013 Texas Instruments, Inc.
   6 *
   7 * Based on dfu_mmc.c which is:
   8 * Copyright (C) 2012 Samsung Electronics
   9 * author: Lukasz Majewski <l.majewski@samsung.com>
  10 */
  11
  12#include <common.h>
  13#include <malloc.h>
  14#include <errno.h>
  15#include <div64.h>
  16#include <dfu.h>
  17#include <linux/mtd/mtd.h>
  18#include <jffs2/load_kernel.h>
  19#include <nand.h>
  20
  21static int nand_block_op(enum dfu_op op, struct dfu_entity *dfu,
  22                        u64 offset, void *buf, long *len)
  23{
  24        loff_t start, lim;
  25        size_t count, actual;
  26        int ret;
  27        struct mtd_info *mtd;
  28
  29        /* if buf == NULL return total size of the area */
  30        if (buf == NULL) {
  31                *len = dfu->data.nand.size;
  32                return 0;
  33        }
  34
  35        start = dfu->data.nand.start + offset + dfu->bad_skip;
  36        lim = dfu->data.nand.start + dfu->data.nand.size - start;
  37        count = *len;
  38
  39        mtd = get_nand_dev_by_index(nand_curr_device);
  40
  41        if (nand_curr_device < 0 ||
  42            nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
  43            !mtd) {
  44                printf("%s: invalid nand device\n", __func__);
  45                return -1;
  46        }
  47
  48        if (op == DFU_OP_READ) {
  49                ret = nand_read_skip_bad(mtd, start, &count, &actual,
  50                                         lim, buf);
  51        } else {
  52                nand_erase_options_t opts;
  53
  54                memset(&opts, 0, sizeof(opts));
  55                opts.offset = start;
  56                opts.length = count;
  57                opts.spread = 1;
  58                opts.quiet = 1;
  59                opts.lim = lim;
  60                /* first erase */
  61                ret = nand_erase_opts(mtd, &opts);
  62                if (ret)
  63                        return ret;
  64                /* then write */
  65                ret = nand_write_skip_bad(mtd, start, &count, &actual,
  66                                          lim, buf, WITH_WR_VERIFY);
  67        }
  68
  69        if (ret != 0) {
  70                printf("%s: nand_%s_skip_bad call failed at %llx!\n",
  71                       __func__, op == DFU_OP_READ ? "read" : "write",
  72                       start);
  73                return ret;
  74        }
  75
  76        /*
  77         * Find out where we stopped writing data.  This can be deeper into
  78         * the NAND than we expected due to having to skip bad blocks.  So
  79         * we must take this into account for the next write, if any.
  80         */
  81        if (actual > count)
  82                dfu->bad_skip += actual - count;
  83
  84        return ret;
  85}
  86
  87static inline int nand_block_write(struct dfu_entity *dfu,
  88                u64 offset, void *buf, long *len)
  89{
  90        return nand_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
  91}
  92
  93static inline int nand_block_read(struct dfu_entity *dfu,
  94                u64 offset, void *buf, long *len)
  95{
  96        return nand_block_op(DFU_OP_READ, dfu, offset, buf, len);
  97}
  98
  99static int dfu_write_medium_nand(struct dfu_entity *dfu,
 100                u64 offset, void *buf, long *len)
 101{
 102        int ret = -1;
 103
 104        switch (dfu->layout) {
 105        case DFU_RAW_ADDR:
 106                ret = nand_block_write(dfu, offset, buf, len);
 107                break;
 108        default:
 109                printf("%s: Layout (%s) not (yet) supported!\n", __func__,
 110                       dfu_get_layout(dfu->layout));
 111        }
 112
 113        return ret;
 114}
 115
 116int dfu_get_medium_size_nand(struct dfu_entity *dfu, u64 *size)
 117{
 118        *size = dfu->data.nand.size;
 119
 120        return 0;
 121}
 122
 123static int dfu_read_medium_nand(struct dfu_entity *dfu, u64 offset, void *buf,
 124                long *len)
 125{
 126        int ret = -1;
 127
 128        switch (dfu->layout) {
 129        case DFU_RAW_ADDR:
 130                ret = nand_block_read(dfu, offset, buf, len);
 131                break;
 132        default:
 133                printf("%s: Layout (%s) not (yet) supported!\n", __func__,
 134                       dfu_get_layout(dfu->layout));
 135        }
 136
 137        return ret;
 138}
 139
 140static int dfu_flush_medium_nand(struct dfu_entity *dfu)
 141{
 142        int ret = 0;
 143        u64 off;
 144
 145        /* in case of ubi partition, erase rest of the partition */
 146        if (dfu->data.nand.ubi) {
 147                struct mtd_info *mtd = get_nand_dev_by_index(nand_curr_device);
 148                nand_erase_options_t opts;
 149
 150                if (nand_curr_device < 0 ||
 151                    nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
 152                    !mtd) {
 153                        printf("%s: invalid nand device\n", __func__);
 154                        return -1;
 155                }
 156
 157                memset(&opts, 0, sizeof(opts));
 158                off = dfu->offset;
 159                if ((off & (mtd->erasesize - 1)) != 0) {
 160                        /*
 161                         * last write ended with unaligned length
 162                         * sector is erased, jump to next
 163                         */
 164                        off = off & ~((mtd->erasesize - 1));
 165                        off += mtd->erasesize;
 166                }
 167                opts.offset = dfu->data.nand.start + off +
 168                                dfu->bad_skip;
 169                opts.length = dfu->data.nand.start +
 170                                dfu->data.nand.size - opts.offset;
 171                ret = nand_erase_opts(mtd, &opts);
 172                if (ret != 0)
 173                        printf("Failure erase: %d\n", ret);
 174        }
 175
 176        return ret;
 177}
 178
 179unsigned int dfu_polltimeout_nand(struct dfu_entity *dfu)
 180{
 181        /*
 182         * Currently, Poll Timeout != 0 is only needed on nand
 183         * ubi partition, as the not used sectors need an erase
 184         */
 185        if (dfu->data.nand.ubi)
 186                return DFU_MANIFEST_POLL_TIMEOUT;
 187
 188        return DFU_DEFAULT_POLL_TIMEOUT;
 189}
 190
 191int dfu_fill_entity_nand(struct dfu_entity *dfu, char *devstr, char *s)
 192{
 193        char *st;
 194        int ret, dev, part;
 195
 196        dfu->data.nand.ubi = 0;
 197        dfu->dev_type = DFU_DEV_NAND;
 198        st = strsep(&s, " ");
 199        if (!strcmp(st, "raw")) {
 200                dfu->layout = DFU_RAW_ADDR;
 201                dfu->data.nand.start = simple_strtoul(s, &s, 16);
 202                s++;
 203                dfu->data.nand.size = simple_strtoul(s, &s, 16);
 204        } else if ((!strcmp(st, "part")) || (!strcmp(st, "partubi"))) {
 205                char mtd_id[32];
 206                struct mtd_device *mtd_dev;
 207                u8 part_num;
 208                struct part_info *pi;
 209
 210                dfu->layout = DFU_RAW_ADDR;
 211
 212                dev = simple_strtoul(s, &s, 10);
 213                s++;
 214                part = simple_strtoul(s, &s, 10);
 215
 216                sprintf(mtd_id, "%s%d,%d", "nand", dev, part - 1);
 217                debug("using id '%s'\n", mtd_id);
 218
 219                mtdparts_init();
 220
 221                ret = find_dev_and_part(mtd_id, &mtd_dev, &part_num, &pi);
 222                if (ret != 0) {
 223                        printf("Could not locate '%s'\n", mtd_id);
 224                        return -1;
 225                }
 226
 227                dfu->data.nand.start = pi->offset;
 228                dfu->data.nand.size = pi->size;
 229                if (!strcmp(st, "partubi"))
 230                        dfu->data.nand.ubi = 1;
 231        } else {
 232                printf("%s: Memory layout (%s) not supported!\n", __func__, st);
 233                return -1;
 234        }
 235
 236        dfu->get_medium_size = dfu_get_medium_size_nand;
 237        dfu->read_medium = dfu_read_medium_nand;
 238        dfu->write_medium = dfu_write_medium_nand;
 239        dfu->flush_medium = dfu_flush_medium_nand;
 240        dfu->poll_timeout = dfu_polltimeout_nand;
 241
 242        /* initial state */
 243        dfu->inited = 0;
 244
 245        return 0;
 246}
 247