1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * MTD block - abstraction over MTD subsystem, allowing
4  * to read and write in blocks using BLK UCLASS.
5  *
6  * - Read algorithm:
7  *
8  *   1. Convert start block number to start address.
9  *   2. Read block_dev->blksz bytes using mtd_read() and
10  *      add to start address pointer block_dev->blksz bytes,
11  *      until the requested number of blocks have been read.
12  *
13  * - Write algorithm:
14  *
15  *   1. Convert start block number to start address.
16  *   2. Round this address down by mtd->erasesize.
17  *
18  *   Erase addr      Start addr
19  *      |                |
20  *      v                v
21  *      +----------------+----------------+----------------+
22  *      |     blksz      |      blksz     |      blksz     |
23  *      +----------------+----------------+----------------+
24  *
25  *   3. Calculate offset between this two addresses.
26  *   4. Read mtd->erasesize bytes using mtd_read() into
27  *      temporary buffer from erase address.
28  *
29  *   Erase addr      Start addr
30  *      |                |
31  *      v                v
32  *      +----------------+----------------+----------------+
33  *      |     blksz      |      blksz     |      blksz     |
34  *      +----------------+----------------+----------------+
35  *      ^
36  *      |
37  *      |
38  *   mtd_read()
39  *   from here
40  *
41  *   5. Copy data from user buffer to temporary buffer with offset,
42  *      calculated at step 3.
43  *   6. Erase and write mtd->erasesize bytes at erase address
44  *      pointer using mtd_erase/mtd_write().
45  *   7. Add to erase address pointer mtd->erasesize bytes.
46  *   8. goto 1 until the requested number of blocks have
47  *      been written.
48  *
49  * (C) Copyright 2024 SaluteDevices, Inc.
50  *
51  * Author: Alexey Romanov <avromanov@salutedevices.com>
52  */
53 
54 #include <blk.h>
55 #include <part.h>
56 #include <dm/device.h>
57 #include <dm/device-internal.h>
58 #include <linux/mtd/mtd.h>
59 
mtd_bind(struct udevice * dev,struct mtd_info ** mtd)60 int mtd_bind(struct udevice *dev, struct mtd_info **mtd)
61 {
62 	struct blk_desc *bdesc;
63 	struct udevice *bdev;
64 	int ret;
65 
66 	ret = blk_create_devicef(dev, "mtd_blk", "blk", UCLASS_MTD,
67 				 -1, 512, 0, &bdev);
68 	if (ret) {
69 		pr_err("Cannot create block device\n");
70 		return ret;
71 	}
72 
73 	bdesc = dev_get_uclass_plat(bdev);
74 	dev_set_priv(bdev, mtd);
75 	bdesc->bdev = bdev;
76 	bdesc->part_type = PART_TYPE_MTD;
77 
78 	return 0;
79 }
80 
mtd_blk_read(struct udevice * dev,lbaint_t start,lbaint_t blkcnt,void * dst)81 static ulong mtd_blk_read(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
82 			  void *dst)
83 {
84 	struct blk_desc *block_dev = dev_get_uclass_plat(dev);
85 	struct mtd_info *mtd = blk_desc_to_mtd(block_dev);
86 	unsigned int sect_size = block_dev->blksz;
87 	lbaint_t cur = start;
88 	ulong read_cnt = 0;
89 
90 	while (read_cnt < blkcnt) {
91 		int ret;
92 		loff_t sect_start = cur * sect_size;
93 		size_t retlen;
94 
95 		ret = mtd_read(mtd, sect_start, sect_size, &retlen, dst);
96 		if (ret)
97 			return ret;
98 
99 		if (retlen != sect_size) {
100 			pr_err("mtdblock: failed to read block 0x" LBAF "\n", cur);
101 			return -EIO;
102 		}
103 
104 		cur++;
105 		dst += sect_size;
106 		read_cnt++;
107 	}
108 
109 	return read_cnt;
110 }
111 
mtd_erase_write(struct mtd_info * mtd,uint64_t start,const void * src)112 static int mtd_erase_write(struct mtd_info *mtd, uint64_t start, const void *src)
113 {
114 	int ret;
115 	size_t retlen;
116 	struct erase_info erase = { 0 };
117 
118 	erase.mtd = mtd;
119 	erase.addr = start;
120 	erase.len = mtd->erasesize;
121 
122 	ret = mtd_erase(mtd, &erase);
123 	if (ret)
124 		return ret;
125 
126 	ret = mtd_write(mtd, start, mtd->erasesize, &retlen, src);
127 	if (ret)
128 		return ret;
129 
130 	if (retlen != mtd->erasesize) {
131 		pr_err("mtdblock: failed to read block at 0x%llx\n", start);
132 		return -EIO;
133 	}
134 
135 	return 0;
136 }
137 
mtd_blk_write(struct udevice * dev,lbaint_t start,lbaint_t blkcnt,const void * src)138 static ulong mtd_blk_write(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
139 			   const void *src)
140 {
141 	struct blk_desc *block_dev = dev_get_uclass_plat(dev);
142 	struct mtd_info *mtd = blk_desc_to_mtd(block_dev);
143 	unsigned int sect_size = block_dev->blksz;
144 	lbaint_t cur = start, blocks_todo = blkcnt;
145 	ulong write_cnt = 0;
146 	u8 *buf;
147 	int ret = 0;
148 
149 	buf = malloc(mtd->erasesize);
150 	if (!buf)
151 		return -ENOMEM;
152 
153 	while (blocks_todo > 0) {
154 		loff_t sect_start = cur * sect_size;
155 		loff_t erase_start = ALIGN_DOWN(sect_start, mtd->erasesize);
156 		u32 offset = sect_start - erase_start;
157 		size_t cur_size = min_t(size_t,  mtd->erasesize - offset,
158 					blocks_todo * sect_size);
159 		size_t retlen;
160 		lbaint_t written;
161 
162 		ret = mtd_read(mtd, erase_start, mtd->erasesize, &retlen, buf);
163 		if (ret)
164 			goto out;
165 
166 		if (retlen != mtd->erasesize) {
167 			pr_err("mtdblock: failed to read block 0x" LBAF "\n", cur);
168 			ret = -EIO;
169 			goto out;
170 		}
171 
172 		memcpy(buf + offset, src, cur_size);
173 
174 		ret = mtd_erase_write(mtd, erase_start, buf);
175 		if (ret)
176 			goto out;
177 
178 		written = cur_size / sect_size;
179 
180 		blocks_todo -= written;
181 		cur += written;
182 		src += cur_size;
183 		write_cnt += written;
184 	}
185 
186 out:
187 	free(buf);
188 
189 	if (ret)
190 		return ret;
191 
192 	return write_cnt;
193 }
194 
mtd_blk_probe(struct udevice * dev)195 static int mtd_blk_probe(struct udevice *dev)
196 {
197 	struct blk_desc *bdesc;
198 	struct mtd_info *mtd;
199 	int ret;
200 
201 	ret = device_probe(dev);
202 	if (ret) {
203 		pr_err("Probing %s failed (err=%d)\n", dev->name, ret);
204 		return ret;
205 	}
206 
207 	bdesc = dev_get_uclass_plat(dev);
208 	mtd = blk_desc_to_mtd(bdesc);
209 
210 	if (mtd_type_is_nand(mtd))
211 		pr_warn("MTD device '%s' is NAND, please use UBI devices instead\n",
212 			mtd->name);
213 
214 	return 0;
215 }
216 
217 static const struct blk_ops mtd_blk_ops = {
218 	.read = mtd_blk_read,
219 	.write = mtd_blk_write,
220 };
221 
222 U_BOOT_DRIVER(mtd_blk) = {
223 	.name = "mtd_blk",
224 	.id = UCLASS_BLK,
225 	.ops = &mtd_blk_ops,
226 	.probe = mtd_blk_probe,
227 };
228