1 /* 2 * Copyright 2008, Freescale Semiconductor, Inc 3 * Andy Fleming 4 * 5 * Based vaguely on the Linux code 6 * 7 * SPDX-License-Identifier: GPL-2.0+ 8 */ 9 10 #include <config.h> 11 #include <common.h> 12 #include <dm.h> 13 #include <part.h> 14 #include <div64.h> 15 #include <linux/math64.h> 16 #include "mmc_private.h" 17 18 static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt) 19 { 20 struct mmc_cmd cmd; 21 ulong end; 22 int err, start_cmd, end_cmd; 23 uint erase_mode; 24 25 if (mmc->high_capacity) { 26 end = start + blkcnt - 1; 27 } else { 28 end = (start + blkcnt - 1) * mmc->write_bl_len; 29 start *= mmc->write_bl_len; 30 } 31 32 /* 33 * The SD card have just one erase mode, the erase command 34 * perform an erase on the sector(s). 35 * The Emmc have four erase mode. We use the trim mode with 36 * cmd.cmdarg equal to MMC_TRIM_ARG which command performs an 37 * erase on the sector(s). 38 */ 39 if (IS_SD(mmc)) { 40 start_cmd = SD_CMD_ERASE_WR_BLK_START; 41 end_cmd = SD_CMD_ERASE_WR_BLK_END; 42 erase_mode = MMC_ERASE_ARG; 43 } else { 44 start_cmd = MMC_CMD_ERASE_GROUP_START; 45 end_cmd = MMC_CMD_ERASE_GROUP_END; 46 if (mmc->esr.mmc_can_trim) 47 erase_mode = MMC_TRIM_ARG; 48 else 49 erase_mode = MMC_ERASE_ARG; 50 } 51 52 cmd.cmdidx = start_cmd; 53 cmd.cmdarg = start; 54 cmd.resp_type = MMC_RSP_R1; 55 56 err = mmc_send_cmd(mmc, &cmd, NULL); 57 if (err) 58 goto err_out; 59 60 cmd.cmdidx = end_cmd; 61 cmd.cmdarg = end; 62 63 err = mmc_send_cmd(mmc, &cmd, NULL); 64 if (err) 65 goto err_out; 66 67 cmd.cmdidx = MMC_CMD_ERASE; 68 cmd.cmdarg = erase_mode; 69 cmd.resp_type = MMC_RSP_R1b; 70 71 err = mmc_send_cmd(mmc, &cmd, NULL); 72 if (err) 73 goto err_out; 74 75 return 0; 76 77 err_out: 78 puts("mmc erase failed\n"); 79 return err; 80 } 81 82 #ifdef CONFIG_BLK 83 ulong mmc_berase(struct udevice *dev, lbaint_t start, lbaint_t blkcnt) 84 #else 85 ulong mmc_berase(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt) 86 #endif 87 { 88 #ifdef CONFIG_BLK 89 struct blk_desc *block_dev = dev_get_uclass_platdata(dev); 90 #endif 91 int dev_num = block_dev->devnum; 92 int err = 0; 93 u32 start_rem, blkcnt_rem; 94 struct mmc *mmc = find_mmc_device(dev_num); 95 lbaint_t blk = 0, blk_r = 0; 96 int timeout = 1000; 97 int mode = 0; 98 99 if (!mmc) 100 return -1; 101 102 if (!blkcnt) 103 return 0; 104 105 err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, 106 block_dev->hwpart); 107 108 if (err < 0) 109 return -1; 110 111 if (!IS_SD(mmc)) { 112 if (mmc->esr.mmc_can_trim) 113 mode = 1; 114 } 115 116 if (mode) { 117 err = mmc_erase_t(mmc, start, blkcnt); 118 if (err) 119 return err; 120 if (mmc_send_status(mmc, timeout)) 121 return 0; 122 123 return blkcnt; 124 } else { 125 /* 126 * We want to see if the requested start or total block 127 * count are unaligned. We discard the whole numbers and 128 * only care about the remainder. 129 */ 130 err = div_u64_rem(start, mmc->erase_grp_size, &start_rem); 131 err = div_u64_rem(blkcnt, mmc->erase_grp_size, &blkcnt_rem); 132 if (start_rem || blkcnt_rem) 133 printf("\n\nCaution! Your devices Erase group is 0x%x\n" 134 "The erase range would be change to " 135 "0x" LBAF "~0x" LBAF "\n\n", 136 mmc->erase_grp_size, 137 start & ~(mmc->erase_grp_size - 1), 138 ((start + blkcnt + mmc->erase_grp_size) 139 & ~(mmc->erase_grp_size - 1)) - 1); 140 while (blk < blkcnt) { 141 if (IS_SD(mmc) && mmc->ssr.au) { 142 blk_r = ((blkcnt - blk) > mmc->ssr.au) ? 143 mmc->ssr.au : (blkcnt - blk); 144 } else { 145 blk_r = ((blkcnt - blk) > mmc->erase_grp_size) ? 146 mmc->erase_grp_size : (blkcnt - blk); 147 } 148 err = mmc_erase_t(mmc, start + blk, blk_r); 149 if (err) 150 break; 151 152 blk += blk_r; 153 154 /* Waiting for the ready status */ 155 if (mmc_send_status(mmc, timeout)) 156 return 0; 157 } 158 return blk; 159 } 160 } 161 162 static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start, 163 lbaint_t blkcnt, const void *src) 164 { 165 struct mmc_cmd cmd; 166 struct mmc_data data; 167 int timeout = 1000; 168 ulong writen_cnt = blkcnt; 169 170 if ((start + blkcnt) > mmc_get_blk_desc(mmc)->lba) { 171 printf("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n", 172 start + blkcnt, mmc_get_blk_desc(mmc)->lba); 173 return 0; 174 } 175 176 if (blkcnt == 0) 177 return 0; 178 else if (blkcnt == 1) 179 cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK; 180 else 181 cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK; 182 183 if (mmc->high_capacity) 184 cmd.cmdarg = start; 185 else 186 cmd.cmdarg = start * mmc->write_bl_len; 187 188 cmd.resp_type = MMC_RSP_R1; 189 190 data.src = src; 191 data.blocks = blkcnt; 192 data.blocksize = mmc->write_bl_len; 193 data.flags = MMC_DATA_WRITE; 194 195 if (mmc_send_cmd(mmc, &cmd, &data)) { 196 printf("mmc write failed\n"); 197 writen_cnt = 0; 198 } 199 200 /* SPI multiblock writes terminate using a special 201 * token, not a STOP_TRANSMISSION request. 202 */ 203 if (!mmc_host_is_spi(mmc) && blkcnt > 1) { 204 cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; 205 cmd.cmdarg = 0; 206 cmd.resp_type = MMC_RSP_R1b; 207 if (mmc_send_cmd(mmc, &cmd, NULL)) { 208 printf("mmc fail to send stop cmd\n"); 209 return 0; 210 } 211 } 212 213 /* Waiting for the ready status */ 214 if (mmc_send_status(mmc, timeout)) 215 return 0; 216 217 return writen_cnt; 218 } 219 220 #ifdef CONFIG_BLK 221 ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, 222 const void *src) 223 #else 224 ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, 225 const void *src) 226 #endif 227 { 228 #ifdef CONFIG_BLK 229 struct blk_desc *block_dev = dev_get_uclass_platdata(dev); 230 #endif 231 int dev_num = block_dev->devnum; 232 lbaint_t cur, blocks_todo = blkcnt; 233 int err; 234 235 struct mmc *mmc = find_mmc_device(dev_num); 236 if (!mmc) 237 return 0; 238 239 err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, block_dev->hwpart); 240 if (err < 0) 241 return 0; 242 243 if (mmc_set_blocklen(mmc, mmc->write_bl_len)) 244 return 0; 245 246 do { 247 cur = (blocks_todo > mmc->cfg->b_max) ? 248 mmc->cfg->b_max : blocks_todo; 249 if (mmc_write_blocks(mmc, start, cur, src) != cur) { 250 /* retry again with Open-ended Multiple block write */ 251 if (mmc_write_blocks(mmc, start, cur, src) != cur) 252 return 0; 253 } 254 blocks_todo -= cur; 255 start += cur; 256 src += cur * mmc->write_bl_len; 257 } while (blocks_todo > 0); 258 259 return blkcnt; 260 } 261