Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 1 | /* |
Qixiang Xu | ede9a19 | 2018-01-17 13:31:21 +0800 | [diff] [blame] | 2 | * Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved. |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 3 | * |
dp-arm | fa3cf0b | 2017-05-03 09:38:09 +0100 | [diff] [blame] | 4 | * SPDX-License-Identifier: BSD-3-Clause |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 5 | * |
| 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 15 | #include <utils.h> |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 16 | |
| 17 | static const emmc_ops_t *ops; |
| 18 | static unsigned int emmc_ocr_value; |
| 19 | static emmc_csd_t emmc_csd; |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 20 | static unsigned int emmc_flags; |
| 21 | |
| 22 | static int is_cmd23_enabled(void) |
| 23 | { |
| 24 | return (!!(emmc_flags & EMMC_FLAG_CMD23)); |
| 25 | } |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 26 | |
| 27 | static int emmc_device_state(void) |
| 28 | { |
| 29 | emmc_cmd_t cmd; |
| 30 | int ret; |
| 31 | |
| 32 | do { |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 33 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 34 | 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 | |
| 46 | static void emmc_set_ext_csd(unsigned int ext_cmd, unsigned int value) |
| 47 | { |
| 48 | emmc_cmd_t cmd; |
| 49 | int ret, state; |
| 50 | |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 51 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 52 | 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 | |
| 66 | static 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 | |
| 79 | static 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 87 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 88 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 94 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 95 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 107 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 108 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 114 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 115 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 122 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 123 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 131 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 132 | 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 | |
| 146 | size_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 Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 160 | if (is_cmd23_enabled()) { |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 161 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 162 | /* 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 169 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 170 | cmd.cmd_idx = EMMC_CMD18; |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 171 | } else { |
| 172 | if (size > EMMC_BLOCK_SIZE) |
| 173 | cmd.cmd_idx = EMMC_CMD18; |
| 174 | else |
| 175 | cmd.cmd_idx = EMMC_CMD17; |
| 176 | } |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 177 | 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 Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 191 | if (is_cmd23_enabled() == 0) { |
| 192 | if (size > EMMC_BLOCK_SIZE) { |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 193 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 194 | cmd.cmd_idx = EMMC_CMD12; |
| 195 | ret = ops->send_cmd(&cmd); |
| 196 | assert(ret == 0); |
| 197 | } |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 198 | } |
| 199 | /* Ignore improbable errors in release builds */ |
| 200 | (void)ret; |
| 201 | return size; |
| 202 | } |
| 203 | |
| 204 | size_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 Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 218 | if (is_cmd23_enabled()) { |
| 219 | /* set block count */ |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 220 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 221 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 227 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 228 | cmd.cmd_idx = EMMC_CMD25; |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 229 | } else { |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 230 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 231 | if (size > EMMC_BLOCK_SIZE) |
| 232 | cmd.cmd_idx = EMMC_CMD25; |
| 233 | else |
| 234 | cmd.cmd_idx = EMMC_CMD24; |
| 235 | } |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 236 | 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 Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 250 | if (is_cmd23_enabled() == 0) { |
| 251 | if (size > EMMC_BLOCK_SIZE) { |
Douglas Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 252 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 253 | cmd.cmd_idx = EMMC_CMD12; |
| 254 | ret = ops->send_cmd(&cmd); |
| 255 | assert(ret == 0); |
| 256 | } |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 257 | } |
| 258 | /* Ignore improbable errors in release builds */ |
| 259 | (void)ret; |
| 260 | return size; |
| 261 | } |
| 262 | |
| 263 | size_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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 271 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 272 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 278 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 279 | 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 Raillard | a8954fc | 2017-01-26 15:54:44 +0000 | [diff] [blame] | 285 | zeromem(&cmd, sizeof(emmc_cmd_t)); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 286 | 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 | |
| 300 | static 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 | |
| 307 | static inline void emmc_rpmb_disable(void) |
| 308 | { |
| 309 | emmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG, |
| 310 | PART_CFG_BOOT_PARTITION1_ENABLE); |
| 311 | } |
| 312 | |
| 313 | size_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 | |
| 323 | size_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 | |
| 333 | size_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 Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 343 | void emmc_init(const emmc_ops_t *ops_ptr, int clk, int width, |
| 344 | unsigned int flags) |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 345 | { |
| 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 Xu | ede9a19 | 2018-01-17 13:31:21 +0800 | [diff] [blame] | 356 | (width == EMMC_BUS_WIDTH_8) || |
| 357 | (width == EMMC_BUS_WIDTH_DDR_4) || |
| 358 | (width == EMMC_BUS_WIDTH_DDR_8))); |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 359 | ops = ops_ptr; |
Haojian Zhuang | bd5cf9c | 2016-08-02 20:51:27 +0800 | [diff] [blame] | 360 | emmc_flags = flags; |
Haojian Zhuang | fffe9e7 | 2016-03-18 22:08:26 +0800 | [diff] [blame] | 361 | |
| 362 | emmc_enumerate(clk, width); |
| 363 | } |