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 21 static 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 87 static 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 93 static 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 99 static 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 116 int dfu_get_medium_size_nand(struct dfu_entity *dfu, u64 *size) 117 { 118 *size = dfu->data.nand.size; 119 120 return 0; 121 } 122 123 static 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 140 static 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 179 unsigned 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 191 int 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 printf("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