blob: 92d1e8727ef0d9e2b9bef1b976d0b2cb8fdc483d [file] [log] [blame]
Haojian Zhuangfffe9e72016-03-18 22:08:26 +08001/*
Qixiang Xuede9a192018-01-17 13:31:21 +08002 * Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
Haojian Zhuangfffe9e72016-03-18 22:08:26 +08003 *
dp-armfa3cf0b2017-05-03 09:38:09 +01004 * SPDX-License-Identifier: BSD-3-Clause
Haojian Zhuangfffe9e72016-03-18 22:08:26 +08005 *
6 * Defines a simple and generic interface to access eMMC device.
7 */
8
9#include <arch_helpers.h>
10#include <assert.h>
11#include <debug.h>
12#include <emmc.h>
13#include <errno.h>
14#include <string.h>
Douglas Raillarda8954fc2017-01-26 15:54:44 +000015#include <utils.h>
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080016
17static const emmc_ops_t *ops;
18static unsigned int emmc_ocr_value;
19static emmc_csd_t emmc_csd;
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +080020static unsigned int emmc_flags;
21
22static int is_cmd23_enabled(void)
23{
24 return (!!(emmc_flags & EMMC_FLAG_CMD23));
25}
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080026
27static int emmc_device_state(void)
28{
29 emmc_cmd_t cmd;
30 int ret;
31
32 do {
Douglas Raillarda8954fc2017-01-26 15:54:44 +000033 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080034 cmd.cmd_idx = EMMC_CMD13;
35 cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET;
36 cmd.resp_type = EMMC_RESPONSE_R1;
37 ret = ops->send_cmd(&cmd);
38 assert(ret == 0);
39 assert((cmd.resp_data[0] & STATUS_SWITCH_ERROR) == 0);
40 /* Ignore improbable errors in release builds */
41 (void)ret;
42 } while ((cmd.resp_data[0] & STATUS_READY_FOR_DATA) == 0);
43 return EMMC_GET_STATE(cmd.resp_data[0]);
44}
45
46static void emmc_set_ext_csd(unsigned int ext_cmd, unsigned int value)
47{
48 emmc_cmd_t cmd;
49 int ret, state;
50
Douglas Raillarda8954fc2017-01-26 15:54:44 +000051 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080052 cmd.cmd_idx = EMMC_CMD6;
53 cmd.cmd_arg = EXTCSD_WRITE_BYTES | EXTCSD_CMD(ext_cmd) |
54 EXTCSD_VALUE(value) | 1;
55 ret = ops->send_cmd(&cmd);
56 assert(ret == 0);
57
58 /* wait to exit PRG state */
59 do {
60 state = emmc_device_state();
61 } while (state == EMMC_STATE_PRG);
62 /* Ignore improbable errors in release builds */
63 (void)ret;
64}
65
66static void emmc_set_ios(int clk, int bus_width)
67{
68 int ret;
69
70 /* set IO speed & IO bus width */
71 if (emmc_csd.spec_vers == 4)
72 emmc_set_ext_csd(CMD_EXTCSD_BUS_WIDTH, bus_width);
73 ret = ops->set_ios(clk, bus_width);
74 assert(ret == 0);
75 /* Ignore improbable errors in release builds */
76 (void)ret;
77}
78
79static int emmc_enumerate(int clk, int bus_width)
80{
81 emmc_cmd_t cmd;
82 int ret, state;
83
84 ops->init();
85
86 /* CMD0: reset to IDLE */
Douglas Raillarda8954fc2017-01-26 15:54:44 +000087 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080088 cmd.cmd_idx = EMMC_CMD0;
89 ret = ops->send_cmd(&cmd);
90 assert(ret == 0);
91
92 while (1) {
93 /* CMD1: get OCR register */
Douglas Raillarda8954fc2017-01-26 15:54:44 +000094 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +080095 cmd.cmd_idx = EMMC_CMD1;
96 cmd.cmd_arg = OCR_SECTOR_MODE | OCR_VDD_MIN_2V7 |
97 OCR_VDD_MIN_1V7;
98 cmd.resp_type = EMMC_RESPONSE_R3;
99 ret = ops->send_cmd(&cmd);
100 assert(ret == 0);
101 emmc_ocr_value = cmd.resp_data[0];
102 if (emmc_ocr_value & OCR_POWERUP)
103 break;
104 }
105
106 /* CMD2: Card Identification */
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000107 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800108 cmd.cmd_idx = EMMC_CMD2;
109 cmd.resp_type = EMMC_RESPONSE_R2;
110 ret = ops->send_cmd(&cmd);
111 assert(ret == 0);
112
113 /* CMD3: Set Relative Address */
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000114 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800115 cmd.cmd_idx = EMMC_CMD3;
116 cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET;
117 cmd.resp_type = EMMC_RESPONSE_R1;
118 ret = ops->send_cmd(&cmd);
119 assert(ret == 0);
120
121 /* CMD9: CSD Register */
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000122 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800123 cmd.cmd_idx = EMMC_CMD9;
124 cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET;
125 cmd.resp_type = EMMC_RESPONSE_R2;
126 ret = ops->send_cmd(&cmd);
127 assert(ret == 0);
128 memcpy(&emmc_csd, &cmd.resp_data, sizeof(cmd.resp_data));
129
130 /* CMD7: Select Card */
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000131 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800132 cmd.cmd_idx = EMMC_CMD7;
133 cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET;
134 cmd.resp_type = EMMC_RESPONSE_R1;
135 ret = ops->send_cmd(&cmd);
136 assert(ret == 0);
137 /* wait to TRAN state */
138 do {
139 state = emmc_device_state();
140 } while (state != EMMC_STATE_TRAN);
141
142 emmc_set_ios(clk, bus_width);
143 return ret;
144}
145
146size_t emmc_read_blocks(int lba, uintptr_t buf, size_t size)
147{
148 emmc_cmd_t cmd;
149 int ret;
150
151 assert((ops != 0) &&
152 (ops->read != 0) &&
153 ((buf & EMMC_BLOCK_MASK) == 0) &&
154 ((size & EMMC_BLOCK_MASK) == 0));
155
156 inv_dcache_range(buf, size);
157 ret = ops->prepare(lba, buf, size);
158 assert(ret == 0);
159
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800160 if (is_cmd23_enabled()) {
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000161 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800162 /* set block count */
163 cmd.cmd_idx = EMMC_CMD23;
164 cmd.cmd_arg = size / EMMC_BLOCK_SIZE;
165 cmd.resp_type = EMMC_RESPONSE_R1;
166 ret = ops->send_cmd(&cmd);
167 assert(ret == 0);
168
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000169 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800170 cmd.cmd_idx = EMMC_CMD18;
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800171 } else {
172 if (size > EMMC_BLOCK_SIZE)
173 cmd.cmd_idx = EMMC_CMD18;
174 else
175 cmd.cmd_idx = EMMC_CMD17;
176 }
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800177 if ((emmc_ocr_value & OCR_ACCESS_MODE_MASK) == OCR_BYTE_MODE)
178 cmd.cmd_arg = lba * EMMC_BLOCK_SIZE;
179 else
180 cmd.cmd_arg = lba;
181 cmd.resp_type = EMMC_RESPONSE_R1;
182 ret = ops->send_cmd(&cmd);
183 assert(ret == 0);
184
185 ret = ops->read(lba, buf, size);
186 assert(ret == 0);
187
188 /* wait buffer empty */
189 emmc_device_state();
190
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800191 if (is_cmd23_enabled() == 0) {
192 if (size > EMMC_BLOCK_SIZE) {
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000193 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800194 cmd.cmd_idx = EMMC_CMD12;
195 ret = ops->send_cmd(&cmd);
196 assert(ret == 0);
197 }
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800198 }
199 /* Ignore improbable errors in release builds */
200 (void)ret;
201 return size;
202}
203
204size_t emmc_write_blocks(int lba, const uintptr_t buf, size_t size)
205{
206 emmc_cmd_t cmd;
207 int ret;
208
209 assert((ops != 0) &&
210 (ops->write != 0) &&
211 ((buf & EMMC_BLOCK_MASK) == 0) &&
212 ((size & EMMC_BLOCK_MASK) == 0));
213
214 clean_dcache_range(buf, size);
215 ret = ops->prepare(lba, buf, size);
216 assert(ret == 0);
217
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800218 if (is_cmd23_enabled()) {
219 /* set block count */
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000220 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800221 cmd.cmd_idx = EMMC_CMD23;
222 cmd.cmd_arg = size / EMMC_BLOCK_SIZE;
223 cmd.resp_type = EMMC_RESPONSE_R1;
224 ret = ops->send_cmd(&cmd);
225 assert(ret == 0);
226
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000227 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800228 cmd.cmd_idx = EMMC_CMD25;
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800229 } else {
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000230 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800231 if (size > EMMC_BLOCK_SIZE)
232 cmd.cmd_idx = EMMC_CMD25;
233 else
234 cmd.cmd_idx = EMMC_CMD24;
235 }
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800236 if ((emmc_ocr_value & OCR_ACCESS_MODE_MASK) == OCR_BYTE_MODE)
237 cmd.cmd_arg = lba * EMMC_BLOCK_SIZE;
238 else
239 cmd.cmd_arg = lba;
240 cmd.resp_type = EMMC_RESPONSE_R1;
241 ret = ops->send_cmd(&cmd);
242 assert(ret == 0);
243
244 ret = ops->write(lba, buf, size);
245 assert(ret == 0);
246
247 /* wait buffer empty */
248 emmc_device_state();
249
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800250 if (is_cmd23_enabled() == 0) {
251 if (size > EMMC_BLOCK_SIZE) {
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000252 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800253 cmd.cmd_idx = EMMC_CMD12;
254 ret = ops->send_cmd(&cmd);
255 assert(ret == 0);
256 }
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800257 }
258 /* Ignore improbable errors in release builds */
259 (void)ret;
260 return size;
261}
262
263size_t emmc_erase_blocks(int lba, size_t size)
264{
265 emmc_cmd_t cmd;
266 int ret, state;
267
268 assert(ops != 0);
269 assert((size != 0) && ((size % EMMC_BLOCK_SIZE) == 0));
270
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000271 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800272 cmd.cmd_idx = EMMC_CMD35;
273 cmd.cmd_arg = lba;
274 cmd.resp_type = EMMC_RESPONSE_R1;
275 ret = ops->send_cmd(&cmd);
276 assert(ret == 0);
277
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000278 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800279 cmd.cmd_idx = EMMC_CMD36;
280 cmd.cmd_arg = lba + (size / EMMC_BLOCK_SIZE) - 1;
281 cmd.resp_type = EMMC_RESPONSE_R1;
282 ret = ops->send_cmd(&cmd);
283 assert(ret == 0);
284
Douglas Raillarda8954fc2017-01-26 15:54:44 +0000285 zeromem(&cmd, sizeof(emmc_cmd_t));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800286 cmd.cmd_idx = EMMC_CMD38;
287 cmd.resp_type = EMMC_RESPONSE_R1B;
288 ret = ops->send_cmd(&cmd);
289 assert(ret == 0);
290
291 /* wait to TRAN state */
292 do {
293 state = emmc_device_state();
294 } while (state != EMMC_STATE_TRAN);
295 /* Ignore improbable errors in release builds */
296 (void)ret;
297 return size;
298}
299
300static inline void emmc_rpmb_enable(void)
301{
302 emmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG,
303 PART_CFG_BOOT_PARTITION1_ENABLE |
304 PART_CFG_PARTITION1_ACCESS);
305}
306
307static inline void emmc_rpmb_disable(void)
308{
309 emmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG,
310 PART_CFG_BOOT_PARTITION1_ENABLE);
311}
312
313size_t emmc_rpmb_read_blocks(int lba, uintptr_t buf, size_t size)
314{
315 size_t size_read;
316
317 emmc_rpmb_enable();
318 size_read = emmc_read_blocks(lba, buf, size);
319 emmc_rpmb_disable();
320 return size_read;
321}
322
323size_t emmc_rpmb_write_blocks(int lba, const uintptr_t buf, size_t size)
324{
325 size_t size_written;
326
327 emmc_rpmb_enable();
328 size_written = emmc_write_blocks(lba, buf, size);
329 emmc_rpmb_disable();
330 return size_written;
331}
332
333size_t emmc_rpmb_erase_blocks(int lba, size_t size)
334{
335 size_t size_erased;
336
337 emmc_rpmb_enable();
338 size_erased = emmc_erase_blocks(lba, size);
339 emmc_rpmb_disable();
340 return size_erased;
341}
342
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800343void emmc_init(const emmc_ops_t *ops_ptr, int clk, int width,
344 unsigned int flags)
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800345{
346 assert((ops_ptr != 0) &&
347 (ops_ptr->init != 0) &&
348 (ops_ptr->send_cmd != 0) &&
349 (ops_ptr->set_ios != 0) &&
350 (ops_ptr->prepare != 0) &&
351 (ops_ptr->read != 0) &&
352 (ops_ptr->write != 0) &&
353 (clk != 0) &&
354 ((width == EMMC_BUS_WIDTH_1) ||
355 (width == EMMC_BUS_WIDTH_4) ||
Qixiang Xuede9a192018-01-17 13:31:21 +0800356 (width == EMMC_BUS_WIDTH_8) ||
357 (width == EMMC_BUS_WIDTH_DDR_4) ||
358 (width == EMMC_BUS_WIDTH_DDR_8)));
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800359 ops = ops_ptr;
Haojian Zhuangbd5cf9c2016-08-02 20:51:27 +0800360 emmc_flags = flags;
Haojian Zhuangfffe9e72016-03-18 22:08:26 +0800361
362 emmc_enumerate(clk, width);
363}