blob: 66a79b8c56ae3dd4eea806e677673bf84d7980a1 [file] [log] [blame]
Alexey Romanov8e1fd2e2024-07-18 08:46:05 +03001// 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
60int 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
81static 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
112static 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
138static 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
186out:
187 free(buf);
188
189 if (ret)
190 return ret;
191
192 return write_cnt;
193}
194
195static 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
217static const struct blk_ops mtd_blk_ops = {
218 .read = mtd_blk_read,
219 .write = mtd_blk_write,
220};
221
222U_BOOT_DRIVER(mtd_blk) = {
223 .name = "mtd_blk",
224 .id = UCLASS_BLK,
225 .ops = &mtd_blk_ops,
226 .probe = mtd_blk_probe,
227};