/* * Copyright (c) 2014-2015, Linaro Ltd. All rights reserved. * Copyright (c) 2014-2015, Hisilicon Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of ARM nor the names of its contributors may be used * to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <console.h> #include <debug.h> #include <errno.h> #include <mmio.h> #include <string.h> #include <sp804_timer.h> #include <dw_mmc.h> #include <partitions.h> #include <platform_def.h> #include <hi6220.h> #include <hi6553.h> #define MMC_PLL 100000000 #define IDMAC_DES0_DIC (1 << 1) #define IDMAC_DES0_LD (1 << 2) #define IDMAC_DES0_FS (1 << 3) #define IDMAC_DES0_CH (1 << 4) #define IDMAC_DES0_ER (1 << 5) #define IDMAC_DES0_CES (1 << 30) #define IDMAC_DES0_OWN (1 << 31) #define IDMAC_DES1_BS1(x) ((x) & 0x1fff) #define IDMAC_DES2_BS2(x) (((x) & 0x1fff) << 13) struct idmac_desc { unsigned int des0; unsigned int des1; unsigned int des2; unsigned int des3; }; static inline int mmc_state(unsigned int data) { return ((data & MMC_STATUS_CURRENT_STATE_MASK) >> MMC_STATUS_CURRENT_STATE_SHIFT); } static inline int wait_data_ready(void) { unsigned int data; while (1) { data = mmio_read_32(MMC0_RINTSTS); if (data & (MMC_INT_DCRC | MMC_INT_DRT | MMC_INT_SBE | MMC_INT_EBE)) { NOTICE("unwanted interrupts:0x%x\n", data); return -EINVAL; } if (data & MMC_INT_DTO) break; } /* clear interrupts */ mmio_write_32(MMC0_RINTSTS, ~0); return 0; } static int update_mmc0_clock(void) { unsigned int data; /* CMD_UPDATE_CLK */ data = BIT_CMD_WAIT_PRVDATA_COMPLETE | BIT_CMD_UPDATE_CLOCK_ONLY | BIT_CMD_START; mmio_write_32(MMC0_CMD, data); while (1) { data = mmio_read_32(MMC0_CMD); if (!(data & CMD_START_BIT)) break; data = mmio_read_32(MMC0_RINTSTS); if (data & MMC_INT_HLE) { NOTICE("fail to update mmc clock frequency\n"); return -EINVAL; } } return 0; } static int set_mmc0_clock(int rate) { int ret, divider, found = 0; unsigned int data; for (divider = 1; divider < 256; divider++) { if ((MMC_PLL / (2 * divider)) <= rate) { found = 1; break; } } if (!found) return -EINVAL; /* wait until mmc is idle */ do { data = mmio_read_32(MMC0_STATUS); } while (data & MMC_STS_DATA_BUSY); /* Disable mmc clock first */ mmio_write_32(MMC0_CLKENA, 0); do { ret = update_mmc0_clock(); } while (ret); /* enable mmc clock */ do { mmio_write_32(MMC0_CLKENA, 1); mmio_write_32(MMC0_CLKSRC, 0); mmio_write_32(MMC0_CLKDIV, divider); ret = update_mmc0_clock(); } while (ret); return 0; } static void set_mmc0_io(void) { mmio_write_32(MMC0_CTYPE, MMC_8BIT_MODE); mmio_write_32(MMC0_TMOUT, ~0); /* maxium timeout value */ mmio_write_32(MMC0_DEBNCE, 0x00ffffff); mmio_write_32(MMC0_BLKSIZ, MMC_BLOCK_SIZE); mmio_write_32(MMC0_BYTCNT, 256 * 1024); } static int mmc0_send_cmd(unsigned int cmd, unsigned int arg, unsigned int *buf) { unsigned int data, err_mask; if (!buf) { NOTICE("buf is invalid\n"); return -EFAULT; } mmio_write_32(MMC0_CMDARG, arg); /* clear interrupts */ mmio_write_32(MMC0_RINTSTS, ~0); switch (cmd) { case 0: data = BIT_CMD_SEND_INIT; break; case 1: data = BIT_CMD_RESPONSE_EXPECT; break; case 2: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_LONG_RESPONSE | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_SEND_INIT; break; case 3: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_SEND_INIT; break; case 8: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_READ | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case 9: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_LONG_RESPONSE; break; case 12: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_STOP_ABORT_CMD; break; case 17: case 18: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_READ | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case 24: case 25: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_WRITE | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case 30: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED; break; case 7: if (arg) data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC; else data = 0; break; default: data = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC; break; } data |= (cmd & 0x3f) | BIT_CMD_USE_HOLD_REG | BIT_CMD_START; mmio_write_32(MMC0_CMD, data); err_mask = MMC_INT_EBE | MMC_INT_HLE | MMC_INT_RTO | MMC_INT_RCRC | MMC_INT_RE; do { data = mmio_read_32(MMC0_RINTSTS); if (data & err_mask) { NOTICE("mmc: error status 0x%x\n", data); return -EIO; } } while (!(data & MMC_INT_CMD_DONE)); buf[0] = mmio_read_32(MMC0_RESP0); if ((cmd == 2) || (cmd == 9)) { buf[1] = mmio_read_32(MMC0_RESP1); buf[2] = mmio_read_32(MMC0_RESP2); buf[3] = mmio_read_32(MMC0_RESP3); } return 0; } /* Only print error message if it meets failure? */ static void mmc0_check_tran_mode(void) { unsigned int buf[4]; int ret; mmio_write_32(MMC0_RINTSTS, ~0); while (1) { ret = mmc0_send_cmd(13, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed on command 13\n"); return; } if (((buf[0] >> 9) & 0xf) == 4) return; } } static int mmc0_update_ext_csd(int index, int value) { unsigned int arg, data, buf[4]; int ret; arg = 3 << 24; arg |= (index & 0xff) << 16; arg |= (value & 0xff) << 8; arg |= 1; memset(buf, 0, 4 * sizeof(buf[0])); ret = mmc0_send_cmd(6, arg, buf); if (ret) { NOTICE("failed to send command 6\n"); return ret; } /* wait busy de-assert */ while (1) { data = mmio_read_32(MMC0_STATUS); if (!(data & MMC_STS_DATA_BUSY)) break; } do { ret = mmc0_send_cmd(13, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to send command 13\n"); return ret; } if (buf[0] & MMC_STATUS_SWITCH_ERROR) { NOTICE("maybe switch mmc mode error\n"); return -1; } } while (mmc_state(buf[0]) == MMC_STATE_PRG); return 0; } #define EXTCSD_BUS_WIDTH 183 static int mmc0_set_clock_and_width(int rate, int width) { int ret; switch (width) { case 0: mmio_write_32(MMC0_CTYPE, 0); ret = mmc0_update_ext_csd(EXTCSD_BUS_WIDTH, 0); break; case 8: mmio_write_32(MMC0_CTYPE, 1 << 16); ret = mmc0_update_ext_csd(EXTCSD_BUS_WIDTH, 2 + 4); mmio_write_32(MMC0_UHSREG, 1 << 16); break; default: NOTICE("wrong bus width:%d\n", width); return -EINVAL; } if (ret) { NOTICE("return failure on %s, %d\n", __func__, __LINE__); return ret; } set_mmc0_clock(rate); return 0; } static int manu_id; #define EXTCSD_HS_TIMING 185 #ifdef EMMC_READ_EXT_CSD static int mmc0_read_ext_csd(unsigned int dst_start); #endif static int enum_mmc0_card(void) { unsigned int buf[4], cid[4]; int ret = 0, i, version; /* CMD0: reset to IDLE */ ret = mmc0_send_cmd(0, 0, buf); if (ret) { NOTICE("failed to send IDLE command\n"); return ret; } while (1) { udelay(100); /* CMD1: READY */ ret = mmc0_send_cmd(1, 0x40ff8000, buf); if (ret) { NOTICE("failed to send READY command\n"); return ret; } if (buf[0] & 0x80000000) break; } /* CMD2: IDENT */ ret = mmc0_send_cmd(2, 0, buf); if (ret) { NOTICE("failed to send IDENT command\n"); return ret; } VERBOSE("manuid:"); for (i = 0; i < 4; i++) { cid[i] = buf[i]; VERBOSE(" 0x%x", cid[i]); } VERBOSE("\n"); /* CMD3: STBY */ ret = mmc0_send_cmd(3, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to send STBY command\n"); return ret; } /* CMD9: get CSD */ ret = mmc0_send_cmd(9, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to get CSD\n"); return ret; } VERBOSE("CSD: %x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]); version = (buf[3] >> 26) & 0xf; switch (version) { case 0: /* MMC v1.0-v1.2 */ case 1: /* MMC v1.4 */ manu_id = (cid[3] >> 8) & 0xffffff; break; case 2: /* MMC v2.0-v2.2 */ case 3: /* MMC v3.1-v3.3 */ case 4: /* MMC v4 */ manu_id = (cid[3] >> 24) & 0xff; break; default: WARN("wrong mmc version (%d) is specified.\n", version); break; } VERBOSE("mmc version:%d\n", version); /* CMD7: TRAN */ ret = mmc0_send_cmd(7, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to send TRAN command\n"); return ret; } mmc0_check_tran_mode(); mmc0_set_clock_and_width(400000, 0); #ifdef EMMC_READ_EXT_CSD mmc0_read_ext_csd(0x50000); #endif ret = mmc0_update_ext_csd(EXTCSD_HS_TIMING, 1); if (ret) { NOTICE("alter HS mode fail\n"); } ret = mmc0_set_clock_and_width(50000000, 8); return ret; } static int enable_mmc0(void) { unsigned int data; /* reset mmc0 */ data = MMC_CTRL_RESET | MMC_FIFO_RESET | MMC_DMA_RESET; mmio_write_32(MMC0_CTRL, data); /* wait until reset operation finished */ do { data = mmio_read_32(MMC0_CTRL); } while (data); data = MMC_INT_EN | MMC_DMA_EN; mmio_write_32(MMC0_CTRL, data); mmio_write_32(MMC0_INTMASK, 0x0); mmio_write_32(MMC0_RINTSTS, ~0); mmio_write_32(MMC0_IDINTEN, ~0); mmio_write_32(MMC0_IDSTS, ~0); mmio_write_32(MMC0_BLKSIZ, MMC_BLOCK_SIZE); mmio_write_32(MMC0_BMOD, MMC_IDMAC_SWRESET); do { data = mmio_read_32(MMC0_BMOD); } while (data & MMC_IDMAC_SWRESET); data |= MMC_IDMAC_ENABLE | MMC_IDMAC_FB; mmio_write_32(MMC0_BMOD, data); data = MMC_DMA_BURST_SIZE(2) | MMC_FIFO_TWMARK(8) | MMC_FIFO_RWMARK(7); mmio_write_32(MMC0_FIFOTH, data); data = MMC_CARD_RD_THR(512) | MMC_CARD_RD_THR_EN; mmio_write_32(MMC0_CARDTHRCTL, data); udelay(100); set_mmc0_clock(378000); udelay(100); set_mmc0_io(); return 0; } #define MMC_BLOCK_SIZE 512 #define MMC_DMA_MAX_BUFFER_SIZE (512 * 8) #ifdef EMMC_READ_EXT_CSD static int mmc0_read_ext_csd(unsigned int dst_start) { unsigned int blk_cnt, bytes, desc_num, buf[4], data; struct idmac_desc *desc = NULL; int i, ret, last_idx; uintptr_t src_addr, dst_addr = dst_start; blk_cnt = 1; bytes = blk_cnt * MMC_BLOCK_SIZE; memset((void *)MMC_DATA_BASE, 0, bytes); mmio_write_32(MMC0_BYTCNT, bytes); mmio_write_32(MMC0_RINTSTS, ~0); desc_num = (bytes + MMC_DMA_MAX_BUFFER_SIZE - 1) / MMC_DMA_MAX_BUFFER_SIZE; desc = (struct idmac_desc *)MMC_DESC_BASE; for (i = 0; i < desc_num; i++) { (desc + i)->des0 = IDMAC_DES0_OWN | IDMAC_DES0_CH | IDMAC_DES0_DIC; (desc + i)->des1 = IDMAC_DES1_BS1(MMC_DMA_MAX_BUFFER_SIZE); /* buffer address */ (desc + i)->des2 = MMC_DATA_BASE + MMC_DMA_MAX_BUFFER_SIZE * i; /* next descriptor address */ (desc + i)->des3 = MMC_DESC_BASE + (sizeof(struct idmac_desc) * (i + 1)); } /* first descriptor */ desc->des0 |= IDMAC_DES0_FS; /* last descriptor */ last_idx = desc_num - 1; (desc + last_idx)->des0 |= IDMAC_DES0_LD; (desc + last_idx)->des0 &= ~(IDMAC_DES0_DIC | IDMAC_DES0_CH); (desc + last_idx)->des1 = IDMAC_DES1_BS1(bytes - (last_idx * MMC_DMA_MAX_BUFFER_SIZE)); /* set next descriptor address as 0 */ (desc + last_idx)->des3 = 0; mmio_write_32(MMC0_DBADDR, MMC_DESC_BASE); /* read extended CSD */ ret = mmc0_send_cmd(8, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to send CMD8\n"); mmio_write_32(MMC0_RINTSTS, ~0); return -EFAULT; } ret = wait_data_ready(); if (ret) return ret; if (blk_cnt > 1) { ret = mmc0_send_cmd(12, EMMC_FIX_RCA << 16, buf); if (ret) { NOTICE("failed to send Stop Transmission command\n"); return ret; } mmio_write_32(MMC0_RINTSTS, ~0); } src_addr = MMC_DATA_BASE; memcpy((void *)dst_addr, (void *)src_addr, MMC_BLOCK_SIZE); return 0; } #endif int mmc0_read(unsigned long src_start, size_t src_size, unsigned long dst_start, uint32_t boot_partition) { unsigned int src_blk_start = src_start / MMC_BLOCK_SIZE; unsigned int src_blk_cnt, offset, bytes, desc_num, buf[4]; struct idmac_desc *desc = NULL; int i, ret, last_idx; uintptr_t src_addr, dst_addr = dst_start; if (boot_partition) { /* switch to boot partition 1 */ ret = mmc0_update_ext_csd(EXT_CSD_PARTITION_CONFIG, PART_CFG_BOOT_PARTITION1_ENABLE | PART_CFG_PARTITION1_ACCESS); if (ret) { NOTICE("fail to switch eMMC boot partition\n"); return ret; } } offset = src_start % MMC_BLOCK_SIZE; src_blk_cnt = (src_size + offset + MMC_BLOCK_SIZE - 1) / MMC_BLOCK_SIZE; bytes = src_blk_cnt * MMC_BLOCK_SIZE; mmio_write_32(MMC0_BYTCNT, bytes); mmio_write_32(MMC0_RINTSTS, ~0); desc_num = (bytes + MMC_DMA_MAX_BUFFER_SIZE - 1) / MMC_DMA_MAX_BUFFER_SIZE; desc = (struct idmac_desc *)MMC_DESC_BASE; for (i = 0; i < desc_num; i++) { (desc + i)->des0 = IDMAC_DES0_OWN | IDMAC_DES0_CH | IDMAC_DES0_DIC; (desc + i)->des1 = IDMAC_DES1_BS1(MMC_DMA_MAX_BUFFER_SIZE); /* buffer address */ (desc + i)->des2 = MMC_DATA_BASE + MMC_DMA_MAX_BUFFER_SIZE * i; /* next descriptor address */ (desc + i)->des3 = MMC_DESC_BASE + (sizeof(struct idmac_desc) * (i + 1)); } /* first descriptor */ desc->des0 |= IDMAC_DES0_FS; /* last descriptor */ last_idx = desc_num - 1; (desc + last_idx)->des0 |= IDMAC_DES0_LD; (desc + last_idx)->des0 &= ~(IDMAC_DES0_DIC | IDMAC_DES0_CH); (desc + last_idx)->des1 = IDMAC_DES1_BS1(bytes - (last_idx * MMC_DMA_MAX_BUFFER_SIZE)); /* set next descriptor address as 0 */ (desc + last_idx)->des3 = 0; mmio_write_32(MMC0_DBADDR, MMC_DESC_BASE); ret = mmc0_send_cmd(23, src_blk_cnt & 0xffff, buf); if (ret) { NOTICE("failed to send CMD23\n"); mmio_write_32(MMC0_RINTSTS, ~0); return -EFAULT; } /* multiple read */ ret = mmc0_send_cmd(18, src_blk_start, buf); if (ret) { NOTICE("failed to send CMD18\n"); mmio_write_32(MMC0_RINTSTS, ~0); return -EFAULT; } ret = wait_data_ready(); if (ret) return ret; src_addr = MMC_DATA_BASE + offset; memcpy((void *)dst_addr, (void *)src_addr, src_size); if (boot_partition) { /* switch back to normal partition */ ret = mmc0_update_ext_csd(EXT_CSD_PARTITION_CONFIG, PART_CFG_BOOT_PARTITION1_ENABLE); if (ret) NOTICE("fail to switch eMMC normal partition\n"); } return ret; } static int write_multi_blocks(unsigned int lba, unsigned int count, unsigned int buffer, unsigned int boot_partition) { unsigned int bytes, resp_buf[4], desc_num; struct idmac_desc *desc = NULL; int ret, last_idx, i; if (buffer % 4) { NOTICE("invalid buffer address:0x%x\n", buffer); return -EINVAL; } if (boot_partition) { /* switch to boot partition 1 */ ret = mmc0_update_ext_csd(EXT_CSD_PARTITION_CONFIG, PART_CFG_BOOT_PARTITION1_ENABLE | PART_CFG_PARTITION1_ACCESS); if (ret) { NOTICE("fail to switch eMMC boot partition\n"); return ret; } } bytes = MMC_BLOCK_SIZE * count; mmio_write_32(MMC0_BYTCNT, bytes); mmio_write_32(MMC0_RINTSTS, ~0); desc_num = (bytes + MMC_DMA_MAX_BUFFER_SIZE - 1) / MMC_DMA_MAX_BUFFER_SIZE; desc = (struct idmac_desc *)MMC_DESC_BASE; for (i = 0; i < desc_num; i++) { (desc + i)->des0 = IDMAC_DES0_OWN | IDMAC_DES0_CH | IDMAC_DES0_DIC; (desc + i)->des1 = IDMAC_DES1_BS1(MMC_DMA_MAX_BUFFER_SIZE); /* buffer address */ (desc + i)->des2 = buffer + MMC_DMA_MAX_BUFFER_SIZE * i; /* next descriptor address */ (desc + i)->des3 = MMC_DESC_BASE + (sizeof(struct idmac_desc) * (i + 1)); } /* first descriptor */ desc->des0 |= IDMAC_DES0_FS; /* last descriptor */ last_idx = desc_num - 1; (desc + last_idx)->des0 |= IDMAC_DES0_LD; (desc + last_idx)->des0 &= ~(IDMAC_DES0_DIC | IDMAC_DES0_CH); (desc + last_idx)->des1 = IDMAC_DES1_BS1(bytes - (last_idx * MMC_DMA_MAX_BUFFER_SIZE)); /* set next descriptor address as 0 */ (desc + last_idx)->des3 = 0; mmio_write_32(MMC0_DBADDR, MMC_DESC_BASE); ret = mmc0_send_cmd(25, lba, resp_buf); if (ret) { NOTICE("failed to send CMD25\n"); mmio_write_32(MMC0_RINTSTS, ~0); return -EFAULT; } ret = wait_data_ready(); if (ret) return ret; ret = mmc0_send_cmd(12, EMMC_FIX_RCA << 16, resp_buf); if (ret) { NOTICE("failed to send CMD12\n"); mmio_write_32(MMC0_RINTSTS, ~0); return -EFAULT; } do { ret = mmc0_send_cmd(13, EMMC_FIX_RCA << 16, resp_buf); if (ret) { NOTICE("failed to send command 13\n"); return ret; } } while (!(resp_buf[0] & MMC_STATUS_READY_FOR_DATA) || (mmc_state(resp_buf[0] != MMC_STATE_TRAN))); if (boot_partition) { /* switch back to normal partition */ ret = mmc0_update_ext_csd(EXT_CSD_PARTITION_CONFIG, PART_CFG_BOOT_PARTITION1_ENABLE); if (ret) NOTICE("fail to switch eMMC normal partition\n"); } return ret; } int mmc0_write(unsigned long mmc_start, size_t size, unsigned long buffer, uint32_t boot_partition) { unsigned int mmc_blk_start = mmc_start / MMC_BLOCK_SIZE; unsigned int mmc_blk_cnt, offset; offset = mmc_start % MMC_BLOCK_SIZE; mmc_blk_cnt = (size + offset + MMC_BLOCK_SIZE - 1) / MMC_BLOCK_SIZE; return write_multi_blocks(mmc_blk_start, mmc_blk_cnt, buffer, boot_partition); } int init_mmc(void) { int ret; enable_mmc0(); ret = enum_mmc0_card(); if (ret) return ret; /* set boot mode to 8-bit */ mmc0_update_ext_csd(177, 2); /* response to RESET signal */ mmc0_update_ext_csd(162, 1); /* set access userdata area */ mmc0_update_ext_csd(EXT_CSD_PARTITION_CONFIG, PART_CFG_BOOT_PARTITION1_ENABLE); mmio_write_32(MMC0_RINTSTS, ~0); return 0; }