1054229abSJason Zhu /*
2054229abSJason Zhu * (C) Copyright 2019 Rockchip Electronics Co., Ltd
3054229abSJason Zhu *
4054229abSJason Zhu * SPDX-License-Identifier: GPL-2.0+
5054229abSJason Zhu */
6054229abSJason Zhu
7054229abSJason Zhu #include <common.h>
822dccd11SJason Zhu #include <blk.h>
922dccd11SJason Zhu #include <boot_rkimg.h>
10054229abSJason Zhu #include <dm.h>
11054229abSJason Zhu #include <errno.h>
12661bcdfeSJason Zhu #include <image.h>
13479a1588SJon Lin #include <linux/log2.h>
1422dccd11SJason Zhu #include <malloc.h>
15054229abSJason Zhu #include <nand.h>
1622dccd11SJason Zhu #include <part.h>
173fb7bf02SJason Zhu #include <spi.h>
18054229abSJason Zhu #include <dm/device-internal.h>
196af5fcf6SJon Lin #include <linux/mtd/spinand.h>
203fb7bf02SJason Zhu #include <linux/mtd/spi-nor.h>
216524556dSJon Lin #ifdef CONFIG_NAND
226524556dSJon Lin #include <linux/mtd/nand.h>
236524556dSJon Lin #endif
24054229abSJason Zhu
251a3d87beSJon Lin // #define MTD_BLK_VERBOSE
261a3d87beSJon Lin
2722dccd11SJason Zhu #define MTD_PART_NAND_HEAD "mtdparts="
2822dccd11SJason Zhu #define MTD_PART_INFO_MAX_SIZE 512
2922dccd11SJason Zhu #define MTD_SINGLE_PART_INFO_MAX_SIZE 40
3022dccd11SJason Zhu
31c9e94690SJon Lin #define MTD_BLK_TABLE_BLOCK_UNKNOWN (-2)
32c9e94690SJon Lin #define MTD_BLK_TABLE_BLOCK_SHIFT (-1)
33c9e94690SJon Lin
34106a71f0SJon Lin #define FACTORY_UNKNOWN_LBA (0xffffffff - 34)
35106a71f0SJon Lin
361f21bf61SJon Lin static int *mtd_map_blk_table;
371f21bf61SJon Lin
38bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
39bee19e1dSJon Lin static loff_t usbplug_dummy_partition_write_last_addr;
40bee19e1dSJon Lin static loff_t usbplug_dummy_partition_write_seek;
41bee19e1dSJon Lin static loff_t usbplug_dummy_partition_read_last_addr;
42bee19e1dSJon Lin static loff_t usbplug_dummy_partition_read_seek;
43bee19e1dSJon Lin #endif
44bee19e1dSJon Lin
mtd_blk_map_table_init(struct blk_desc * desc,loff_t offset,size_t length)451f21bf61SJon Lin int mtd_blk_map_table_init(struct blk_desc *desc,
461f21bf61SJon Lin loff_t offset,
471f21bf61SJon Lin size_t length)
481f21bf61SJon Lin {
491f21bf61SJon Lin u32 blk_total, blk_begin, blk_cnt;
501f21bf61SJon Lin struct mtd_info *mtd = NULL;
511f21bf61SJon Lin int i, j;
521f21bf61SJon Lin
531f21bf61SJon Lin if (!desc)
541f21bf61SJon Lin return -ENODEV;
551f21bf61SJon Lin
566524556dSJon Lin switch (desc->devnum) {
576524556dSJon Lin case BLK_MTD_NAND:
586524556dSJon Lin case BLK_MTD_SPI_NAND:
591f21bf61SJon Lin mtd = desc->bdev->priv;
606524556dSJon Lin break;
616524556dSJon Lin default:
626524556dSJon Lin break;
631f21bf61SJon Lin }
641f21bf61SJon Lin
651f21bf61SJon Lin if (!mtd) {
661f21bf61SJon Lin return -ENODEV;
671f21bf61SJon Lin } else {
68b4e07918SJon Lin blk_total = (mtd->size + mtd->erasesize - 1) >> mtd->erasesize_shift;
691f21bf61SJon Lin if (!mtd_map_blk_table) {
700f1dc487SJon Lin mtd_map_blk_table = (int *)malloc(blk_total * sizeof(int));
716524556dSJon Lin if (!mtd_map_blk_table)
726524556dSJon Lin return -ENOMEM;
730f1dc487SJon Lin for (i = 0; i < blk_total; i++)
740f1dc487SJon Lin mtd_map_blk_table[i] = MTD_BLK_TABLE_BLOCK_UNKNOWN;
751f21bf61SJon Lin }
761f21bf61SJon Lin
77b4e07918SJon Lin blk_begin = (u32)offset >> mtd->erasesize_shift;
78e091dc9dSJon Lin blk_cnt = ((u32)((offset & mtd->erasesize_mask) + length + \
79e091dc9dSJon Lin mtd->erasesize - 1) >> mtd->erasesize_shift);
800f1dc487SJon Lin if (blk_begin >= blk_total) {
810f1dc487SJon Lin pr_err("map table blk begin[%d] overflow\n", blk_begin);
820f1dc487SJon Lin return -EINVAL;
830f1dc487SJon Lin }
84d6290238SJon Lin if ((blk_begin + blk_cnt) > blk_total)
85d6290238SJon Lin blk_cnt = blk_total - blk_begin;
86c9e94690SJon Lin
87c9e94690SJon Lin if (mtd_map_blk_table[blk_begin] != MTD_BLK_TABLE_BLOCK_UNKNOWN)
88c9e94690SJon Lin return 0;
89c9e94690SJon Lin
901f21bf61SJon Lin j = 0;
911f21bf61SJon Lin /* should not across blk_cnt */
921f21bf61SJon Lin for (i = 0; i < blk_cnt; i++) {
931f21bf61SJon Lin if (j >= blk_cnt)
94c9e94690SJon Lin mtd_map_blk_table[blk_begin + i] = MTD_BLK_TABLE_BLOCK_SHIFT;
951f21bf61SJon Lin for (; j < blk_cnt; j++) {
96b4e07918SJon Lin if (!mtd_block_isbad(mtd, (blk_begin + j) << mtd->erasesize_shift)) {
971f21bf61SJon Lin mtd_map_blk_table[blk_begin + i] = blk_begin + j;
981f21bf61SJon Lin j++;
991f21bf61SJon Lin if (j == blk_cnt)
1001f21bf61SJon Lin j++;
1011f21bf61SJon Lin break;
1021f21bf61SJon Lin }
1031f21bf61SJon Lin }
1041f21bf61SJon Lin }
1051f21bf61SJon Lin
1061f21bf61SJon Lin return 0;
1071f21bf61SJon Lin }
1081f21bf61SJon Lin }
1091f21bf61SJon Lin
get_mtd_blk_map_address(struct mtd_info * mtd,loff_t * off)110c402731fSJon Lin static bool get_mtd_blk_map_address(struct mtd_info *mtd, loff_t *off)
111c402731fSJon Lin {
112c402731fSJon Lin bool mapped;
113c402731fSJon Lin loff_t offset = *off;
114c402731fSJon Lin size_t block_offset = offset & (mtd->erasesize - 1);
115c402731fSJon Lin
116c402731fSJon Lin mapped = false;
117c402731fSJon Lin if (!mtd_map_blk_table ||
118c402731fSJon Lin mtd_map_blk_table[(u64)offset >> mtd->erasesize_shift] ==
119c402731fSJon Lin MTD_BLK_TABLE_BLOCK_UNKNOWN ||
120c402731fSJon Lin mtd_map_blk_table[(u64)offset >> mtd->erasesize_shift] ==
121c402731fSJon Lin 0xffffffff)
122c402731fSJon Lin return mapped;
123c402731fSJon Lin
124c402731fSJon Lin mapped = true;
125c402731fSJon Lin *off = (loff_t)(((u32)mtd_map_blk_table[(u64)offset >>
126c402731fSJon Lin mtd->erasesize_shift] << mtd->erasesize_shift) + block_offset);
127c402731fSJon Lin
128c402731fSJon Lin return mapped;
129c402731fSJon Lin }
130c402731fSJon Lin
mtd_blk_map_partitions(struct blk_desc * desc)131a07b97f2SJason Zhu void mtd_blk_map_partitions(struct blk_desc *desc)
132a07b97f2SJason Zhu {
133a07b97f2SJason Zhu disk_partition_t info;
134a07b97f2SJason Zhu int i, ret;
135a07b97f2SJason Zhu
136a07b97f2SJason Zhu if (!desc)
137a07b97f2SJason Zhu return;
138a07b97f2SJason Zhu
139a07b97f2SJason Zhu if (desc->if_type != IF_TYPE_MTD)
140a07b97f2SJason Zhu return;
141a07b97f2SJason Zhu
142a07b97f2SJason Zhu for (i = 1; i < MAX_SEARCH_PARTITIONS; i++) {
143a07b97f2SJason Zhu ret = part_get_info(desc, i, &info);
144a07b97f2SJason Zhu if (ret != 0)
14535d02c8bSJon Lin break;
146a07b97f2SJason Zhu
147a07b97f2SJason Zhu if (mtd_blk_map_table_init(desc,
148a07b97f2SJason Zhu info.start << 9,
149a07b97f2SJason Zhu info.size << 9)) {
1509ee38883SJon Lin pr_debug("mtd block map table fail\n");
151a07b97f2SJason Zhu }
152a07b97f2SJason Zhu }
153a07b97f2SJason Zhu }
154a07b97f2SJason Zhu
mtd_blk_map_fit(struct blk_desc * desc,ulong sector,void * fit)155661bcdfeSJason Zhu void mtd_blk_map_fit(struct blk_desc *desc, ulong sector, void *fit)
156661bcdfeSJason Zhu {
157661bcdfeSJason Zhu struct mtd_info *mtd = NULL;
158661bcdfeSJason Zhu int totalsize = 0;
159661bcdfeSJason Zhu
160661bcdfeSJason Zhu if (desc->if_type != IF_TYPE_MTD)
161661bcdfeSJason Zhu return;
162661bcdfeSJason Zhu
163661bcdfeSJason Zhu if (desc->devnum == BLK_MTD_NAND) {
164661bcdfeSJason Zhu #if defined(CONFIG_NAND)
165661bcdfeSJason Zhu mtd = dev_get_priv(desc->bdev->parent);
166661bcdfeSJason Zhu #endif
167661bcdfeSJason Zhu } else if (desc->devnum == BLK_MTD_SPI_NAND) {
168661bcdfeSJason Zhu #if defined(CONFIG_MTD_SPI_NAND)
169661bcdfeSJason Zhu mtd = desc->bdev->priv;
170661bcdfeSJason Zhu #endif
171661bcdfeSJason Zhu }
172661bcdfeSJason Zhu
173661bcdfeSJason Zhu #ifdef CONFIG_SPL_FIT
174661bcdfeSJason Zhu if (fit_get_totalsize(fit, &totalsize))
175661bcdfeSJason Zhu debug("Can not find /totalsize node.\n");
176661bcdfeSJason Zhu #endif
177661bcdfeSJason Zhu if (mtd && totalsize) {
178661bcdfeSJason Zhu if (mtd_blk_map_table_init(desc, sector << 9, totalsize + (size_t)mtd->erasesize))
179661bcdfeSJason Zhu debug("Map block table fail.\n");
180661bcdfeSJason Zhu }
181661bcdfeSJason Zhu }
182661bcdfeSJason Zhu
mtd_map_read(struct mtd_info * mtd,loff_t offset,size_t * length,size_t * actual,loff_t lim,u_char * buffer)1831f21bf61SJon Lin static __maybe_unused int mtd_map_read(struct mtd_info *mtd, loff_t offset,
1841f21bf61SJon Lin size_t *length, size_t *actual,
1851f21bf61SJon Lin loff_t lim, u_char *buffer)
1861f21bf61SJon Lin {
1871f21bf61SJon Lin size_t left_to_read = *length;
1881f21bf61SJon Lin u_char *p_buffer = buffer;
1891f21bf61SJon Lin int rval;
1901f21bf61SJon Lin
191bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
192bee19e1dSJon Lin if (usbplug_dummy_partition_read_last_addr != offset)
193bee19e1dSJon Lin usbplug_dummy_partition_read_seek = 0;
194bee19e1dSJon Lin usbplug_dummy_partition_read_last_addr = offset + left_to_read;
195bee19e1dSJon Lin offset += usbplug_dummy_partition_read_seek;
196bee19e1dSJon Lin #endif
197bee19e1dSJon Lin
1981f21bf61SJon Lin while (left_to_read > 0) {
199360a2911SJon Lin size_t block_offset = offset & (mtd->erasesize - 1);
2001f21bf61SJon Lin size_t read_length;
201360a2911SJon Lin loff_t mapped_offset;
2021f21bf61SJon Lin
2031f21bf61SJon Lin if (offset >= mtd->size)
2041f21bf61SJon Lin return 0;
2051f21bf61SJon Lin
206360a2911SJon Lin mapped_offset = offset;
207360a2911SJon Lin if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
208360a2911SJon Lin if (mtd_block_isbad(mtd, mapped_offset &
209360a2911SJon Lin ~(mtd->erasesize - 1))) {
210bee19e1dSJon Lin printf("Skipping bad block 0x%08x in read\n",
211bee19e1dSJon Lin (u32)(offset & ~(mtd->erasesize - 1)));
212360a2911SJon Lin offset += mtd->erasesize - block_offset;
213bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
214bee19e1dSJon Lin usbplug_dummy_partition_read_seek += mtd->erasesize;
215bee19e1dSJon Lin #endif
2161f21bf61SJon Lin continue;
2171f21bf61SJon Lin }
2181f21bf61SJon Lin }
2191f21bf61SJon Lin
220360a2911SJon Lin if (left_to_read < (mtd->erasesize - block_offset))
2211f21bf61SJon Lin read_length = left_to_read;
2221f21bf61SJon Lin else
223360a2911SJon Lin read_length = mtd->erasesize - block_offset;
2241f21bf61SJon Lin
225360a2911SJon Lin rval = mtd_read(mtd, mapped_offset, read_length, &read_length,
2261f21bf61SJon Lin p_buffer);
2271f21bf61SJon Lin if (rval && rval != -EUCLEAN) {
22868fb1b3bSJon Lin printf("NAND read from offset %x failed %d\n",
22968fb1b3bSJon Lin (u32)offset, rval);
2301f21bf61SJon Lin *length -= left_to_read;
2311f21bf61SJon Lin return rval;
2321f21bf61SJon Lin }
2331f21bf61SJon Lin
2341f21bf61SJon Lin left_to_read -= read_length;
2351f21bf61SJon Lin offset += read_length;
2361f21bf61SJon Lin p_buffer += read_length;
2371f21bf61SJon Lin }
2381f21bf61SJon Lin
2391f21bf61SJon Lin return 0;
2401f21bf61SJon Lin }
2411f21bf61SJon Lin
mtd_map_write(struct mtd_info * mtd,loff_t offset,size_t * length,size_t * actual,loff_t lim,u_char * buffer,int flags)2429ee38883SJon Lin static __maybe_unused int mtd_map_write(struct mtd_info *mtd, loff_t offset,
2439ee38883SJon Lin size_t *length, size_t *actual,
2449ee38883SJon Lin loff_t lim, u_char *buffer, int flags)
2459ee38883SJon Lin {
2469ee38883SJon Lin int rval = 0, blocksize;
2479ee38883SJon Lin size_t left_to_write = *length;
2489ee38883SJon Lin u_char *p_buffer = buffer;
2499ee38883SJon Lin struct erase_info ei;
2509ee38883SJon Lin
2519ee38883SJon Lin blocksize = mtd->erasesize;
2529ee38883SJon Lin
253bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
254bee19e1dSJon Lin if (usbplug_dummy_partition_write_last_addr != offset)
255bee19e1dSJon Lin usbplug_dummy_partition_write_seek = 0;
256bee19e1dSJon Lin usbplug_dummy_partition_write_last_addr = offset + left_to_write;
257bee19e1dSJon Lin offset += usbplug_dummy_partition_write_seek;
258bee19e1dSJon Lin #endif
259bee19e1dSJon Lin
2609ee38883SJon Lin /*
2619ee38883SJon Lin * nand_write() handles unaligned, partial page writes.
2629ee38883SJon Lin *
2639ee38883SJon Lin * We allow length to be unaligned, for convenience in
2649ee38883SJon Lin * using the $filesize variable.
2659ee38883SJon Lin *
2669ee38883SJon Lin * However, starting at an unaligned offset makes the
2679ee38883SJon Lin * semantics of bad block skipping ambiguous (really,
2689ee38883SJon Lin * you should only start a block skipping access at a
2699ee38883SJon Lin * partition boundary). So don't try to handle that.
2709ee38883SJon Lin */
2719ee38883SJon Lin if ((offset & (mtd->writesize - 1)) != 0) {
2729ee38883SJon Lin printf("Attempt to write non page-aligned data\n");
2739ee38883SJon Lin *length = 0;
2749ee38883SJon Lin return -EINVAL;
2759ee38883SJon Lin }
2769ee38883SJon Lin
2779ee38883SJon Lin while (left_to_write > 0) {
2789ee38883SJon Lin size_t block_offset = offset & (mtd->erasesize - 1);
2799ee38883SJon Lin size_t write_size, truncated_write_size;
2809ee38883SJon Lin loff_t mapped_offset;
2819ee38883SJon Lin
2829ee38883SJon Lin if (offset >= mtd->size)
2839ee38883SJon Lin return 0;
2849ee38883SJon Lin
2859ee38883SJon Lin mapped_offset = offset;
2869ee38883SJon Lin if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
2879ee38883SJon Lin if (mtd_block_isbad(mtd, mapped_offset &
2889ee38883SJon Lin ~(mtd->erasesize - 1))) {
289bee19e1dSJon Lin printf("Skipping bad block 0x%08x in write\n",
290bee19e1dSJon Lin (u32)(offset & ~(mtd->erasesize - 1)));
2919ee38883SJon Lin offset += mtd->erasesize - block_offset;
292bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
293bee19e1dSJon Lin usbplug_dummy_partition_write_seek += mtd->erasesize;
294bee19e1dSJon Lin #endif
2959ee38883SJon Lin continue;
2969ee38883SJon Lin }
2979ee38883SJon Lin }
2989ee38883SJon Lin
2999ee38883SJon Lin if (!(mapped_offset & mtd->erasesize_mask)) {
3009ee38883SJon Lin memset(&ei, 0, sizeof(struct erase_info));
3019ee38883SJon Lin ei.addr = mapped_offset;
3029ee38883SJon Lin ei.len = mtd->erasesize;
3039ee38883SJon Lin rval = mtd_erase(mtd, &ei);
3049ee38883SJon Lin if (rval) {
3059ee38883SJon Lin pr_info("error %d while erasing %llx\n", rval,
3069ee38883SJon Lin mapped_offset);
3079ee38883SJon Lin return rval;
3089ee38883SJon Lin }
3099ee38883SJon Lin }
3109ee38883SJon Lin
3119ee38883SJon Lin if (left_to_write < (blocksize - block_offset))
3129ee38883SJon Lin write_size = left_to_write;
3139ee38883SJon Lin else
3149ee38883SJon Lin write_size = blocksize - block_offset;
3159ee38883SJon Lin
3169ee38883SJon Lin truncated_write_size = write_size;
3179ee38883SJon Lin rval = mtd_write(mtd, mapped_offset, truncated_write_size,
3189ee38883SJon Lin (size_t *)(&truncated_write_size), p_buffer);
3199ee38883SJon Lin
3209ee38883SJon Lin offset += write_size;
3219ee38883SJon Lin p_buffer += write_size;
3229ee38883SJon Lin
3239ee38883SJon Lin if (rval != 0) {
3249ee38883SJon Lin printf("NAND write to offset %llx failed %d\n",
3259ee38883SJon Lin offset, rval);
3269ee38883SJon Lin *length -= left_to_write;
3279ee38883SJon Lin return rval;
3289ee38883SJon Lin }
3299ee38883SJon Lin
3309ee38883SJon Lin left_to_write -= write_size;
3319ee38883SJon Lin }
3329ee38883SJon Lin
3339ee38883SJon Lin return 0;
3349ee38883SJon Lin }
3359ee38883SJon Lin
mtd_map_erase(struct mtd_info * mtd,loff_t offset,size_t length)336338697c5SJon Lin static __maybe_unused int mtd_map_erase(struct mtd_info *mtd, loff_t offset,
337338697c5SJon Lin size_t length)
338338697c5SJon Lin {
339338697c5SJon Lin struct erase_info ei;
340338697c5SJon Lin loff_t pos, len;
341338697c5SJon Lin int ret;
342338697c5SJon Lin
343338697c5SJon Lin pos = offset;
344338697c5SJon Lin len = length;
345338697c5SJon Lin
346338697c5SJon Lin if ((pos & mtd->erasesize_mask) || (len & mtd->erasesize_mask)) {
347338697c5SJon Lin pr_err("Attempt to erase non block-aligned data, pos= %llx, len= %llx\n",
348338697c5SJon Lin pos, len);
349338697c5SJon Lin
350338697c5SJon Lin return -EINVAL;
351338697c5SJon Lin }
352338697c5SJon Lin
353338697c5SJon Lin while (len) {
354bd676549SJon Lin loff_t mapped_offset;
355bd676549SJon Lin
356bd676549SJon Lin mapped_offset = pos;
357bd676549SJon Lin if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
358338697c5SJon Lin if (mtd_block_isbad(mtd, pos) || mtd_block_isreserved(mtd, pos)) {
359338697c5SJon Lin pr_debug("attempt to erase a bad/reserved block @%llx\n",
360338697c5SJon Lin pos);
361338697c5SJon Lin pos += mtd->erasesize;
362338697c5SJon Lin continue;
363338697c5SJon Lin }
364bd676549SJon Lin }
365338697c5SJon Lin
366338697c5SJon Lin memset(&ei, 0, sizeof(struct erase_info));
367bd676549SJon Lin ei.addr = mapped_offset;
368338697c5SJon Lin ei.len = mtd->erasesize;
369338697c5SJon Lin ret = mtd_erase(mtd, &ei);
370338697c5SJon Lin if (ret) {
371338697c5SJon Lin pr_err("map_erase error %d while erasing %llx\n", ret,
372338697c5SJon Lin pos);
373338697c5SJon Lin return ret;
374338697c5SJon Lin }
375338697c5SJon Lin
376338697c5SJon Lin pos += mtd->erasesize;
377338697c5SJon Lin len -= mtd->erasesize;
378338697c5SJon Lin }
379338697c5SJon Lin
380338697c5SJon Lin return 0;
381338697c5SJon Lin }
382338697c5SJon Lin
mtd_part_parse(struct blk_desc * dev_desc)383c4fe67c2SJason Zhu char *mtd_part_parse(struct blk_desc *dev_desc)
38422dccd11SJason Zhu {
38522dccd11SJason Zhu char mtd_part_info_temp[MTD_SINGLE_PART_INFO_MAX_SIZE] = {0};
38622dccd11SJason Zhu u32 length, data_len = MTD_PART_INFO_MAX_SIZE;
38722dccd11SJason Zhu disk_partition_t info;
38822dccd11SJason Zhu char *mtd_part_info_p;
389c9289eddSJason Zhu struct mtd_info *mtd;
39022dccd11SJason Zhu char *mtd_part_info;
39122dccd11SJason Zhu int ret;
39222dccd11SJason Zhu int p;
39322dccd11SJason Zhu
394c4fe67c2SJason Zhu #ifndef CONFIG_SPL_BUILD
39522dccd11SJason Zhu dev_desc = rockchip_get_bootdev();
396c4fe67c2SJason Zhu #endif
39722dccd11SJason Zhu if (!dev_desc)
39822dccd11SJason Zhu return NULL;
39922dccd11SJason Zhu
400c9289eddSJason Zhu mtd = (struct mtd_info *)dev_desc->bdev->priv;
401ec6d4288SJason Zhu if (!mtd)
402ec6d4288SJason Zhu return NULL;
403ec6d4288SJason Zhu
40422dccd11SJason Zhu mtd_part_info = (char *)calloc(MTD_PART_INFO_MAX_SIZE, sizeof(char));
40522dccd11SJason Zhu if (!mtd_part_info) {
40622dccd11SJason Zhu printf("%s: Fail to malloc!", __func__);
40722dccd11SJason Zhu return NULL;
40822dccd11SJason Zhu }
40922dccd11SJason Zhu
41022dccd11SJason Zhu mtd_part_info_p = mtd_part_info;
41122dccd11SJason Zhu snprintf(mtd_part_info_p, data_len - 1, "%s%s:",
41222dccd11SJason Zhu MTD_PART_NAND_HEAD,
41322dccd11SJason Zhu dev_desc->product);
41422dccd11SJason Zhu data_len -= strlen(mtd_part_info_p);
41522dccd11SJason Zhu mtd_part_info_p = mtd_part_info_p + strlen(mtd_part_info_p);
41622dccd11SJason Zhu
41722dccd11SJason Zhu for (p = 1; p < MAX_SEARCH_PARTITIONS; p++) {
41822dccd11SJason Zhu ret = part_get_info(dev_desc, p, &info);
41922dccd11SJason Zhu if (ret)
42022dccd11SJason Zhu break;
42122dccd11SJason Zhu
42222dccd11SJason Zhu debug("name is %s, start addr is %x\n", info.name,
42322dccd11SJason Zhu (int)(size_t)info.start);
42422dccd11SJason Zhu
42522dccd11SJason Zhu snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
42622dccd11SJason Zhu (int)(size_t)info.size << 9,
42722dccd11SJason Zhu (int)(size_t)info.start << 9,
42822dccd11SJason Zhu info.name);
42922dccd11SJason Zhu snprintf(mtd_part_info_temp, MTD_SINGLE_PART_INFO_MAX_SIZE - 1,
43022dccd11SJason Zhu "0x%x@0x%x(%s)",
43122dccd11SJason Zhu (int)(size_t)info.size << 9,
43222dccd11SJason Zhu (int)(size_t)info.start << 9,
43322dccd11SJason Zhu info.name);
43422dccd11SJason Zhu strcat(mtd_part_info, ",");
435a3e58bf5SJon Lin if (part_get_info(dev_desc, p + 1, &info)) {
436a3e58bf5SJon Lin /* Partition with grow tag in parameter will be resized */
437106a71f0SJon Lin if ((info.size + info.start + 64) >= dev_desc->lba ||
438106a71f0SJon Lin (info.size + info.start - 1) == FACTORY_UNKNOWN_LBA) {
439ce9d2743SJon Lin if (dev_desc->devnum == BLK_MTD_SPI_NOR) {
440ce9d2743SJon Lin /* Nor is 64KB erase block(kernel) and gpt table just
441ce9d2743SJon Lin * resserve 33 sectors for the last partition. This
442ce9d2743SJon Lin * will erase the backup gpt table by user program,
443ce9d2743SJon Lin * so reserve one block.
444ce9d2743SJon Lin */
445ce9d2743SJon Lin snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
446ce9d2743SJon Lin (int)(size_t)(info.size -
447ce9d2743SJon Lin (info.size - 1) %
448ce9d2743SJon Lin (0x10000 >> 9) - 1) << 9,
449ce9d2743SJon Lin (int)(size_t)info.start << 9,
450ce9d2743SJon Lin info.name);
451ce9d2743SJon Lin break;
452ce9d2743SJon Lin } else {
453c9289eddSJason Zhu /* Nand flash is erased by block and gpt table just
454c9289eddSJason Zhu * resserve 33 sectors for the last partition. This
455c9289eddSJason Zhu * will erase the backup gpt table by user program,
456c9289eddSJason Zhu * so reserve one block.
457c9289eddSJason Zhu */
458c9289eddSJason Zhu snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
459c9289eddSJason Zhu (int)(size_t)(info.size -
460c9289eddSJason Zhu (info.size - 1) %
461c9289eddSJason Zhu (mtd->erasesize >> 9) - 1) << 9,
46222dccd11SJason Zhu (int)(size_t)info.start << 9,
46322dccd11SJason Zhu info.name);
46422dccd11SJason Zhu break;
46522dccd11SJason Zhu }
466a3e58bf5SJon Lin } else {
467a3e58bf5SJon Lin snprintf(mtd_part_info_temp, MTD_SINGLE_PART_INFO_MAX_SIZE - 1,
468a3e58bf5SJon Lin "0x%x@0x%x(%s)",
469a3e58bf5SJon Lin (int)(size_t)info.size << 9,
470a3e58bf5SJon Lin (int)(size_t)info.start << 9,
471a3e58bf5SJon Lin info.name);
472a3e58bf5SJon Lin break;
473a3e58bf5SJon Lin }
474ce9d2743SJon Lin }
47522dccd11SJason Zhu length = strlen(mtd_part_info_temp);
47622dccd11SJason Zhu data_len -= length;
47722dccd11SJason Zhu mtd_part_info_p = mtd_part_info_p + length + 1;
47822dccd11SJason Zhu memset(mtd_part_info_temp, 0, MTD_SINGLE_PART_INFO_MAX_SIZE);
47922dccd11SJason Zhu }
48022dccd11SJason Zhu
481*ad31dd99SJon Lin length = strlen(mtd_part_info);
482*ad31dd99SJon Lin if (length > 0 && mtd_part_info[length - 1] == ',')
483*ad31dd99SJon Lin mtd_part_info[length - 1] = '\0';
484*ad31dd99SJon Lin
48522dccd11SJason Zhu return mtd_part_info;
48622dccd11SJason Zhu }
48722dccd11SJason Zhu
mtd_dread(struct udevice * udev,lbaint_t start,lbaint_t blkcnt,void * dst)488054229abSJason Zhu ulong mtd_dread(struct udevice *udev, lbaint_t start,
489054229abSJason Zhu lbaint_t blkcnt, void *dst)
490054229abSJason Zhu {
491054229abSJason Zhu struct blk_desc *desc = dev_get_uclass_platdata(udev);
4920dccd0d8SJason Zhu #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
4936e8ac5a8SJason Zhu loff_t off = (loff_t)(start * 512);
4946e8ac5a8SJason Zhu size_t rwsize = blkcnt * 512;
4950dccd0d8SJason Zhu #endif
4966e8ac5a8SJason Zhu struct mtd_info *mtd;
497bbb83f58SJason Zhu int ret = 0;
4981a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
4991a3d87beSJon Lin ulong us = 1;
5001a3d87beSJon Lin #endif
501054229abSJason Zhu
502054229abSJason Zhu if (!desc)
50339e38ab3SJason Zhu return ret;
504054229abSJason Zhu
5056e8ac5a8SJason Zhu mtd = desc->bdev->priv;
5066e8ac5a8SJason Zhu if (!mtd)
5076e8ac5a8SJason Zhu return 0;
5086e8ac5a8SJason Zhu
509054229abSJason Zhu if (blkcnt == 0)
510054229abSJason Zhu return 0;
511054229abSJason Zhu
5121a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
5131a3d87beSJon Lin us = get_ticks();
5141a3d87beSJon Lin #endif
515054229abSJason Zhu if (desc->devnum == BLK_MTD_NAND) {
5161f21bf61SJon Lin ret = mtd_map_read(mtd, off, &rwsize,
5171f21bf61SJon Lin NULL, mtd->size,
5181f21bf61SJon Lin (u_char *)(dst));
5196e8ac5a8SJason Zhu if (!ret)
5201a3d87beSJon Lin ret = blkcnt;
521054229abSJason Zhu } else if (desc->devnum == BLK_MTD_SPI_NAND) {
5226af5fcf6SJon Lin #if defined(CONFIG_MTD_SPI_NAND)
5236af5fcf6SJon Lin struct spinand_device *spinand = mtd_to_spinand(mtd);
5246af5fcf6SJon Lin struct spi_slave *spi = spinand->slave;
5256af5fcf6SJon Lin size_t retlen_nand;
5266af5fcf6SJon Lin
5276af5fcf6SJon Lin if (desc->op_flag == BLK_PRE_RW) {
5286af5fcf6SJon Lin spi->mode |= SPI_DMA_PREPARE;
5296af5fcf6SJon Lin ret = mtd_read(mtd, off, rwsize,
5306af5fcf6SJon Lin &retlen_nand, (u_char *)(dst));
5316af5fcf6SJon Lin spi->mode &= ~SPI_DMA_PREPARE;
5326af5fcf6SJon Lin if (retlen_nand == rwsize)
5336af5fcf6SJon Lin ret = blkcnt;
5346af5fcf6SJon Lin } else {
5356af5fcf6SJon Lin if (spinand->support_cont_read)
5366af5fcf6SJon Lin ret = mtd_read(mtd, off, rwsize,
5376af5fcf6SJon Lin &retlen_nand,
5386af5fcf6SJon Lin (u_char *)(dst));
5396af5fcf6SJon Lin else
5401f21bf61SJon Lin ret = mtd_map_read(mtd, off, &rwsize,
541bbb83f58SJason Zhu NULL, mtd->size,
542bbb83f58SJason Zhu (u_char *)(dst));
543bbb83f58SJason Zhu if (!ret)
5441a3d87beSJon Lin ret = blkcnt;
5456af5fcf6SJon Lin }
5466af5fcf6SJon Lin #endif
547054229abSJason Zhu } else if (desc->devnum == BLK_MTD_SPI_NOR) {
5480dccd0d8SJason Zhu #if defined(CONFIG_SPI_FLASH_MTD) || defined(CONFIG_SPL_BUILD)
5493fb7bf02SJason Zhu struct spi_nor *nor = (struct spi_nor *)mtd->priv;
5503fb7bf02SJason Zhu struct spi_slave *spi = nor->spi;
5510dccd0d8SJason Zhu size_t retlen_nor;
5520dccd0d8SJason Zhu
5533fb7bf02SJason Zhu if (desc->op_flag == BLK_PRE_RW)
5543fb7bf02SJason Zhu spi->mode |= SPI_DMA_PREPARE;
5551a3d87beSJon Lin ret = mtd_read(mtd, off, rwsize, &retlen_nor, dst);
5563fb7bf02SJason Zhu if (desc->op_flag == BLK_PRE_RW)
557bfdb06edSJon Lin spi->mode &= ~SPI_DMA_PREPARE;
5583fb7bf02SJason Zhu
5590dccd0d8SJason Zhu if (retlen_nor == rwsize)
5601a3d87beSJon Lin ret = blkcnt;
5610dccd0d8SJason Zhu #endif
562054229abSJason Zhu }
5631a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
56413ceb2afSXuhui Lin us = (get_ticks() - us) / (gd->arch.timer_rate_hz / 1000000);
5651a3d87beSJon Lin pr_err("mtd dread %s %lx %lx cost %ldus: %ldMB/s\n\n", mtd->name, start, blkcnt, us, (blkcnt / 2) / ((us + 999) / 1000));
5661a3d87beSJon Lin #else
5671a3d87beSJon Lin pr_debug("mtd dread %s %lx %lx\n\n", mtd->name, start, blkcnt);
5681a3d87beSJon Lin #endif
5691a3d87beSJon Lin
5701a3d87beSJon Lin return ret;
571054229abSJason Zhu }
572054229abSJason Zhu
573a0166cc6SJason Zhu #if CONFIG_IS_ENABLED(MTD_WRITE)
mtd_dwrite(struct udevice * udev,lbaint_t start,lbaint_t blkcnt,const void * src)574054229abSJason Zhu ulong mtd_dwrite(struct udevice *udev, lbaint_t start,
575054229abSJason Zhu lbaint_t blkcnt, const void *src)
576054229abSJason Zhu {
5779ee38883SJon Lin struct blk_desc *desc = dev_get_uclass_platdata(udev);
5789ee38883SJon Lin #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
5799ee38883SJon Lin loff_t off = (loff_t)(start * 512);
5809ee38883SJon Lin size_t rwsize = blkcnt * 512;
5819ee38883SJon Lin #endif
5829ee38883SJon Lin struct mtd_info *mtd;
5839ee38883SJon Lin int ret = 0;
5849ee38883SJon Lin
5859ee38883SJon Lin if (!desc)
5869ee38883SJon Lin return ret;
5879ee38883SJon Lin
5889ee38883SJon Lin mtd = desc->bdev->priv;
5899ee38883SJon Lin if (!mtd)
5909ee38883SJon Lin return 0;
5919ee38883SJon Lin
5929ee38883SJon Lin pr_debug("mtd dwrite %s %lx %lx\n", mtd->name, start, blkcnt);
5939ee38883SJon Lin
5949ee38883SJon Lin if (blkcnt == 0)
5959ee38883SJon Lin return 0;
5969ee38883SJon Lin
597af0bf855SJon Lin if (desc->op_flag & BLK_MTD_CONT_WRITE &&
598af0bf855SJon Lin (start == 1 || ((desc->lba - start) <= 33))) {
599af0bf855SJon Lin printf("Write in GPT area, lba=%ld cnt=%ld\n", start, blkcnt);
600f4688965SJon Lin desc->op_flag &= ~BLK_MTD_CONT_WRITE;
601f4688965SJon Lin }
602f4688965SJon Lin
6039ee38883SJon Lin if (desc->devnum == BLK_MTD_NAND ||
6049ee38883SJon Lin desc->devnum == BLK_MTD_SPI_NAND ||
6059ee38883SJon Lin desc->devnum == BLK_MTD_SPI_NOR) {
606bee19e1dSJon Lin if (desc->op_flag & BLK_MTD_CONT_WRITE) {
607853fc11fSJon Lin ret = mtd_map_write(mtd, off, &rwsize,
608853fc11fSJon Lin NULL, mtd->size,
609853fc11fSJon Lin (u_char *)(src), 0);
610853fc11fSJon Lin if (!ret)
611853fc11fSJon Lin return blkcnt;
612853fc11fSJon Lin else
613853fc11fSJon Lin return 0;
614853fc11fSJon Lin } else {
61542439462SJon Lin lbaint_t off_aligned, alinged;
61642439462SJon Lin size_t rwsize_aligned;
61742439462SJon Lin u8 *p_buf;
61842439462SJon Lin
61942439462SJon Lin alinged = off & mtd->erasesize_mask;
62042439462SJon Lin off_aligned = off - alinged;
62142439462SJon Lin rwsize_aligned = rwsize + alinged;
62242439462SJon Lin rwsize_aligned = (rwsize_aligned + mtd->erasesize - 1) &
62342439462SJon Lin ~(mtd->erasesize - 1);
62442439462SJon Lin
62542439462SJon Lin p_buf = malloc(rwsize_aligned);
62642439462SJon Lin if (!p_buf) {
62742439462SJon Lin printf("%s: Fail to malloc!", __func__);
62842439462SJon Lin return 0;
62942439462SJon Lin }
63042439462SJon Lin
63142439462SJon Lin ret = mtd_map_read(mtd, off_aligned, &rwsize_aligned,
63242439462SJon Lin NULL, mtd->size,
63342439462SJon Lin (u_char *)(p_buf));
63442439462SJon Lin if (ret) {
63542439462SJon Lin free(p_buf);
63642439462SJon Lin return 0;
63742439462SJon Lin }
63842439462SJon Lin
63942439462SJon Lin memcpy(p_buf + alinged, src, rwsize);
64042439462SJon Lin
64142439462SJon Lin ret = mtd_map_write(mtd, off_aligned, &rwsize_aligned,
64242439462SJon Lin NULL, mtd->size,
64342439462SJon Lin (u_char *)(p_buf), 0);
64442439462SJon Lin free(p_buf);
64542439462SJon Lin if (!ret)
64642439462SJon Lin return blkcnt;
64742439462SJon Lin else
64842439462SJon Lin return 0;
64942439462SJon Lin }
6509ee38883SJon Lin } else {
6519ee38883SJon Lin return 0;
6529ee38883SJon Lin }
6539ee38883SJon Lin
654054229abSJason Zhu return 0;
655054229abSJason Zhu }
656054229abSJason Zhu
mtd_derase(struct udevice * udev,lbaint_t start,lbaint_t blkcnt)657054229abSJason Zhu ulong mtd_derase(struct udevice *udev, lbaint_t start,
658054229abSJason Zhu lbaint_t blkcnt)
659054229abSJason Zhu {
660338697c5SJon Lin struct blk_desc *desc = dev_get_uclass_platdata(udev);
661338697c5SJon Lin #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
662338697c5SJon Lin loff_t off = (loff_t)(start * 512);
663338697c5SJon Lin size_t len = blkcnt * 512;
664338697c5SJon Lin #endif
665338697c5SJon Lin struct mtd_info *mtd;
666338697c5SJon Lin int ret = 0;
667338697c5SJon Lin
668338697c5SJon Lin if (!desc)
669338697c5SJon Lin return ret;
670338697c5SJon Lin
671338697c5SJon Lin mtd = desc->bdev->priv;
672338697c5SJon Lin if (!mtd)
673338697c5SJon Lin return 0;
674338697c5SJon Lin
675338697c5SJon Lin pr_debug("mtd derase %s %lx %lx\n", mtd->name, start, blkcnt);
676e84cdeddSJon Lin len = round_up(len, mtd->erasesize);
677338697c5SJon Lin
678338697c5SJon Lin if (blkcnt == 0)
679338697c5SJon Lin return 0;
680338697c5SJon Lin
681338697c5SJon Lin if (desc->devnum == BLK_MTD_NAND ||
6829148182dSJon Lin desc->devnum == BLK_MTD_SPI_NAND ||
6839148182dSJon Lin desc->devnum == BLK_MTD_SPI_NOR) {
684338697c5SJon Lin ret = mtd_map_erase(mtd, off, len);
685338697c5SJon Lin if (ret)
686338697c5SJon Lin return ret;
687338697c5SJon Lin } else {
688338697c5SJon Lin return 0;
689338697c5SJon Lin }
690338697c5SJon Lin
6917182d4beSJon Lin return blkcnt;
692054229abSJason Zhu }
693a0166cc6SJason Zhu #endif
694054229abSJason Zhu
mtd_blk_probe(struct udevice * udev)695054229abSJason Zhu static int mtd_blk_probe(struct udevice *udev)
696054229abSJason Zhu {
6976524556dSJon Lin struct mtd_info *mtd;
698054229abSJason Zhu struct blk_desc *desc = dev_get_uclass_platdata(udev);
6996524556dSJon Lin int ret, i = 0;
7006524556dSJon Lin
7016524556dSJon Lin mtd = dev_get_uclass_priv(udev->parent);
7026524556dSJon Lin if (mtd->type == MTD_NANDFLASH && desc->devnum == BLK_MTD_NAND) {
7036524556dSJon Lin #ifndef CONFIG_SPL_BUILD
7046524556dSJon Lin mtd = dev_get_priv(udev->parent);
7056524556dSJon Lin #endif
7066524556dSJon Lin }
707054229abSJason Zhu
708479a1588SJon Lin /* Fill mtd devices information */
709479a1588SJon Lin if (is_power_of_2(mtd->erasesize))
710479a1588SJon Lin mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
711479a1588SJon Lin else
712479a1588SJon Lin mtd->erasesize_shift = 0;
713479a1588SJon Lin
714479a1588SJon Lin if (is_power_of_2(mtd->writesize))
715479a1588SJon Lin mtd->writesize_shift = ffs(mtd->writesize) - 1;
716479a1588SJon Lin else
717479a1588SJon Lin mtd->writesize_shift = 0;
718479a1588SJon Lin
719479a1588SJon Lin mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
720479a1588SJon Lin mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
721479a1588SJon Lin
722c9289eddSJason Zhu desc->bdev->priv = mtd;
723054229abSJason Zhu sprintf(desc->vendor, "0x%.4x", 0x2207);
724a5c69560SJon Lin if (strncmp(mtd->name, "nand", 4) == 0)
725a5c69560SJon Lin memcpy(desc->product, "rk-nand", strlen("rk-nand"));
726a5c69560SJon Lin else
727e6482de4SJason Zhu memcpy(desc->product, mtd->name, strlen(mtd->name));
728054229abSJason Zhu memcpy(desc->revision, "V1.00", sizeof("V1.00"));
729f1892190SJason Zhu if (mtd->type == MTD_NANDFLASH) {
7306524556dSJon Lin #ifdef CONFIG_NAND
731f3ba630bSJason Zhu if (desc->devnum == BLK_MTD_NAND)
7326524556dSJon Lin i = NAND_BBT_SCAN_MAXBLOCKS;
733ab7b5b70SJon Lin #endif
734ab7b5b70SJon Lin #ifdef CONFIG_MTD_SPI_NAND
735ab7b5b70SJon Lin if (desc->devnum == BLK_MTD_SPI_NAND)
7366524556dSJon Lin i = NANDDEV_BBT_SCAN_MAXBLOCKS;
7376524556dSJon Lin #endif
7386524556dSJon Lin
7391d39542fSJason Zhu /*
7401d39542fSJason Zhu * Find the first useful block in the end,
7411d39542fSJason Zhu * and it is the end lba of the nand storage.
7421d39542fSJason Zhu */
7436524556dSJon Lin for (; i < (mtd->size / mtd->erasesize); i++) {
7441d39542fSJason Zhu ret = mtd_block_isbad(mtd,
7451d39542fSJason Zhu mtd->size - mtd->erasesize * (i + 1));
7461d39542fSJason Zhu if (!ret) {
7471d39542fSJason Zhu desc->lba = (mtd->size >> 9) -
7481d39542fSJason Zhu (mtd->erasesize >> 9) * i;
7497d79e789SJon Lin desc->rawlba = desc->lba;
7501d39542fSJason Zhu break;
7511d39542fSJason Zhu }
7521d39542fSJason Zhu }
753f1892190SJason Zhu } else {
754f1892190SJason Zhu desc->lba = mtd->size >> 9;
755f1892190SJason Zhu }
756054229abSJason Zhu
7571d39542fSJason Zhu debug("MTD: desc->lba is %lx\n", desc->lba);
7581d39542fSJason Zhu
759054229abSJason Zhu return 0;
760054229abSJason Zhu }
761054229abSJason Zhu
762054229abSJason Zhu static const struct blk_ops mtd_blk_ops = {
763054229abSJason Zhu .read = mtd_dread,
764a0166cc6SJason Zhu #if CONFIG_IS_ENABLED(MTD_WRITE)
765054229abSJason Zhu .write = mtd_dwrite,
766054229abSJason Zhu .erase = mtd_derase,
767054229abSJason Zhu #endif
768054229abSJason Zhu };
769054229abSJason Zhu
770054229abSJason Zhu U_BOOT_DRIVER(mtd_blk) = {
771054229abSJason Zhu .name = "mtd_blk",
772054229abSJason Zhu .id = UCLASS_BLK,
773054229abSJason Zhu .ops = &mtd_blk_ops,
774054229abSJason Zhu .probe = mtd_blk_probe,
775054229abSJason Zhu };
776