xref: /rk3399_rockchip-uboot/drivers/mtd/mtd_blk.c (revision e84cdedd1ab5a94fb16cc27eaf7f6989c8b1b600)
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 
341f21bf61SJon Lin static int *mtd_map_blk_table;
351f21bf61SJon Lin 
36bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
37bee19e1dSJon Lin static loff_t usbplug_dummy_partition_write_last_addr;
38bee19e1dSJon Lin static loff_t usbplug_dummy_partition_write_seek;
39bee19e1dSJon Lin static loff_t usbplug_dummy_partition_read_last_addr;
40bee19e1dSJon Lin static loff_t usbplug_dummy_partition_read_seek;
41bee19e1dSJon Lin #endif
42bee19e1dSJon Lin 
431f21bf61SJon Lin int mtd_blk_map_table_init(struct blk_desc *desc,
441f21bf61SJon Lin 			   loff_t offset,
451f21bf61SJon Lin 			   size_t length)
461f21bf61SJon Lin {
471f21bf61SJon Lin 	u32 blk_total, blk_begin, blk_cnt;
481f21bf61SJon Lin 	struct mtd_info *mtd = NULL;
491f21bf61SJon Lin 	int i, j;
501f21bf61SJon Lin 
511f21bf61SJon Lin 	if (!desc)
521f21bf61SJon Lin 		return -ENODEV;
531f21bf61SJon Lin 
546524556dSJon Lin 	switch (desc->devnum) {
556524556dSJon Lin 	case BLK_MTD_NAND:
566524556dSJon Lin 	case BLK_MTD_SPI_NAND:
571f21bf61SJon Lin 		mtd = desc->bdev->priv;
586524556dSJon Lin 		break;
596524556dSJon Lin 	default:
606524556dSJon Lin 		break;
611f21bf61SJon Lin 	}
621f21bf61SJon Lin 
631f21bf61SJon Lin 	if (!mtd) {
641f21bf61SJon Lin 		return -ENODEV;
651f21bf61SJon Lin 	} else {
66b4e07918SJon Lin 		blk_total = (mtd->size + mtd->erasesize - 1) >> mtd->erasesize_shift;
671f21bf61SJon Lin 		if (!mtd_map_blk_table) {
680f1dc487SJon Lin 			mtd_map_blk_table = (int *)malloc(blk_total * sizeof(int));
696524556dSJon Lin 			if (!mtd_map_blk_table)
706524556dSJon Lin 				return -ENOMEM;
710f1dc487SJon Lin 			for (i = 0; i < blk_total; i++)
720f1dc487SJon Lin 				mtd_map_blk_table[i] = MTD_BLK_TABLE_BLOCK_UNKNOWN;
731f21bf61SJon Lin 		}
741f21bf61SJon Lin 
75b4e07918SJon Lin 		blk_begin = (u32)offset >> mtd->erasesize_shift;
76e091dc9dSJon Lin 		blk_cnt = ((u32)((offset & mtd->erasesize_mask) + length + \
77e091dc9dSJon Lin 			mtd->erasesize - 1) >> mtd->erasesize_shift);
780f1dc487SJon Lin 		if (blk_begin >= blk_total) {
790f1dc487SJon Lin 			pr_err("map table blk begin[%d] overflow\n", blk_begin);
800f1dc487SJon Lin 			return -EINVAL;
810f1dc487SJon Lin 		}
82d6290238SJon Lin 		if ((blk_begin + blk_cnt) > blk_total)
83d6290238SJon Lin 			blk_cnt = blk_total - blk_begin;
84c9e94690SJon Lin 
85c9e94690SJon Lin 		if (mtd_map_blk_table[blk_begin] != MTD_BLK_TABLE_BLOCK_UNKNOWN)
86c9e94690SJon Lin 			return 0;
87c9e94690SJon Lin 
881f21bf61SJon Lin 		j = 0;
891f21bf61SJon Lin 		 /* should not across blk_cnt */
901f21bf61SJon Lin 		for (i = 0; i < blk_cnt; i++) {
911f21bf61SJon Lin 			if (j >= blk_cnt)
92c9e94690SJon Lin 				mtd_map_blk_table[blk_begin + i] = MTD_BLK_TABLE_BLOCK_SHIFT;
931f21bf61SJon Lin 			for (; j < blk_cnt; j++) {
94b4e07918SJon Lin 				if (!mtd_block_isbad(mtd, (blk_begin + j) << mtd->erasesize_shift)) {
951f21bf61SJon Lin 					mtd_map_blk_table[blk_begin + i] = blk_begin + j;
961f21bf61SJon Lin 					j++;
971f21bf61SJon Lin 					if (j == blk_cnt)
981f21bf61SJon Lin 						j++;
991f21bf61SJon Lin 					break;
1001f21bf61SJon Lin 				}
1011f21bf61SJon Lin 			}
1021f21bf61SJon Lin 		}
1031f21bf61SJon Lin 
1041f21bf61SJon Lin 		return 0;
1051f21bf61SJon Lin 	}
1061f21bf61SJon Lin }
1071f21bf61SJon Lin 
108c402731fSJon Lin static bool get_mtd_blk_map_address(struct mtd_info *mtd, loff_t *off)
109c402731fSJon Lin {
110c402731fSJon Lin 	bool mapped;
111c402731fSJon Lin 	loff_t offset = *off;
112c402731fSJon Lin 	size_t block_offset = offset & (mtd->erasesize - 1);
113c402731fSJon Lin 
114c402731fSJon Lin 	mapped = false;
115c402731fSJon Lin 	if (!mtd_map_blk_table ||
116c402731fSJon Lin 	    mtd_map_blk_table[(u64)offset >> mtd->erasesize_shift] ==
117c402731fSJon Lin 	    MTD_BLK_TABLE_BLOCK_UNKNOWN ||
118c402731fSJon Lin 	    mtd_map_blk_table[(u64)offset >> mtd->erasesize_shift] ==
119c402731fSJon Lin 	    0xffffffff)
120c402731fSJon Lin 		return mapped;
121c402731fSJon Lin 
122c402731fSJon Lin 	mapped = true;
123c402731fSJon Lin 	*off = (loff_t)(((u32)mtd_map_blk_table[(u64)offset >>
124c402731fSJon Lin 		mtd->erasesize_shift] << mtd->erasesize_shift) + block_offset);
125c402731fSJon Lin 
126c402731fSJon Lin 	return mapped;
127c402731fSJon Lin }
128c402731fSJon Lin 
129a07b97f2SJason Zhu void mtd_blk_map_partitions(struct blk_desc *desc)
130a07b97f2SJason Zhu {
131a07b97f2SJason Zhu 	disk_partition_t info;
132a07b97f2SJason Zhu 	int i, ret;
133a07b97f2SJason Zhu 
134a07b97f2SJason Zhu 	if (!desc)
135a07b97f2SJason Zhu 		return;
136a07b97f2SJason Zhu 
137a07b97f2SJason Zhu 	if (desc->if_type != IF_TYPE_MTD)
138a07b97f2SJason Zhu 		return;
139a07b97f2SJason Zhu 
140a07b97f2SJason Zhu 	for (i = 1; i < MAX_SEARCH_PARTITIONS; i++) {
141a07b97f2SJason Zhu 		ret = part_get_info(desc, i, &info);
142a07b97f2SJason Zhu 		if (ret != 0)
14335d02c8bSJon Lin 			break;
144a07b97f2SJason Zhu 
145a07b97f2SJason Zhu 		if (mtd_blk_map_table_init(desc,
146a07b97f2SJason Zhu 					   info.start << 9,
147a07b97f2SJason Zhu 					   info.size << 9)) {
1489ee38883SJon Lin 			pr_debug("mtd block map table fail\n");
149a07b97f2SJason Zhu 		}
150a07b97f2SJason Zhu 	}
151a07b97f2SJason Zhu }
152a07b97f2SJason Zhu 
153661bcdfeSJason Zhu void mtd_blk_map_fit(struct blk_desc *desc, ulong sector, void *fit)
154661bcdfeSJason Zhu {
155661bcdfeSJason Zhu 	struct mtd_info *mtd = NULL;
156661bcdfeSJason Zhu 	int totalsize = 0;
157661bcdfeSJason Zhu 
158661bcdfeSJason Zhu 	if (desc->if_type != IF_TYPE_MTD)
159661bcdfeSJason Zhu 		return;
160661bcdfeSJason Zhu 
161661bcdfeSJason Zhu 	if (desc->devnum == BLK_MTD_NAND) {
162661bcdfeSJason Zhu #if defined(CONFIG_NAND)
163661bcdfeSJason Zhu 		mtd = dev_get_priv(desc->bdev->parent);
164661bcdfeSJason Zhu #endif
165661bcdfeSJason Zhu 	} else if (desc->devnum == BLK_MTD_SPI_NAND) {
166661bcdfeSJason Zhu #if defined(CONFIG_MTD_SPI_NAND)
167661bcdfeSJason Zhu 		mtd = desc->bdev->priv;
168661bcdfeSJason Zhu #endif
169661bcdfeSJason Zhu 	}
170661bcdfeSJason Zhu 
171661bcdfeSJason Zhu #ifdef CONFIG_SPL_FIT
172661bcdfeSJason Zhu 	if (fit_get_totalsize(fit, &totalsize))
173661bcdfeSJason Zhu 		debug("Can not find /totalsize node.\n");
174661bcdfeSJason Zhu #endif
175661bcdfeSJason Zhu 	if (mtd && totalsize) {
176661bcdfeSJason Zhu 		if (mtd_blk_map_table_init(desc, sector << 9, totalsize + (size_t)mtd->erasesize))
177661bcdfeSJason Zhu 			debug("Map block table fail.\n");
178661bcdfeSJason Zhu 	}
179661bcdfeSJason Zhu }
180661bcdfeSJason Zhu 
1811f21bf61SJon Lin static __maybe_unused int mtd_map_read(struct mtd_info *mtd, loff_t offset,
1821f21bf61SJon Lin 				       size_t *length, size_t *actual,
1831f21bf61SJon Lin 				       loff_t lim, u_char *buffer)
1841f21bf61SJon Lin {
1851f21bf61SJon Lin 	size_t left_to_read = *length;
1861f21bf61SJon Lin 	u_char *p_buffer = buffer;
1871f21bf61SJon Lin 	int rval;
1881f21bf61SJon Lin 
189bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
190bee19e1dSJon Lin 	if (usbplug_dummy_partition_read_last_addr != offset)
191bee19e1dSJon Lin 		usbplug_dummy_partition_read_seek = 0;
192bee19e1dSJon Lin 	usbplug_dummy_partition_read_last_addr = offset + left_to_read;
193bee19e1dSJon Lin 	offset += usbplug_dummy_partition_read_seek;
194bee19e1dSJon Lin #endif
195bee19e1dSJon Lin 
1961f21bf61SJon Lin 	while (left_to_read > 0) {
197360a2911SJon Lin 		size_t block_offset = offset & (mtd->erasesize - 1);
1981f21bf61SJon Lin 		size_t read_length;
199360a2911SJon Lin 		loff_t mapped_offset;
2001f21bf61SJon Lin 
2011f21bf61SJon Lin 		if (offset >= mtd->size)
2021f21bf61SJon Lin 			return 0;
2031f21bf61SJon Lin 
204360a2911SJon Lin 		mapped_offset = offset;
205360a2911SJon Lin 		if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
206360a2911SJon Lin 			if (mtd_block_isbad(mtd, mapped_offset &
207360a2911SJon Lin 					    ~(mtd->erasesize - 1))) {
208bee19e1dSJon Lin 				printf("Skipping bad block 0x%08x in read\n",
209bee19e1dSJon Lin 				       (u32)(offset & ~(mtd->erasesize - 1)));
210360a2911SJon Lin 				offset += mtd->erasesize - block_offset;
211bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
212bee19e1dSJon Lin 				usbplug_dummy_partition_read_seek += mtd->erasesize;
213bee19e1dSJon Lin #endif
2141f21bf61SJon Lin 				continue;
2151f21bf61SJon Lin 			}
2161f21bf61SJon Lin 		}
2171f21bf61SJon Lin 
218360a2911SJon Lin 		if (left_to_read < (mtd->erasesize - block_offset))
2191f21bf61SJon Lin 			read_length = left_to_read;
2201f21bf61SJon Lin 		else
221360a2911SJon Lin 			read_length = mtd->erasesize - block_offset;
2221f21bf61SJon Lin 
223360a2911SJon Lin 		rval = mtd_read(mtd, mapped_offset, read_length, &read_length,
2241f21bf61SJon Lin 				p_buffer);
2251f21bf61SJon Lin 		if (rval && rval != -EUCLEAN) {
22668fb1b3bSJon Lin 			printf("NAND read from offset %x failed %d\n",
22768fb1b3bSJon Lin 			       (u32)offset, rval);
2281f21bf61SJon Lin 			*length -= left_to_read;
2291f21bf61SJon Lin 			return rval;
2301f21bf61SJon Lin 		}
2311f21bf61SJon Lin 
2321f21bf61SJon Lin 		left_to_read -= read_length;
2331f21bf61SJon Lin 		offset       += read_length;
2341f21bf61SJon Lin 		p_buffer     += read_length;
2351f21bf61SJon Lin 	}
2361f21bf61SJon Lin 
2371f21bf61SJon Lin 	return 0;
2381f21bf61SJon Lin }
2391f21bf61SJon Lin 
2409ee38883SJon Lin static __maybe_unused int mtd_map_write(struct mtd_info *mtd, loff_t offset,
2419ee38883SJon Lin 					size_t *length, size_t *actual,
2429ee38883SJon Lin 					loff_t lim, u_char *buffer, int flags)
2439ee38883SJon Lin {
2449ee38883SJon Lin 	int rval = 0, blocksize;
2459ee38883SJon Lin 	size_t left_to_write = *length;
2469ee38883SJon Lin 	u_char *p_buffer = buffer;
2479ee38883SJon Lin 	struct erase_info ei;
2489ee38883SJon Lin 
2499ee38883SJon Lin 	blocksize = mtd->erasesize;
2509ee38883SJon Lin 
251bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
252bee19e1dSJon Lin 	if (usbplug_dummy_partition_write_last_addr != offset)
253bee19e1dSJon Lin 		usbplug_dummy_partition_write_seek = 0;
254bee19e1dSJon Lin 	usbplug_dummy_partition_write_last_addr = offset + left_to_write;
255bee19e1dSJon Lin 	offset += usbplug_dummy_partition_write_seek;
256bee19e1dSJon Lin #endif
257bee19e1dSJon Lin 
2589ee38883SJon Lin 	/*
2599ee38883SJon Lin 	 * nand_write() handles unaligned, partial page writes.
2609ee38883SJon Lin 	 *
2619ee38883SJon Lin 	 * We allow length to be unaligned, for convenience in
2629ee38883SJon Lin 	 * using the $filesize variable.
2639ee38883SJon Lin 	 *
2649ee38883SJon Lin 	 * However, starting at an unaligned offset makes the
2659ee38883SJon Lin 	 * semantics of bad block skipping ambiguous (really,
2669ee38883SJon Lin 	 * you should only start a block skipping access at a
2679ee38883SJon Lin 	 * partition boundary).  So don't try to handle that.
2689ee38883SJon Lin 	 */
2699ee38883SJon Lin 	if ((offset & (mtd->writesize - 1)) != 0) {
2709ee38883SJon Lin 		printf("Attempt to write non page-aligned data\n");
2719ee38883SJon Lin 		*length = 0;
2729ee38883SJon Lin 		return -EINVAL;
2739ee38883SJon Lin 	}
2749ee38883SJon Lin 
2759ee38883SJon Lin 	while (left_to_write > 0) {
2769ee38883SJon Lin 		size_t block_offset = offset & (mtd->erasesize - 1);
2779ee38883SJon Lin 		size_t write_size, truncated_write_size;
2789ee38883SJon Lin 		loff_t mapped_offset;
2799ee38883SJon Lin 
2809ee38883SJon Lin 		if (offset >= mtd->size)
2819ee38883SJon Lin 			return 0;
2829ee38883SJon Lin 
2839ee38883SJon Lin 		mapped_offset = offset;
2849ee38883SJon Lin 		if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
2859ee38883SJon Lin 			if (mtd_block_isbad(mtd, mapped_offset &
2869ee38883SJon Lin 					    ~(mtd->erasesize - 1))) {
287bee19e1dSJon Lin 				printf("Skipping bad block 0x%08x in write\n",
288bee19e1dSJon Lin 				       (u32)(offset & ~(mtd->erasesize - 1)));
2899ee38883SJon Lin 				offset += mtd->erasesize - block_offset;
290bee19e1dSJon Lin #if CONFIG_IS_ENABLED(SUPPORT_USBPLUG)
291bee19e1dSJon Lin 				usbplug_dummy_partition_write_seek += mtd->erasesize;
292bee19e1dSJon Lin #endif
2939ee38883SJon Lin 				continue;
2949ee38883SJon Lin 			}
2959ee38883SJon Lin 		}
2969ee38883SJon Lin 
2979ee38883SJon Lin 		if (!(mapped_offset & mtd->erasesize_mask)) {
2989ee38883SJon Lin 			memset(&ei, 0, sizeof(struct erase_info));
2999ee38883SJon Lin 			ei.addr = mapped_offset;
3009ee38883SJon Lin 			ei.len  = mtd->erasesize;
3019ee38883SJon Lin 			rval = mtd_erase(mtd, &ei);
3029ee38883SJon Lin 			if (rval) {
3039ee38883SJon Lin 				pr_info("error %d while erasing %llx\n", rval,
3049ee38883SJon Lin 					mapped_offset);
3059ee38883SJon Lin 				return rval;
3069ee38883SJon Lin 			}
3079ee38883SJon Lin 		}
3089ee38883SJon Lin 
3099ee38883SJon Lin 		if (left_to_write < (blocksize - block_offset))
3109ee38883SJon Lin 			write_size = left_to_write;
3119ee38883SJon Lin 		else
3129ee38883SJon Lin 			write_size = blocksize - block_offset;
3139ee38883SJon Lin 
3149ee38883SJon Lin 		truncated_write_size = write_size;
3159ee38883SJon Lin 		rval = mtd_write(mtd, mapped_offset, truncated_write_size,
3169ee38883SJon Lin 				 (size_t *)(&truncated_write_size), p_buffer);
3179ee38883SJon Lin 
3189ee38883SJon Lin 		offset += write_size;
3199ee38883SJon Lin 		p_buffer += write_size;
3209ee38883SJon Lin 
3219ee38883SJon Lin 		if (rval != 0) {
3229ee38883SJon Lin 			printf("NAND write to offset %llx failed %d\n",
3239ee38883SJon Lin 			       offset, rval);
3249ee38883SJon Lin 			*length -= left_to_write;
3259ee38883SJon Lin 			return rval;
3269ee38883SJon Lin 		}
3279ee38883SJon Lin 
3289ee38883SJon Lin 		left_to_write -= write_size;
3299ee38883SJon Lin 	}
3309ee38883SJon Lin 
3319ee38883SJon Lin 	return 0;
3329ee38883SJon Lin }
3339ee38883SJon Lin 
334338697c5SJon Lin static __maybe_unused int mtd_map_erase(struct mtd_info *mtd, loff_t offset,
335338697c5SJon Lin 					size_t length)
336338697c5SJon Lin {
337338697c5SJon Lin 	struct erase_info ei;
338338697c5SJon Lin 	loff_t pos, len;
339338697c5SJon Lin 	int ret;
340338697c5SJon Lin 
341338697c5SJon Lin 	pos = offset;
342338697c5SJon Lin 	len = length;
343338697c5SJon Lin 
344338697c5SJon Lin 	if ((pos & mtd->erasesize_mask) || (len & mtd->erasesize_mask)) {
345338697c5SJon Lin 		pr_err("Attempt to erase non block-aligned data, pos= %llx, len= %llx\n",
346338697c5SJon Lin 		       pos, len);
347338697c5SJon Lin 
348338697c5SJon Lin 		return -EINVAL;
349338697c5SJon Lin 	}
350338697c5SJon Lin 
351338697c5SJon Lin 	while (len) {
352bd676549SJon Lin 		loff_t mapped_offset;
353bd676549SJon Lin 
354bd676549SJon Lin 		mapped_offset = pos;
355bd676549SJon Lin 		if (!get_mtd_blk_map_address(mtd, &mapped_offset)) {
356338697c5SJon Lin 			if (mtd_block_isbad(mtd, pos) || mtd_block_isreserved(mtd, pos)) {
357338697c5SJon Lin 				pr_debug("attempt to erase a bad/reserved block @%llx\n",
358338697c5SJon Lin 					 pos);
359338697c5SJon Lin 				pos += mtd->erasesize;
360338697c5SJon Lin 				continue;
361338697c5SJon Lin 			}
362bd676549SJon Lin 		}
363338697c5SJon Lin 
364338697c5SJon Lin 		memset(&ei, 0, sizeof(struct erase_info));
365bd676549SJon Lin 		ei.addr = mapped_offset;
366338697c5SJon Lin 		ei.len  = mtd->erasesize;
367338697c5SJon Lin 		ret = mtd_erase(mtd, &ei);
368338697c5SJon Lin 		if (ret) {
369338697c5SJon Lin 			pr_err("map_erase error %d while erasing %llx\n", ret,
370338697c5SJon Lin 			       pos);
371338697c5SJon Lin 			return ret;
372338697c5SJon Lin 		}
373338697c5SJon Lin 
374338697c5SJon Lin 		pos += mtd->erasesize;
375338697c5SJon Lin 		len -= mtd->erasesize;
376338697c5SJon Lin 	}
377338697c5SJon Lin 
378338697c5SJon Lin 	return 0;
379338697c5SJon Lin }
380338697c5SJon Lin 
381c4fe67c2SJason Zhu char *mtd_part_parse(struct blk_desc *dev_desc)
38222dccd11SJason Zhu {
38322dccd11SJason Zhu 	char mtd_part_info_temp[MTD_SINGLE_PART_INFO_MAX_SIZE] = {0};
38422dccd11SJason Zhu 	u32 length, data_len = MTD_PART_INFO_MAX_SIZE;
38522dccd11SJason Zhu 	disk_partition_t info;
38622dccd11SJason Zhu 	char *mtd_part_info_p;
387c9289eddSJason Zhu 	struct mtd_info *mtd;
38822dccd11SJason Zhu 	char *mtd_part_info;
38922dccd11SJason Zhu 	int ret;
39022dccd11SJason Zhu 	int p;
39122dccd11SJason Zhu 
392c4fe67c2SJason Zhu #ifndef CONFIG_SPL_BUILD
39322dccd11SJason Zhu 	dev_desc = rockchip_get_bootdev();
394c4fe67c2SJason Zhu #endif
39522dccd11SJason Zhu 	if (!dev_desc)
39622dccd11SJason Zhu 		return NULL;
39722dccd11SJason Zhu 
398c9289eddSJason Zhu 	mtd = (struct mtd_info *)dev_desc->bdev->priv;
399ec6d4288SJason Zhu 	if (!mtd)
400ec6d4288SJason Zhu 		return NULL;
401ec6d4288SJason Zhu 
40222dccd11SJason Zhu 	mtd_part_info = (char *)calloc(MTD_PART_INFO_MAX_SIZE, sizeof(char));
40322dccd11SJason Zhu 	if (!mtd_part_info) {
40422dccd11SJason Zhu 		printf("%s: Fail to malloc!", __func__);
40522dccd11SJason Zhu 		return NULL;
40622dccd11SJason Zhu 	}
40722dccd11SJason Zhu 
40822dccd11SJason Zhu 	mtd_part_info_p = mtd_part_info;
40922dccd11SJason Zhu 	snprintf(mtd_part_info_p, data_len - 1, "%s%s:",
41022dccd11SJason Zhu 		 MTD_PART_NAND_HEAD,
41122dccd11SJason Zhu 		 dev_desc->product);
41222dccd11SJason Zhu 	data_len -= strlen(mtd_part_info_p);
41322dccd11SJason Zhu 	mtd_part_info_p = mtd_part_info_p + strlen(mtd_part_info_p);
41422dccd11SJason Zhu 
41522dccd11SJason Zhu 	for (p = 1; p < MAX_SEARCH_PARTITIONS; p++) {
41622dccd11SJason Zhu 		ret = part_get_info(dev_desc, p, &info);
41722dccd11SJason Zhu 		if (ret)
41822dccd11SJason Zhu 			break;
41922dccd11SJason Zhu 
42022dccd11SJason Zhu 		debug("name is %s, start addr is %x\n", info.name,
42122dccd11SJason Zhu 		      (int)(size_t)info.start);
42222dccd11SJason Zhu 
42322dccd11SJason Zhu 		snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
42422dccd11SJason Zhu 			 (int)(size_t)info.size << 9,
42522dccd11SJason Zhu 			 (int)(size_t)info.start << 9,
42622dccd11SJason Zhu 			 info.name);
42722dccd11SJason Zhu 		snprintf(mtd_part_info_temp, MTD_SINGLE_PART_INFO_MAX_SIZE - 1,
42822dccd11SJason Zhu 			 "0x%x@0x%x(%s)",
42922dccd11SJason Zhu 			 (int)(size_t)info.size << 9,
43022dccd11SJason Zhu 			 (int)(size_t)info.start << 9,
43122dccd11SJason Zhu 			 info.name);
43222dccd11SJason Zhu 		strcat(mtd_part_info, ",");
433a3e58bf5SJon Lin 		if (part_get_info(dev_desc, p + 1, &info)) {
434a3e58bf5SJon Lin 			/* Partition with grow tag in parameter will be resized */
435a3e58bf5SJon Lin 			if ((info.size + info.start + 64) >= dev_desc->lba) {
436ce9d2743SJon Lin 				if (dev_desc->devnum == BLK_MTD_SPI_NOR) {
437ce9d2743SJon Lin 					/* Nor is 64KB erase block(kernel) and gpt table just
438ce9d2743SJon Lin 					 * resserve 33 sectors for the last partition. This
439ce9d2743SJon Lin 					 * will erase the backup gpt table by user program,
440ce9d2743SJon Lin 					 * so reserve one block.
441ce9d2743SJon Lin 					 */
442ce9d2743SJon Lin 					snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
443ce9d2743SJon Lin 						 (int)(size_t)(info.size -
444ce9d2743SJon Lin 						 (info.size - 1) %
445ce9d2743SJon Lin 						 (0x10000 >> 9) - 1) << 9,
446ce9d2743SJon Lin 						 (int)(size_t)info.start << 9,
447ce9d2743SJon Lin 						 info.name);
448ce9d2743SJon Lin 					break;
449ce9d2743SJon Lin 				} else {
450c9289eddSJason Zhu 					/* Nand flash is erased by block and gpt table just
451c9289eddSJason Zhu 					 * resserve 33 sectors for the last partition. This
452c9289eddSJason Zhu 					 * will erase the backup gpt table by user program,
453c9289eddSJason Zhu 					 * so reserve one block.
454c9289eddSJason Zhu 					 */
455c9289eddSJason Zhu 					snprintf(mtd_part_info_p, data_len - 1, "0x%x@0x%x(%s)",
456c9289eddSJason Zhu 						 (int)(size_t)(info.size -
457c9289eddSJason Zhu 						 (info.size - 1) %
458c9289eddSJason Zhu 						 (mtd->erasesize >> 9) - 1) << 9,
45922dccd11SJason Zhu 						 (int)(size_t)info.start << 9,
46022dccd11SJason Zhu 						 info.name);
46122dccd11SJason Zhu 					break;
46222dccd11SJason Zhu 				}
463a3e58bf5SJon Lin 			} else {
464a3e58bf5SJon Lin 				snprintf(mtd_part_info_temp, MTD_SINGLE_PART_INFO_MAX_SIZE - 1,
465a3e58bf5SJon Lin 					 "0x%x@0x%x(%s)",
466a3e58bf5SJon Lin 					 (int)(size_t)info.size << 9,
467a3e58bf5SJon Lin 					 (int)(size_t)info.start << 9,
468a3e58bf5SJon Lin 					 info.name);
469a3e58bf5SJon Lin 				break;
470a3e58bf5SJon Lin 			}
471ce9d2743SJon Lin 		}
47222dccd11SJason Zhu 		length = strlen(mtd_part_info_temp);
47322dccd11SJason Zhu 		data_len -= length;
47422dccd11SJason Zhu 		mtd_part_info_p = mtd_part_info_p + length + 1;
47522dccd11SJason Zhu 		memset(mtd_part_info_temp, 0, MTD_SINGLE_PART_INFO_MAX_SIZE);
47622dccd11SJason Zhu 	}
47722dccd11SJason Zhu 
47822dccd11SJason Zhu 	return mtd_part_info;
47922dccd11SJason Zhu }
48022dccd11SJason Zhu 
481054229abSJason Zhu ulong mtd_dread(struct udevice *udev, lbaint_t start,
482054229abSJason Zhu 		lbaint_t blkcnt, void *dst)
483054229abSJason Zhu {
484054229abSJason Zhu 	struct blk_desc *desc = dev_get_uclass_platdata(udev);
4850dccd0d8SJason Zhu #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
4866e8ac5a8SJason Zhu 	loff_t off = (loff_t)(start * 512);
4876e8ac5a8SJason Zhu 	size_t rwsize = blkcnt * 512;
4880dccd0d8SJason Zhu #endif
4896e8ac5a8SJason Zhu 	struct mtd_info *mtd;
490bbb83f58SJason Zhu 	int ret = 0;
4911a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
4921a3d87beSJon Lin 	ulong us = 1;
4931a3d87beSJon Lin #endif
494054229abSJason Zhu 
495054229abSJason Zhu 	if (!desc)
49639e38ab3SJason Zhu 		return ret;
497054229abSJason Zhu 
4986e8ac5a8SJason Zhu 	mtd = desc->bdev->priv;
4996e8ac5a8SJason Zhu 	if (!mtd)
5006e8ac5a8SJason Zhu 		return 0;
5016e8ac5a8SJason Zhu 
502054229abSJason Zhu 	if (blkcnt == 0)
503054229abSJason Zhu 		return 0;
504054229abSJason Zhu 
5051a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
5061a3d87beSJon Lin 	us = get_ticks();
5071a3d87beSJon Lin #endif
508054229abSJason Zhu 	if (desc->devnum == BLK_MTD_NAND) {
5091f21bf61SJon Lin 		ret = mtd_map_read(mtd, off, &rwsize,
5101f21bf61SJon Lin 				   NULL, mtd->size,
5111f21bf61SJon Lin 				   (u_char *)(dst));
5126e8ac5a8SJason Zhu 		if (!ret)
5131a3d87beSJon Lin 			ret = blkcnt;
514054229abSJason Zhu 	} else if (desc->devnum == BLK_MTD_SPI_NAND) {
5156af5fcf6SJon Lin #if defined(CONFIG_MTD_SPI_NAND)
5166af5fcf6SJon Lin 		struct spinand_device *spinand = mtd_to_spinand(mtd);
5176af5fcf6SJon Lin 		struct spi_slave *spi = spinand->slave;
5186af5fcf6SJon Lin 		size_t retlen_nand;
5196af5fcf6SJon Lin 
5206af5fcf6SJon Lin 		if (desc->op_flag == BLK_PRE_RW) {
5216af5fcf6SJon Lin 			spi->mode |= SPI_DMA_PREPARE;
5226af5fcf6SJon Lin 			ret = mtd_read(mtd, off, rwsize,
5236af5fcf6SJon Lin 				       &retlen_nand, (u_char *)(dst));
5246af5fcf6SJon Lin 			spi->mode &= ~SPI_DMA_PREPARE;
5256af5fcf6SJon Lin 			if (retlen_nand == rwsize)
5266af5fcf6SJon Lin 				ret = blkcnt;
5276af5fcf6SJon Lin 		} else {
5286af5fcf6SJon Lin 			if (spinand->support_cont_read)
5296af5fcf6SJon Lin 				ret = mtd_read(mtd, off, rwsize,
5306af5fcf6SJon Lin 					       &retlen_nand,
5316af5fcf6SJon Lin 					       (u_char *)(dst));
5326af5fcf6SJon Lin 			else
5331f21bf61SJon Lin 				ret = mtd_map_read(mtd, off, &rwsize,
534bbb83f58SJason Zhu 						   NULL, mtd->size,
535bbb83f58SJason Zhu 						   (u_char *)(dst));
536bbb83f58SJason Zhu 			if (!ret)
5371a3d87beSJon Lin 				ret = blkcnt;
5386af5fcf6SJon Lin 		}
5396af5fcf6SJon Lin #endif
540054229abSJason Zhu 	} else if (desc->devnum == BLK_MTD_SPI_NOR) {
5410dccd0d8SJason Zhu #if defined(CONFIG_SPI_FLASH_MTD) || defined(CONFIG_SPL_BUILD)
5423fb7bf02SJason Zhu 		struct spi_nor *nor = (struct spi_nor *)mtd->priv;
5433fb7bf02SJason Zhu 		struct spi_slave *spi = nor->spi;
5440dccd0d8SJason Zhu 		size_t retlen_nor;
5450dccd0d8SJason Zhu 
5463fb7bf02SJason Zhu 		if (desc->op_flag == BLK_PRE_RW)
5473fb7bf02SJason Zhu 			spi->mode |= SPI_DMA_PREPARE;
5481a3d87beSJon Lin 		ret = mtd_read(mtd, off, rwsize, &retlen_nor, dst);
5493fb7bf02SJason Zhu 		if (desc->op_flag == BLK_PRE_RW)
550bfdb06edSJon Lin 			spi->mode &= ~SPI_DMA_PREPARE;
5513fb7bf02SJason Zhu 
5520dccd0d8SJason Zhu 		if (retlen_nor == rwsize)
5531a3d87beSJon Lin 			ret = blkcnt;
5540dccd0d8SJason Zhu #endif
555054229abSJason Zhu 	}
5561a3d87beSJon Lin #ifdef MTD_BLK_VERBOSE
5571a3d87beSJon Lin 	us = (get_ticks() - us) / 24UL;
5581a3d87beSJon Lin 	pr_err("mtd dread %s %lx %lx cost %ldus: %ldMB/s\n\n", mtd->name, start, blkcnt, us, (blkcnt / 2) / ((us + 999) / 1000));
5591a3d87beSJon Lin #else
5601a3d87beSJon Lin 	pr_debug("mtd dread %s %lx %lx\n\n", mtd->name, start, blkcnt);
5611a3d87beSJon Lin #endif
5621a3d87beSJon Lin 
5631a3d87beSJon Lin 	return ret;
564054229abSJason Zhu }
565054229abSJason Zhu 
566a0166cc6SJason Zhu #if CONFIG_IS_ENABLED(MTD_WRITE)
567054229abSJason Zhu ulong mtd_dwrite(struct udevice *udev, lbaint_t start,
568054229abSJason Zhu 		 lbaint_t blkcnt, const void *src)
569054229abSJason Zhu {
5709ee38883SJon Lin 	struct blk_desc *desc = dev_get_uclass_platdata(udev);
5719ee38883SJon Lin #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
5729ee38883SJon Lin 	loff_t off = (loff_t)(start * 512);
5739ee38883SJon Lin 	size_t rwsize = blkcnt * 512;
5749ee38883SJon Lin #endif
5759ee38883SJon Lin 	struct mtd_info *mtd;
5769ee38883SJon Lin 	int ret = 0;
5779ee38883SJon Lin 
5789ee38883SJon Lin 	if (!desc)
5799ee38883SJon Lin 		return ret;
5809ee38883SJon Lin 
5819ee38883SJon Lin 	mtd = desc->bdev->priv;
5829ee38883SJon Lin 	if (!mtd)
5839ee38883SJon Lin 		return 0;
5849ee38883SJon Lin 
5859ee38883SJon Lin 	pr_debug("mtd dwrite %s %lx %lx\n", mtd->name, start, blkcnt);
5869ee38883SJon Lin 
5879ee38883SJon Lin 	if (blkcnt == 0)
5889ee38883SJon Lin 		return 0;
5899ee38883SJon Lin 
590af0bf855SJon Lin 	if (desc->op_flag & BLK_MTD_CONT_WRITE &&
591af0bf855SJon Lin 	    (start == 1 || ((desc->lba - start) <= 33))) {
592af0bf855SJon Lin 		printf("Write in GPT area, lba=%ld cnt=%ld\n", start, blkcnt);
593f4688965SJon Lin 		desc->op_flag &= ~BLK_MTD_CONT_WRITE;
594f4688965SJon Lin 	}
595f4688965SJon Lin 
5969ee38883SJon Lin 	if (desc->devnum == BLK_MTD_NAND ||
5979ee38883SJon Lin 	    desc->devnum == BLK_MTD_SPI_NAND ||
5989ee38883SJon Lin 	    desc->devnum == BLK_MTD_SPI_NOR) {
599bee19e1dSJon Lin 		if (desc->op_flag & BLK_MTD_CONT_WRITE) {
600853fc11fSJon Lin 			ret = mtd_map_write(mtd, off, &rwsize,
601853fc11fSJon Lin 					    NULL, mtd->size,
602853fc11fSJon Lin 					    (u_char *)(src), 0);
603853fc11fSJon Lin 			if (!ret)
604853fc11fSJon Lin 				return blkcnt;
605853fc11fSJon Lin 			else
606853fc11fSJon Lin 				return 0;
607853fc11fSJon Lin 		} else {
60842439462SJon Lin 			lbaint_t off_aligned, alinged;
60942439462SJon Lin 			size_t rwsize_aligned;
61042439462SJon Lin 			u8 *p_buf;
61142439462SJon Lin 
61242439462SJon Lin 			alinged = off & mtd->erasesize_mask;
61342439462SJon Lin 			off_aligned = off - alinged;
61442439462SJon Lin 			rwsize_aligned = rwsize + alinged;
61542439462SJon Lin 			rwsize_aligned = (rwsize_aligned + mtd->erasesize - 1) &
61642439462SJon Lin 				~(mtd->erasesize - 1);
61742439462SJon Lin 
61842439462SJon Lin 			p_buf = malloc(rwsize_aligned);
61942439462SJon Lin 			if (!p_buf) {
62042439462SJon Lin 				printf("%s: Fail to malloc!", __func__);
62142439462SJon Lin 				return 0;
62242439462SJon Lin 			}
62342439462SJon Lin 
62442439462SJon Lin 			ret = mtd_map_read(mtd, off_aligned, &rwsize_aligned,
62542439462SJon Lin 					   NULL, mtd->size,
62642439462SJon Lin 					   (u_char *)(p_buf));
62742439462SJon Lin 			if (ret) {
62842439462SJon Lin 				free(p_buf);
62942439462SJon Lin 				return 0;
63042439462SJon Lin 			}
63142439462SJon Lin 
63242439462SJon Lin 			memcpy(p_buf + alinged, src, rwsize);
63342439462SJon Lin 
63442439462SJon Lin 			ret = mtd_map_write(mtd, off_aligned, &rwsize_aligned,
63542439462SJon Lin 					    NULL, mtd->size,
63642439462SJon Lin 					    (u_char *)(p_buf), 0);
63742439462SJon Lin 			free(p_buf);
63842439462SJon Lin 			if (!ret)
63942439462SJon Lin 				return blkcnt;
64042439462SJon Lin 			else
64142439462SJon Lin 				return 0;
64242439462SJon Lin 		}
6439ee38883SJon Lin 	} else {
6449ee38883SJon Lin 		return 0;
6459ee38883SJon Lin 	}
6469ee38883SJon Lin 
647054229abSJason Zhu 	return 0;
648054229abSJason Zhu }
649054229abSJason Zhu 
650054229abSJason Zhu ulong mtd_derase(struct udevice *udev, lbaint_t start,
651054229abSJason Zhu 		 lbaint_t blkcnt)
652054229abSJason Zhu {
653338697c5SJon Lin 	struct blk_desc *desc = dev_get_uclass_platdata(udev);
654338697c5SJon Lin #if defined(CONFIG_NAND) || defined(CONFIG_MTD_SPI_NAND) || defined(CONFIG_SPI_FLASH_MTD)
655338697c5SJon Lin 	loff_t off = (loff_t)(start * 512);
656338697c5SJon Lin 	size_t len = blkcnt * 512;
657338697c5SJon Lin #endif
658338697c5SJon Lin 	struct mtd_info *mtd;
659338697c5SJon Lin 	int ret = 0;
660338697c5SJon Lin 
661338697c5SJon Lin 	if (!desc)
662338697c5SJon Lin 		return ret;
663338697c5SJon Lin 
664338697c5SJon Lin 	mtd = desc->bdev->priv;
665338697c5SJon Lin 	if (!mtd)
666338697c5SJon Lin 		return 0;
667338697c5SJon Lin 
668338697c5SJon Lin 	pr_debug("mtd derase %s %lx %lx\n", mtd->name, start, blkcnt);
669*e84cdeddSJon Lin 	len = round_up(len, mtd->erasesize);
670338697c5SJon Lin 
671338697c5SJon Lin 	if (blkcnt == 0)
672338697c5SJon Lin 		return 0;
673338697c5SJon Lin 
674338697c5SJon Lin 	if (desc->devnum == BLK_MTD_NAND ||
6759148182dSJon Lin 	    desc->devnum == BLK_MTD_SPI_NAND ||
6769148182dSJon Lin 	    desc->devnum == BLK_MTD_SPI_NOR) {
677338697c5SJon Lin 		ret = mtd_map_erase(mtd, off, len);
678338697c5SJon Lin 		if (ret)
679338697c5SJon Lin 			return ret;
680338697c5SJon Lin 	} else {
681338697c5SJon Lin 		return 0;
682338697c5SJon Lin 	}
683338697c5SJon Lin 
6847182d4beSJon Lin 	return blkcnt;
685054229abSJason Zhu }
686a0166cc6SJason Zhu #endif
687054229abSJason Zhu 
688054229abSJason Zhu static int mtd_blk_probe(struct udevice *udev)
689054229abSJason Zhu {
6906524556dSJon Lin 	struct mtd_info *mtd;
691054229abSJason Zhu 	struct blk_desc *desc = dev_get_uclass_platdata(udev);
6926524556dSJon Lin 	int ret, i = 0;
6936524556dSJon Lin 
6946524556dSJon Lin 	mtd = dev_get_uclass_priv(udev->parent);
6956524556dSJon Lin 	if (mtd->type == MTD_NANDFLASH && desc->devnum == BLK_MTD_NAND) {
6966524556dSJon Lin #ifndef CONFIG_SPL_BUILD
6976524556dSJon Lin 		mtd = dev_get_priv(udev->parent);
6986524556dSJon Lin #endif
6996524556dSJon Lin 	}
700054229abSJason Zhu 
701479a1588SJon Lin 	/* Fill mtd devices information */
702479a1588SJon Lin 	if (is_power_of_2(mtd->erasesize))
703479a1588SJon Lin 		mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
704479a1588SJon Lin 	else
705479a1588SJon Lin 		mtd->erasesize_shift = 0;
706479a1588SJon Lin 
707479a1588SJon Lin 	if (is_power_of_2(mtd->writesize))
708479a1588SJon Lin 		mtd->writesize_shift = ffs(mtd->writesize) - 1;
709479a1588SJon Lin 	else
710479a1588SJon Lin 		mtd->writesize_shift = 0;
711479a1588SJon Lin 
712479a1588SJon Lin 	mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
713479a1588SJon Lin 	mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
714479a1588SJon Lin 
715c9289eddSJason Zhu 	desc->bdev->priv = mtd;
716054229abSJason Zhu 	sprintf(desc->vendor, "0x%.4x", 0x2207);
717a5c69560SJon Lin 	if (strncmp(mtd->name, "nand", 4) == 0)
718a5c69560SJon Lin 		memcpy(desc->product, "rk-nand", strlen("rk-nand"));
719a5c69560SJon Lin 	else
720e6482de4SJason Zhu 		memcpy(desc->product, mtd->name, strlen(mtd->name));
721054229abSJason Zhu 	memcpy(desc->revision, "V1.00", sizeof("V1.00"));
722f1892190SJason Zhu 	if (mtd->type == MTD_NANDFLASH) {
7236524556dSJon Lin #ifdef CONFIG_NAND
724f3ba630bSJason Zhu 		if (desc->devnum == BLK_MTD_NAND)
7256524556dSJon Lin 			i = NAND_BBT_SCAN_MAXBLOCKS;
7266524556dSJon Lin 		else if (desc->devnum == BLK_MTD_SPI_NAND)
7276524556dSJon Lin 			i = NANDDEV_BBT_SCAN_MAXBLOCKS;
7286524556dSJon Lin #endif
7296524556dSJon Lin 
7301d39542fSJason Zhu 		/*
7311d39542fSJason Zhu 		 * Find the first useful block in the end,
7321d39542fSJason Zhu 		 * and it is the end lba of the nand storage.
7331d39542fSJason Zhu 		 */
7346524556dSJon Lin 		for (; i < (mtd->size / mtd->erasesize); i++) {
7351d39542fSJason Zhu 			ret =  mtd_block_isbad(mtd,
7361d39542fSJason Zhu 					       mtd->size - mtd->erasesize * (i + 1));
7371d39542fSJason Zhu 			if (!ret) {
7381d39542fSJason Zhu 				desc->lba = (mtd->size >> 9) -
7391d39542fSJason Zhu 					(mtd->erasesize >> 9) * i;
7401d39542fSJason Zhu 				break;
7411d39542fSJason Zhu 			}
7421d39542fSJason Zhu 		}
743f1892190SJason Zhu 	} else {
744f1892190SJason Zhu 		desc->lba = mtd->size >> 9;
745f1892190SJason Zhu 	}
746054229abSJason Zhu 
7471d39542fSJason Zhu 	debug("MTD: desc->lba is %lx\n", desc->lba);
7481d39542fSJason Zhu 
749054229abSJason Zhu 	return 0;
750054229abSJason Zhu }
751054229abSJason Zhu 
752054229abSJason Zhu static const struct blk_ops mtd_blk_ops = {
753054229abSJason Zhu 	.read	= mtd_dread,
754a0166cc6SJason Zhu #if CONFIG_IS_ENABLED(MTD_WRITE)
755054229abSJason Zhu 	.write	= mtd_dwrite,
756054229abSJason Zhu 	.erase	= mtd_derase,
757054229abSJason Zhu #endif
758054229abSJason Zhu };
759054229abSJason Zhu 
760054229abSJason Zhu U_BOOT_DRIVER(mtd_blk) = {
761054229abSJason Zhu 	.name		= "mtd_blk",
762054229abSJason Zhu 	.id		= UCLASS_BLK,
763054229abSJason Zhu 	.ops		= &mtd_blk_ops,
764054229abSJason Zhu 	.probe		= mtd_blk_probe,
765054229abSJason Zhu };
766