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