| /* |
| * (C) Copyright 2003 |
| * Kyle Harris, kharris@nexus-tech.net |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <common.h> |
| #include <malloc.h> |
| #include <command.h> |
| #include <linux/ctype.h> |
| #include <mmc.h> |
| #include <partition_table.h> |
| #include <emmc_partitions.h> |
| #include <asm/arch/cpu_sdio.h> |
| #include <asm/arch/sd_emmc.h> |
| #include <linux/sizes.h> |
| #include <asm/cpu_id.h> |
| #include <amlogic/aml_mmc.h> |
| #include <errno.h> |
| |
| #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) |
| #define SD_EMMC_VDDEE_REG (*((volatile unsigned *)(0xff807000 + (0x01 << 2)))) |
| #ifdef MMC_HS200_MODE |
| //#define MMC_NO_BOOT_PARTITION |
| /*tl1*/ |
| static int pwm_voltage_table_ee[][2] = { |
| { 0x1c0000, 681}, |
| { 0x1b0001, 691}, |
| { 0x1a0002, 701}, |
| { 0x190003, 711}, |
| { 0x180004, 721}, |
| { 0x170005, 731}, |
| { 0x160006, 741}, |
| { 0x150007, 751}, |
| { 0x140008, 761}, |
| { 0x130009, 772}, |
| { 0x12000a, 782}, |
| { 0x11000b, 792}, |
| { 0x10000c, 802}, |
| { 0x0f000d, 812}, |
| { 0x0e000e, 822}, |
| { 0x0d000f, 832}, |
| { 0x0c0010, 842}, |
| { 0x0b0011, 852}, |
| { 0x0a0012, 862}, |
| { 0x090013, 872}, |
| { 0x080014, 882}, |
| { 0x070015, 892}, |
| { 0x060016, 902}, |
| { 0x050017, 912}, |
| { 0x040018, 922}, |
| { 0x030019, 932}, |
| { 0x02001a, 942}, |
| { 0x01001b, 952}, |
| { 0x00001c, 962} |
| }; |
| |
| /*g12b*/ |
| //static int pwm_voltage_table_ee[][2] = { |
| // { 0x1c0000, 681}, |
| // { 0x1b0001, 691}, |
| // { 0x1a0002, 701}, |
| // { 0x190003, 711}, |
| // { 0x180004, 721}, |
| // { 0x170005, 731}, |
| // { 0x160006, 741}, |
| // { 0x150007, 751}, |
| // { 0x140008, 761}, |
| // { 0x130009, 772}, |
| // { 0x12000a, 782}, |
| // { 0x11000b, 792}, |
| // { 0x10000c, 802}, |
| // { 0x0f000d, 812}, |
| // { 0x0e000e, 822}, |
| // { 0x0d000f, 832}, |
| // { 0x0c0010, 842}, |
| // { 0x0b0011, 852}, |
| // { 0x0a0012, 862}, |
| // { 0x090013, 872}, |
| // { 0x080014, 882}, |
| // { 0x070015, 892}, |
| // { 0x060016, 902}, |
| // { 0x050017, 912}, |
| // { 0x040018, 922}, |
| // { 0x030019, 932}, |
| // { 0x02001a, 942}, |
| // { 0x01001b, 952}, |
| // { 0x00001c, 962} |
| //}; |
| |
| //static int pwm_voltage_table_ee[][2] = { |
| // { 0x1c0000, 681}, |
| // { 0x1b0001, 691}, |
| // { 0x1a0002, 701}, |
| // { 0x190003, 711}, |
| // { 0x180004, 721}, |
| // { 0x170005, 731}, |
| // { 0x160006, 741}, |
| // { 0x150007, 751}, |
| // { 0x140008, 761}, |
| // { 0x130009, 772}, |
| // { 0x12000a, 782}, |
| // { 0x11000b, 792}, |
| // { 0x10000c, 802}, |
| // { 0x0f000d, 812}, |
| // { 0x0e000e, 822}, |
| // { 0x0d000f, 832}, |
| // { 0x0c0010, 842}, |
| // { 0x0b0011, 852}, |
| // { 0x0a0012, 862}, |
| // { 0x090013, 872}, |
| // { 0x080014, 882}, |
| // { 0x070015, 892}, |
| // { 0x060016, 902}, |
| // { 0x050017, 912}, |
| // { 0x040018, 922}, |
| // { 0x030019, 932}, |
| // { 0x02001a, 942}, |
| // { 0x01001b, 952}, |
| // { 0x00001c, 962} |
| //}; |
| /*r311 |
| static int pwm_voltage_table_ee[][2] = { |
| { 0x1c0000, 810}, |
| { 0x1b0001, 820}, |
| { 0x1a0002, 830}, |
| { 0x190003, 840}, |
| { 0x180004, 850}, |
| { 0x170005, 860}, |
| { 0x160006, 870}, |
| { 0x150007, 880}, |
| { 0x140008, 890}, |
| { 0x130009, 900}, |
| { 0x12000a, 910}, |
| { 0x11000b, 920}, |
| { 0x10000c, 930}, |
| { 0x0f000d, 940}, |
| { 0x0e000e, 950}, |
| { 0x0d000f, 960}, |
| { 0x0c0010, 970}, |
| { 0x0b0011, 980}, |
| { 0x0a0012, 990}, |
| { 0x090013, 1000}, |
| { 0x080014, 1010}, |
| { 0x070015, 1020}, |
| { 0x060016, 1030}, |
| { 0x050017, 1040}, |
| { 0x040018, 1050}, |
| { 0x030019, 1060}, |
| { 0x02001a, 1070}, |
| { 0x01001b, 1080}, |
| { 0x00001c, 1090} |
| }; |
| */ |
| #endif |
| /* info system. */ |
| #define dtb_err(fmt, ...) printf( "%s()-%d: " fmt , \ |
| __func__, __LINE__, ##__VA_ARGS__) |
| |
| #define dtb_wrn(fmt, ...) printf( "%s()-%d: " fmt , \ |
| __func__, __LINE__, ##__VA_ARGS__) |
| |
| /* for detail debug info */ |
| #define dtb_info(fmt, ...) printf( "%s()-%d: " fmt , \ |
| __func__, __LINE__, ##__VA_ARGS__) |
| |
| #define fb_err(fmt, ...) printf("%s()-%d: " fmt , \ |
| __func__, __LINE__, ##__VA_ARGS__) |
| |
| extern unsigned get_part_tbl_from_ept(int num, char *name); |
| |
| struct aml_dtb_rsv { |
| u8 data[DTB_BLK_SIZE*DTB_BLK_CNT - 4*sizeof(u32)]; |
| u32 magic; |
| u32 version; |
| u32 timestamp; |
| u32 checksum; |
| }; |
| |
| struct aml_dtb_info { |
| u32 stamp[2]; |
| u8 valid[2]; |
| }; |
| |
| #define stamp_after(a,b) ((int)(b) - (int)(a) < 0) |
| /* glb dtb infos */ |
| static struct aml_dtb_info dtb_infos = {{0, 0}, {0, 0}}; |
| |
| #define CONFIG_SECURITYKEY |
| |
| #if !defined(CONFIG_SYS_MMC_BOOT_DEV) |
| #define CONFIG_SYS_MMC_BOOT_DEV (CONFIG_SYS_MMC_ENV_DEV) |
| #endif |
| |
| #define GXB_START_BLK 0 |
| #define GXL_START_BLK 1 |
| |
| /* max 2MB for emmc in blks */ |
| #define UBOOT_SIZE (0x1000) |
| |
| int info_disprotect = 0; |
| |
| bool emmckey_is_protected (struct mmc *mmc) |
| { |
| #ifdef CONFIG_STORE_COMPATIBLE |
| #ifdef CONFIG_SECURITYKEY |
| if (info_disprotect & DISPROTECT_KEY) { |
| printf("%s(): disprotect\n", __func__); |
| return 0; |
| }else{ |
| printf("%s(): protect\n", __func__); |
| return 1; |
| } |
| #else |
| return 0; |
| #endif |
| #else |
| #ifdef CONFIG_SECURITYKEY |
| //return mmc->key_protect; |
| return 0; /* fixme, */ |
| #else |
| return 0; |
| #endif |
| #endif |
| } |
| |
| unsigned emmc_cur_partition = 0; |
| |
| int mmc_read_status(struct mmc *mmc, int timeout) |
| { |
| struct mmc_cmd cmd; |
| int err, retries = 5; |
| int status; |
| |
| cmd.cmdidx = MMC_CMD_SEND_STATUS; |
| cmd.resp_type = MMC_RSP_R1; |
| if (!mmc_host_is_spi(mmc)) |
| cmd.cmdarg = mmc->rca << 16; |
| do { |
| err = mmc_send_cmd(mmc, &cmd, NULL); |
| if (!err) { |
| if ((cmd.response[0] & MMC_STATUS_RDY_FOR_DATA) && |
| (cmd.response[0] & MMC_STATUS_CURR_STATE) != |
| MMC_STATE_PRG) |
| break; |
| else if (cmd.response[0] & MMC_STATUS_MASK) { |
| printf("Status Error: 0x%08X\n", |
| cmd.response[0]); |
| return COMM_ERR; |
| } |
| } else if (--retries < 0) |
| return err; |
| |
| udelay(1000); |
| |
| } while (timeout--); |
| |
| status = (cmd.response[0] & MMC_STATUS_CURR_STATE) >> 9; |
| printf("CURR STATE:%d, status = 0x%x\n", status, cmd.response[0]); |
| |
| if (timeout <= 0) { |
| printf("read status Timeout waiting card ready\n"); |
| return TIMEOUT; |
| } |
| if (cmd.response[0] & MMC_STATUS_SWITCH_ERROR) { |
| printf("mmc status swwitch error status =0x%x\n", status); |
| return SWITCH_ERR; |
| } |
| return 0; |
| } |
| |
| static int get_off_size(struct mmc * mmc, char * name, uint64_t offset, uint64_t size, u64 * blk, u64 * cnt, u64 * sz_byte) |
| { |
| struct partitions *part_info = NULL; |
| uint64_t off = 0; |
| int blk_shift = 0; |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| // printf("blk_shift:%d , off:0x%llx , size:0x%llx.\n ",blk_shift,off,size ); |
| part_info = find_mmc_partition_by_name(name); |
| if (part_info == NULL) { |
| printf("get partition info failed !!\n"); |
| return -1; |
| } |
| off = part_info->offset + offset; |
| |
| // printf("part_info->offset:0x%llx , off:0x%llx , size:0x%llx.\n",part_info->offset ,off,size); |
| |
| *blk = off >> blk_shift ; |
| *cnt = size >> blk_shift ; |
| *sz_byte = size - ((*cnt) << blk_shift) ; |
| |
| // printf("get_partition_off_size : blk:0x%llx , cnt:0x%llx.\n",*blk,*cnt); |
| return 0; |
| } |
| |
| static int get_partition_size(unsigned char* name, uint64_t* addr) |
| { |
| struct partitions *part_info = NULL; |
| part_info = find_mmc_partition_by_name((char *)name); |
| if (part_info == NULL) { |
| printf("get partition info failed !!\n"); |
| return -1; |
| } |
| |
| *addr = part_info->size >> 9; // unit: 512 bytes |
| return 0; |
| } |
| |
| static inline int isstring(char *p) |
| { |
| char *endptr = p; |
| while (*endptr != '\0') { |
| if (!(((*endptr >= '0') && (*endptr <= '9')) |
| || ((*endptr >= 'a') && (*endptr <= 'f')) |
| || ((*endptr >= 'A') && (*endptr <= 'F')) |
| || (*endptr == 'x') || (*endptr == 'X'))) |
| return 1; |
| endptr++; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| erase bootloader on user/boot0/boot1 which indicate by map. |
| bit 0: user |
| bit 1: boot0 |
| bit 2: boot1 |
| */ |
| int amlmmc_erase_bootloader(int dev, int map) |
| { |
| int ret = 0, i, count = 3; |
| int blk_shift; |
| unsigned long n; |
| char *partname[3] = {"user", "boot0", "boot1"}; |
| cpu_id_t cpu_id = get_cpu_id(); |
| struct mmc *mmc = find_mmc_device(dev); |
| |
| /* do nothing */ |
| if (0 == map) |
| goto _out; |
| |
| if (!mmc) { |
| printf("%s() %d: not valid emmc %d\n", __func__, __LINE__, dev); |
| ret = -1; |
| goto _out; |
| } |
| /* make sure mmc is initilized! */ |
| ret = mmc_init(mmc); |
| if (ret) { |
| printf("%s() %d: emmc %d init %d\n", __func__, __LINE__, dev, ret); |
| ret = -2; |
| goto _out; |
| } |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| /* erase bootloader in user/boot0/boot1 */ |
| for (i = 0; i < count; i++) { |
| if (map & (0x1 << i)) { |
| if (!mmc_select_hwpart(dev, i)) { |
| lbaint_t start = 0, blkcnt; |
| |
| blkcnt = mmc->capacity >> blk_shift; |
| if (0 == i) { |
| struct partitions *part_info; |
| /* get info by partition */ |
| part_info = find_mmc_partition_by_name(MMC_BOOT_NAME); |
| if (part_info == NULL) { |
| printf("%s() %d: error!!\n", __func__, __LINE__); |
| /* fixme, do somthing! */ |
| continue; |
| } else { |
| start = part_info->offset>> blk_shift; |
| blkcnt = part_info->size>> blk_shift; |
| if (cpu_id.family_id >= MESON_CPU_MAJOR_ID_GXL) { |
| start = GXL_START_BLK; |
| blkcnt -= GXL_START_BLK; |
| } |
| } |
| } |
| /* some customer may use boot1 higher 2M as private data. */ |
| #ifdef CONFIG_EMMC_BOOT1_TOUCH_REGION |
| if (2 == i && CONFIG_EMMC_BOOT1_TOUCH_REGION <= mmc->capacity) { |
| blkcnt = CONFIG_EMMC_BOOT1_TOUCH_REGION >> blk_shift; |
| } |
| #endif/* CONFIG_EMMC_BOOT1_TOUCH_REGION */ |
| printf("Erasing blocks " LBAFU " to " LBAFU " @ %s\n", |
| start, blkcnt, partname[i]); |
| n = mmc->block_dev.block_erase(dev, start, blkcnt); |
| if (n != 0) { |
| printf("mmc erase %s failed\n", partname[i]); |
| ret = -3; |
| break; |
| } |
| } else |
| printf("%s() %d: switch dev %d to %s fail\n", |
| __func__, __LINE__, dev, partname[i]); |
| } |
| } |
| /* try to switch back to user. */ |
| mmc_select_hwpart(dev, 0); |
| |
| _out: |
| return ret; |
| } |
| |
| /* dtb read&write operation with backup updates */ |
| static u32 _calc_boot_info_checksum(struct storage_emmc_boot_info *boot_info) |
| { |
| u32 *buffer = (u32*)boot_info; |
| u32 checksum = 0; |
| int i = 0; |
| |
| do { |
| checksum += buffer[i]; |
| } while (i++ < ((EMMC_BOOT_INFO_SIZE >> 2) - 2)); |
| |
| return checksum; |
| } |
| |
| #define MAX_REACHABLE_RSV_RANGE 0x30000 |
| static int amlmmc_boot_info_check(struct mmc *mmc, int dev) |
| { |
| struct storage_emmc_boot_info *boot_info; |
| struct virtual_partition *ddr_part; |
| u64 src; |
| u32 *buffer, checksum = 0; |
| int i = 0, ret = -1; |
| |
| buffer = malloc(MMC_BLOCK_SIZE); |
| if (!buffer) |
| return -ENOMEM; |
| |
| if (mmc->block_dev.block_read(dev, 0, 1, buffer) != 1) { |
| ret = -EIO; |
| goto _err;
|
| } |
| boot_info = (struct storage_emmc_boot_info *)buffer; |
| ddr_part = aml_get_virtual_partition_by_name(MMC_DDR_PARAMETER_NAME); |
| if (boot_info->ddr.addr != ddr_part->offset / MMC_BLOCK_SIZE) |
| goto _err; |
| |
| if (!boot_info->checksum || boot_info->version != 0x01) |
| goto _err; |
| |
| src = boot_info->rsv_base_addr + boot_info->ddr.addr; |
| if (!src || src == (uint64_t)(-1) || src > MAX_REACHABLE_RSV_RANGE) |
| goto _err; |
| |
| do { |
| checksum += buffer[i]; |
| } while (i++ < ((EMMC_BOOT_INFO_SIZE >> 2) - 2)); |
| |
| if (!checksum) |
| goto _err; |
| |
| if (checksum == boot_info->checksum) |
| ret = 0; |
| _err: |
| free(buffer); |
| return ret; |
| } |
| |
| #define GET_RSERVED_BASE_ADDR() (_get_inherent_offset(MMC_RESERVED_NAME)) |
| static int amlmmc_write_info_sector(struct mmc *mmc, int dev) |
| { |
| struct storage_emmc_boot_info *boot_info; |
| struct virtual_partition *ddr_part; |
| u8 *buffer; |
| int ret = 0; |
| |
| buffer = malloc(MMC_BLOCK_SIZE); |
| if (!buffer) |
| return -ENOMEM; |
| |
| memset(buffer, 0, sizeof(*boot_info)); |
| boot_info = (struct storage_emmc_boot_info *)buffer; |
| boot_info->rsv_base_addr = GET_RSERVED_BASE_ADDR() / MMC_BLOCK_SIZE; |
| ddr_part = aml_get_virtual_partition_by_name(MMC_DDR_PARAMETER_NAME); |
| boot_info->ddr.addr = ddr_part->offset / MMC_BLOCK_SIZE; |
| boot_info->ddr.size = ddr_part->size / MMC_BLOCK_SIZE; |
| boot_info->version = 1; |
| boot_info->checksum = _calc_boot_info_checksum(boot_info); |
| |
| printf("boot_info.rsv_base_addr\t:\t%04x\n", boot_info->rsv_base_addr); |
| printf("boot_info.ddr.addr\t:\t%04x\n", boot_info->ddr.addr); |
| printf("boot_info.ddr.size\t:\t%04x\n", boot_info->ddr.size); |
| printf("boot_info.version\t:\t%04x\n", boot_info->version); |
| printf("boot_info.checksum\t:\t%04x\n", boot_info->checksum); |
| |
| if (mmc->block_dev.block_write(dev, 0, 1, buffer) != 1) |
| ret = -EIO; |
| |
| free(buffer); |
| return ret; |
| } |
| |
| int amlmmc_check_and_update_boot_info(void) |
| { |
| struct mmc *mmc; |
| int ret = 0, i; |
| |
| mmc = find_mmc_device(CONFIG_SYS_MMC_BOOT_DEV); |
| if (!mmc) |
| return -ENODEV; |
| ret = mmc_init(mmc); |
| if (ret) |
| return -ENODEV; |
| |
| for (i = 1; i < 3; i++) { |
| if (mmc_select_hwpart(CONFIG_SYS_MMC_BOOT_DEV, i)) { |
| printf("switch dev %d to boot%d fail\n", |
| CONFIG_SYS_MMC_BOOT_DEV, i - 1); |
| continue; |
| } |
| if (!amlmmc_boot_info_check(mmc, CONFIG_SYS_MMC_BOOT_DEV)) |
| break; |
| ret = amlmmc_write_info_sector(mmc, CONFIG_SYS_MMC_BOOT_DEV); |
| if (ret) |
| return -EIO; |
| } |
| mmc_select_hwpart(CONFIG_SYS_MMC_BOOT_DEV, 0); |
| return ret; |
| } |
| |
| /* |
| write bootloader on user/boot0/boot1 which indicate by map. |
| bit 0: user |
| bit 1: boot0 |
| bit 2: boot1 |
| */ |
| int amlmmc_write_bootloader(int dev, int map, unsigned int size, const void *src) |
| { |
| int ret = 0, i, count = 3; |
| unsigned long n; |
| char *partname[3] = {"user", "boot0", "boot1"}; |
| struct mmc *mmc = find_mmc_device(dev); |
| lbaint_t start = GXB_START_BLK, blkcnt; |
| cpu_id_t cpu_id = get_cpu_id(); |
| |
| /* do nothing */ |
| if (0 == map) |
| goto _out; |
| |
| if (!mmc) { |
| printf("%s() %d: not valid emmc %d\n", __func__, __LINE__, dev); |
| ret = -1; |
| goto _out; |
| } |
| /* make sure mmc is initilized! */ |
| ret = mmc_init(mmc); |
| if (ret) { |
| printf("%s() %d: emmc %d init %d\n", __func__, __LINE__, dev, ret); |
| ret = -2; |
| goto _out; |
| } |
| printf("%s() %d: map 0x%x\n", __func__, __LINE__, map); |
| if (cpu_id.family_id >= MESON_CPU_MAJOR_ID_GXL) |
| start = GXL_START_BLK; |
| blkcnt = (size + mmc->read_bl_len - 1) / mmc->read_bl_len; |
| |
| /* erase bootloader in user/boot0/boot1 */ |
| for (i = 0; i < count; i++) { |
| if (map & (0x1 << i)) { |
| if (!mmc_select_hwpart(dev, i)) { |
| /* some customer may use boot1 higher 2M as private data. */ |
| #ifdef CONFIG_EMMC_BOOT1_TOUCH_REGION |
| if (2 == i && CONFIG_EMMC_BOOT1_TOUCH_REGION <= size) { |
| printf("%s(), size %d exceeds TOUCH_REGION %d, skip\n", |
| __func__, size, CONFIG_EMMC_BOOT1_TOUCH_REGION); |
| break; |
| } |
| #endif /* CONFIG_EMMC_BOOT1_TOUCH_REGION */ |
| printf("Wrting blocks " LBAFU " to " LBAFU " @ %s\n", |
| start, blkcnt, partname[i]); |
| if (i != 0) |
| amlmmc_write_info_sector(mmc, dev); |
| n = mmc->block_dev.block_write(dev, start, blkcnt, src); |
| if (n != blkcnt) { |
| printf("mmc write %s failed\n", partname[i]); |
| ret = -3; |
| break; |
| } |
| } else |
| printf("%s() %d: switch dev %d to %s fail\n", |
| __func__, __LINE__, dev, partname[i]); |
| } |
| } |
| /* try to switch back to user. */ |
| mmc_select_hwpart(dev, 0); |
| |
| _out: |
| return ret; |
| } |
| |
| static int amlmmc_erase_in_dev(int argc, char *const argv[]) |
| { |
| int dev = 0; |
| u64 cnt = 0, blk = 0, n = 0; |
| struct mmc *mmc; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| blk = simple_strtoull(argv[3], NULL, 16); |
| cnt = simple_strtoull(argv[4], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) |
| return 1; |
| |
| printf("MMC erase: dev # %d, start_erase_address(in block) # %#llx,\ |
| several blocks # %lld will be erased ...\n ", |
| dev, blk, cnt); |
| |
| mmc_init(mmc); |
| |
| if (cnt != 0) |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, " ", (n == 0) ? "OK" : "ERROR"); |
| |
| return (n == 0) ? 0 : 1; |
| } |
| |
| static int amlmmc_erase_in_card(int argc, char *const argv[]) |
| { |
| int dev = 0; |
| u64 cnt = 0, blk = 0, n = 0; |
| /*sz_byte =0;*/ |
| char *name = NULL; |
| u64 offset_addr = 0, size = 0; |
| struct mmc *mmc; |
| int tmp_shift; |
| |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name (name); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| offset_addr = simple_strtoull(argv[3], NULL, 16); |
| size = simple_strtoull(argv[4], NULL, 16); |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| tmp_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| cnt = size >> tmp_shift; |
| blk = offset_addr >> tmp_shift; |
| /* sz_byte = size - (cnt<<tmp_shift); */ |
| |
| printf("MMC erase: dev # %d, start_erase_address(in block) # %#llx,\ |
| several blocks # %lld will be erased ...\n ", |
| dev, blk, cnt); |
| |
| mmc_init(mmc); |
| |
| if (cnt != 0) |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, argv[2], (n == 0) ? "OK" : "ERROR"); |
| |
| return (n == 0) ? 0 : 1; |
| } |
| |
| static int amlmmc_erase_in_part(int argc, char *const argv[]) |
| { |
| int dev = 0; |
| u64 cnt = 0, blk = 0, n = 0, sz_byte =0; |
| char *name = NULL; |
| u64 offset_addr = 0, size = 0; |
| struct mmc *mmc; |
| struct partitions *part_info; |
| |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name (name); |
| offset_addr = simple_strtoull(argv[3], NULL, 16); |
| size = simple_strtoull(argv[4], NULL, 16); |
| part_info = find_mmc_partition_by_name(name); |
| if (!part_info) |
| return -ENODEV; |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return -ENODEV; |
| |
| if (offset_addr >= part_info->size) { |
| printf("Start address out #%s# partition'address region,(addr_byte < 0x%llx)\n", |
| name, part_info->size); |
| return 1; |
| } |
| if ((offset_addr+size) > part_info->size) { |
| printf("End address exceeds #%s# partition,(offset = 0x%llx,size = 0x%llx)\n", |
| name, part_info->offset,part_info->size); |
| return 1; |
| } |
| get_off_size(mmc, name, offset_addr, size, &blk, &cnt, &sz_byte); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| printf("MMC erase: dev # %d, start_erase_address(in block) # %#llx,\ |
| several blocks # %lld will be erased ...\n ", |
| dev, blk, cnt); |
| |
| mmc_init(mmc); |
| |
| if (cnt != 0) |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, argv[2], (n == 0) ? "OK" : "ERROR"); |
| |
| return (n == 0) ? 0 : 1; |
| } |
| |
| static int amlmmc_erase_by_add(int argc, char *const argv[]) |
| { |
| int ret = 0; |
| |
| if (argc != 5) |
| return CMD_RET_USAGE; |
| |
| if (isdigit(argv[2][0])) |
| ret = amlmmc_erase_in_dev(argc, argv); |
| else if (strcmp(argv[2], "card") == 0) |
| ret = amlmmc_erase_in_card(argc, argv); |
| else if (isstring(argv[2])) |
| ret = amlmmc_erase_in_part(argc, argv); |
| |
| return ret; |
| } |
| |
| static int _amlmmc_erase_single_part(int dev, struct mmc *mmc, char *name) |
| { |
| u32 n = 0; |
| int blk_shift; |
| u64 cnt = 0, blk = 0; |
| struct partitions *part_info; |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| part_info = find_mmc_partition_by_name(name); |
| if (part_info == NULL) |
| return 1; |
| |
| blk = part_info->offset >> blk_shift; |
| if (emmc_cur_partition && !strncmp(name, "bootloader", strlen("bootloader"))) |
| cnt = mmc->boot_size >> blk_shift; |
| else |
| cnt = part_info->size >> blk_shift; |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| |
| return (n == 0) ? 0 : 1; |
| } |
| |
| static int _amlmmc_erase_partition(int dev, struct mmc *mmc, char *exclude_name) |
| { |
| int num = 0; |
| unsigned protect_mask = MMC_PARTITION_PROTECT_MASK; |
| unsigned protect_key; |
| u32 n = 0; |
| int ret = 0; |
| unsigned mask_flags; |
| char name[MAX_PART_NAME_LEN]; |
| protect_key = emmckey_is_protected(mmc); |
| |
| if (!protect_key && !strcmp(exclude_name, "none")) { |
| n = mmc->block_dev.block_erase(dev, 0, 0); |
| return (n == 0) ? 0 : 1; |
| } |
| |
| while (get_part_tbl_from_ept(num, name) != -1) { |
| mask_flags = get_part_tbl_from_ept(num, name); |
| if ((mask_flags & protect_mask) && protect_key) |
| printf("%-10s partition is protected\n", name); |
| else if (!strcmp(name, exclude_name)) |
| printf("%-10s partition is keeped\n", name); |
| else if (protect_key && !strcmp(name, MMC_RESERVED_NAME)) |
| printf("%-10s partition is protected\n", name); |
| else { |
| printf("%-10s partition is erased: ", name); |
| ret |= _amlmmc_erase_single_part(dev, mmc, name); |
| } |
| num++; |
| } |
| |
| return ret; |
| } |
| |
| static int amlmmc_erase_non_loader(int dev, struct mmc *mmc) |
| { |
| return _amlmmc_erase_partition(dev, mmc, MMC_BOOT_NAME); |
| } |
| |
| static int amlmmc_erase_single_part(int dev, struct mmc *mmc, char *name) |
| { |
| return _amlmmc_erase_single_part(dev, mmc, name); |
| } |
| |
| static int amlmmc_erase_whole(int dev, struct mmc *mmc) |
| { |
| int ret = 0; |
| |
| ret = _amlmmc_erase_partition(dev, mmc, "none"); |
| |
| if (ret == 0) |
| ret = amlmmc_erase_bootloader(dev, AML_BL_BOOT); |
| if (ret) |
| printf("erase bootloader in boot partition failed\n"); |
| |
| return (ret == 0) ? 0 : 1; |
| } |
| |
| static int amlmmc_erase_non_cache(int dev, struct mmc *mmc) |
| { |
| u32 ret = 0; |
| |
| ret = _amlmmc_erase_partition(dev, mmc, MMC_CACHE_NAME); |
| |
| if (ret == 0) |
| ret = amlmmc_erase_bootloader(dev, AML_BL_BOOT); |
| if (ret) |
| printf("erase bootloader in boot partition failed\n"); |
| |
| return (ret == 0) ? 0 : 1; |
| } |
| |
| static int amlmmc_erase_dev(int dev, struct mmc *mmc) |
| { |
| return amlmmc_erase_whole(dev, mmc); |
| } |
| |
| static int amlmmc_erase_allbootloader(int dev, struct mmc *mmc) |
| { |
| return amlmmc_erase_bootloader(dev, AML_BL_ALL); |
| } |
| |
| static int amlmmc_erase_by_part(int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| char *name = "logo"; |
| struct mmc *mmc; |
| int dev; |
| |
| if (argc != 3) |
| return ret; |
| |
| dev = find_dev_num_by_partition_name(name); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| mmc_init(mmc); |
| |
| if (isdigit(argv[2][0])) |
| ret = amlmmc_erase_dev(dev, mmc); |
| else if (strcmp(argv[2], "whole") == 0) |
| ret = amlmmc_erase_whole(dev, mmc); |
| else if (strcmp(argv[2], "non_cache") == 0) |
| ret = amlmmc_erase_non_cache(dev, mmc); |
| else if (strcmp(argv[2], "non_loader") == 0) |
| ret = amlmmc_erase_non_loader(dev, mmc); |
| else if (strcmp(argv[2], "allbootloader") == 0) |
| ret = amlmmc_erase_allbootloader(dev, mmc); |
| else |
| ret = amlmmc_erase_single_part(dev, mmc, argv[2]); |
| |
| return ret; |
| } |
| |
| static int do_amlmmc_erase(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| |
| if (argc == 3) |
| ret = amlmmc_erase_by_part(argc, argv); |
| else if (argc == 5) |
| ret = amlmmc_erase_by_add(argc, argv); |
| |
| return ret; |
| } |
| |
| static int amlmmc_write_in_part(int argc, char *const argv[]) |
| { |
| int dev, ret; |
| void *addr = NULL; |
| u64 cnt = 0, n = 0, blk = 0, sz_byte = 0; |
| char *name = NULL; |
| u64 offset = 0, size = 0; |
| struct mmc *mmc; |
| #ifdef MMC_BOOT_PARTITION_SUPPORT |
| int map = AML_BL_ALL; |
| #else |
| int map = AML_BL_USER; |
| #endif |
| |
| name = argv[2]; |
| if (strcmp(name, "bootloader") == 0) |
| dev = CONFIG_SYS_MMC_BOOT_DEV; |
| else |
| dev = find_dev_num_by_partition_name (name); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| offset = simple_strtoull(argv[4], NULL, 16); |
| size = simple_strtoull(argv[5], NULL, 16); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| mmc_init(mmc); |
| |
| if (strcmp(name, "bootloader") == 0) { |
| #ifdef CONFIG_EMMC_KEEP_BOOT1 |
| if (get_cpu_id().family_id != MESON_CPU_MAJOR_ID_TM2) { |
| printf("WRONG CONFIG_EMMC_KEEP_BOOT1 enabled!\n"); |
| return -1; |
| } |
| if (get_cpu_id().chip_rev == 0xA) |
| map &= ~AML_BL_BOOT1; |
| #endif |
| ret = amlmmc_write_bootloader(dev, map, size, addr); |
| return ret; |
| } else |
| get_off_size(mmc, name, offset, size, &blk, &cnt, &sz_byte); |
| |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| //write sz_byte bytes |
| if ((n == cnt) && (sz_byte != 0)) { |
| // printf("sz_byte=%#llx bytes\n",sz_byte); |
| void *addr_tmp = malloc(mmc->write_bl_len); |
| void *addr_byte = (void*)(addr+cnt*(mmc->write_bl_len)); |
| ulong start_blk = blk+cnt; |
| |
| if (addr_tmp == NULL) { |
| printf("mmc write: malloc fail\n"); |
| return 1; |
| } |
| |
| if (mmc->block_dev.block_read(dev, start_blk, 1, addr_tmp) != 1) { // read 1 block |
| free(addr_tmp); |
| printf("mmc read 1 block fail\n"); |
| return 1; |
| } |
| |
| memcpy(addr_tmp, addr_byte, sz_byte); |
| if (mmc->block_dev.block_write(dev, start_blk, 1, addr_tmp) != 1) { // write 1 block |
| free(addr_tmp); |
| printf("mmc write 1 block fail\n"); |
| return 1; |
| } |
| free(addr_tmp); |
| } |
| //printf("%#llx blocks , %#llx bytes written: %s\n", n, sz_byte, (n==cnt) ? "OK" : "ERROR"); |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int amlmmc_write_in_card(int argc, char *const argv[]) |
| { |
| int dev; |
| void *addr = NULL; |
| u64 cnt = 0, n = 0, blk = 0, sz_byte = 0; |
| char *name = NULL; |
| u64 offset = 0, size = 0; |
| struct mmc *mmc; |
| int blk_shift; |
| |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name (name); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| offset = simple_strtoull(argv[4], NULL, 16); |
| size = simple_strtoull(argv[5], NULL, 16); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| cnt = size >> blk_shift; |
| blk = offset >> blk_shift; |
| sz_byte = size - (cnt<<blk_shift); |
| mmc_init(mmc); |
| |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| |
| //write sz_byte bytes |
| if ((n == cnt) && (sz_byte != 0)) { |
| // printf("sz_byte=%#llx bytes\n",sz_byte); |
| void *addr_tmp = malloc(mmc->write_bl_len); |
| void *addr_byte = (void*)(addr+cnt*(mmc->write_bl_len)); |
| ulong start_blk = blk+cnt; |
| |
| if (addr_tmp == NULL) { |
| printf("mmc write: malloc fail\n"); |
| return 1; |
| } |
| |
| if (mmc->block_dev.block_read(dev, start_blk, 1, addr_tmp) != 1) { // read 1 block |
| free(addr_tmp); |
| printf("mmc read 1 block fail\n"); |
| return 1; |
| } |
| |
| memcpy(addr_tmp, addr_byte, sz_byte); |
| if (mmc->block_dev.block_write(dev, start_blk, 1, addr_tmp) != 1) { // write 1 block |
| free(addr_tmp); |
| printf("mmc write 1 block fail\n"); |
| return 1; |
| } |
| free(addr_tmp); |
| } |
| //printf("%#llx blocks , %#llx bytes written: %s\n", n, sz_byte, (n==cnt) ? "OK" : "ERROR"); |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int amlmmc_write_in_dev(int argc, char *const argv[]) |
| { |
| int dev; |
| void *addr = NULL; |
| u64 cnt = 0, n = 0, blk = 0; |
| struct mmc *mmc; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| blk = simple_strtoull(argv[4], NULL, 16); |
| cnt = simple_strtoull(argv[5], NULL, 16); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| //printf("MMC write: dev # %d, block # %#llx, count # %#llx ... ", |
| //dev, blk, cnt); |
| |
| mmc_init(mmc); |
| |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int do_amlmmc_write(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = 0; |
| if (argc != 6) |
| return CMD_RET_USAGE; |
| |
| if (isdigit(argv[2][0])) |
| ret = amlmmc_write_in_dev(argc, argv); |
| else if (strcmp(argv[2], "card") == 0) |
| ret = amlmmc_write_in_card(argc, argv); |
| else if (isstring(argv[2])) |
| ret = amlmmc_write_in_part(argc, argv); |
| |
| return ret; |
| } |
| |
| static int amlmmc_read_in_dev(int argc, char *const argv[]) |
| { |
| int dev; |
| void *addr = NULL; |
| u64 cnt =0, n = 0, blk = 0; |
| struct mmc *mmc; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| blk = simple_strtoull(argv[4], NULL, 16); |
| cnt = simple_strtoull(argv[5], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| n = mmc->block_dev.block_read(dev, blk, cnt, addr); |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int amlmmc_read_in_card(int argc, char *const argv[]) |
| { |
| int dev; |
| void *addr = NULL; |
| //u32 flag =0; |
| u64 cnt =0, n = 0, blk = 0, sz_byte = 0; |
| char *name = NULL; |
| u64 offset = 0, size = 0; |
| int blk_shift; |
| struct mmc *mmc; |
| void *addr_tmp; |
| void *addr_byte; |
| ulong start_blk; |
| |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name (name); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| size = simple_strtoull(argv[5], NULL, 16); |
| offset = simple_strtoull(argv[4], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| cnt = size >> blk_shift; |
| blk = offset >> blk_shift; |
| sz_byte = size - (cnt<<blk_shift); |
| |
| mmc_init(mmc); |
| n = mmc->block_dev.block_read(dev, blk, cnt, addr); |
| |
| //read sz_byte bytes |
| if ((n == cnt) && (sz_byte != 0)) { |
| addr_tmp = malloc(mmc->read_bl_len); |
| addr_byte = (void *)(addr+cnt*(mmc->read_bl_len)); |
| start_blk = blk+cnt; |
| if (addr_tmp == NULL) { |
| printf("mmc read: malloc fail\n"); |
| return 1; |
| } |
| if (mmc->block_dev.block_read(dev, start_blk, 1, addr_tmp) != 1) { // read 1 block |
| free(addr_tmp); |
| printf("mmc read 1 block fail\n"); |
| return 1; |
| } |
| memcpy(addr_byte, addr_tmp, sz_byte); |
| free(addr_tmp); |
| } |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int amlmmc_read_in_part(int argc, char *const argv[]) |
| { |
| int dev; |
| void *addr = NULL; |
| u64 cnt = 0, n = 0, blk = 0, sz_byte = 0; |
| char *name = NULL; |
| u64 offset = 0, size = 0; |
| struct mmc *mmc; |
| void *addr_tmp; |
| void *addr_byte; |
| ulong start_blk; |
| |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name (name); |
| addr = (void *)simple_strtoul(argv[3], NULL, 16); |
| offset = simple_strtoull(argv[4], NULL, 16); |
| size = simple_strtoull(argv[5], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| get_off_size(mmc, name, offset, size, &blk, &cnt, &sz_byte); |
| mmc_init(mmc); |
| n = mmc->block_dev.block_read(dev, blk, cnt, addr); |
| |
| //read sz_byte bytes |
| if ((n == cnt) && (sz_byte != 0)) { |
| /*printf("sz_byte=%#llx bytes\n",sz_byte);*/ |
| addr_tmp = malloc(mmc->read_bl_len); |
| addr_byte = (void *)(addr+cnt*(mmc->read_bl_len)); |
| start_blk = blk+cnt; |
| |
| if (addr_tmp == NULL) { |
| printf("mmc read: malloc fail\n"); |
| return 1; |
| } |
| |
| if (mmc->block_dev.block_read(dev, start_blk, 1, addr_tmp) != 1) { // read 1 block |
| free(addr_tmp); |
| printf("mmc read 1 block fail\n"); |
| return 1; |
| } |
| |
| memcpy(addr_byte, addr_tmp, sz_byte); |
| free(addr_tmp); |
| } |
| return (n == cnt) ? 0 : 1; |
| } |
| |
| static int do_amlmmc_read(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = 0; |
| |
| if (argc != 6) |
| return CMD_RET_USAGE; |
| |
| if (isdigit(argv[2][0])) |
| ret = amlmmc_read_in_dev(argc, argv); |
| else if (strcmp(argv[2], "card") == 0) |
| ret = amlmmc_read_in_card(argc, argv); |
| else if (isstring(argv[2])) |
| ret = amlmmc_read_in_part(argc, argv); |
| |
| return ret; |
| } |
| |
| static int do_amlmmc_env(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| printf("herh\n"); |
| env_relocate(); |
| return 0; |
| } |
| |
| static int do_amlmmc_list(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| print_mmc_devices('\n'); |
| return 0; |
| } |
| |
| static int do_amlmmc_size(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| char *name; |
| uint64_t* addr = NULL; |
| int dev; |
| struct mmc *mmc = NULL; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| name = argv[2]; |
| addr = (uint64_t *)simple_strtoul(argv[3], NULL, 16); |
| if (!strcmp(name, "wholeDev")) { |
| dev = CONFIG_SYS_MMC_BOOT_DEV; |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| |
| *addr = mmc->capacity >> 9; // unit: 512 bytes |
| return 0; |
| } |
| return get_partition_size((unsigned char *)name, addr); |
| } |
| |
| static int amlmmc_get_ext_csd(int argc, char *const argv[]) |
| { |
| int ret= 0; |
| u8 ext_csd[512] = {0}; |
| int dev, byte; |
| struct mmc *mmc; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| byte = simple_strtoul(argv[3], NULL, 10); |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| printf("read EXT_CSD byte[%d] val[0x%x] %s\n", |
| byte, ext_csd[byte], (ret == 0) ? "ok" : "fail"); |
| return ret; |
| } |
| |
| static int amlmmc_set_ext_csd(int argc, char *const argv[]) |
| { |
| int ret = 0; |
| int dev, byte; |
| struct mmc *mmc; |
| int val; |
| |
| if (argc != 5) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| byte = simple_strtoul(argv[3], NULL, 10); |
| val = simple_strtoul(argv[4], NULL, 16); |
| if ((byte > 191) || (byte < 0)) { |
| printf("byte is not able to write!\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| |
| ret = mmc_set_ext_csd(mmc, byte, val); |
| printf("write EXT_CSD byte[%d] val[0x%x] %s\n", |
| byte, val, (ret == 0) ? "ok" : "fail"); |
| return ret; |
| } |
| |
| static int do_amlmmc_ext_csd(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| |
| if (argc == 4) |
| ret = amlmmc_get_ext_csd(argc,argv); |
| else if (argc == 5) |
| ret = amlmmc_set_ext_csd(argc,argv); |
| |
| return ret; |
| } |
| |
| static int do_amlmmc_switch(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int rc = 0; |
| int dev; |
| struct mmc *mmc; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| printf("mmc switch to "); |
| |
| if (strcmp(argv[3], "boot0") == 0) { |
| rc = mmc_switch_part(dev, 1); |
| if (rc == 0) { |
| emmc_cur_partition = 1; |
| printf("boot0 success\n"); |
| } else { |
| printf("boot0 failed\n"); |
| } |
| } |
| else if(strcmp(argv[3], "boot1") == 0) { |
| rc = mmc_switch_part(dev, 2); |
| if (rc == 0) { |
| emmc_cur_partition = 2; |
| printf("boot1 success\n"); |
| } else { |
| printf("boot1 failed\n"); |
| } |
| } |
| else if(strcmp(argv[3], "user") == 0) { |
| rc = mmc_switch_part(dev, 0); |
| if (rc == 0) { |
| emmc_cur_partition = 0; |
| printf("user success\n"); |
| } else { |
| printf("user failed\n"); |
| } |
| } |
| #ifdef CONFIG_SUPPORT_EMMC_RPMB |
| else if(strcmp(argv[3], "rpmb") == 0) { |
| rc = mmc_switch_part(dev, 3); |
| if (rc == 0) { |
| emmc_cur_partition = 3; |
| printf("rpmb success\n"); |
| } else { |
| printf("rpmb failed\n"); |
| } |
| } |
| #endif |
| else |
| printf("%s failed\n", argv[3]); |
| return rc; |
| } |
| |
| static int do_amlmmc_controller(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int dev; |
| struct mmc *mmc; |
| struct aml_card_sd_info *aml_priv; |
| struct sd_emmc_global_regs *sd_emmc_reg; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) |
| return 1; |
| |
| aml_priv = mmc->priv; |
| sd_emmc_reg = aml_priv->sd_emmc_reg; |
| |
| printf("sd_emmc_reg->gclock = 0x%x\n", sd_emmc_reg->gclock); |
| printf("sd_emmc_reg->gdelay = 0x%x\n", sd_emmc_reg->gdelay); |
| printf("sd_emmc_reg->gadjust = 0x%x\n", sd_emmc_reg->gadjust); |
| printf("sd_emmc_reg->gcalout = 0x%x\n", sd_emmc_reg->gcalout); |
| if (!mmc->has_init) { |
| printf("mmc dev %d has not been initialed\n", dev); |
| return 1; |
| } |
| printf("sd_emmc_reg->gstart = 0x%x\n", sd_emmc_reg->gstart); |
| printf("sd_emmc_reg->gcfg = 0x%x\n", sd_emmc_reg->gcfg); |
| printf("sd_emmc_reg->gstatus = 0x%x\n", sd_emmc_reg->gstatus); |
| printf("sd_emmc_reg->girq_en = 0x%x\n", sd_emmc_reg->girq_en); |
| printf("sd_emmc_reg->gcmd_cfg = 0x%x\n", sd_emmc_reg->gcmd_cfg); |
| printf("sd_emmc_reg->gcmd_arg = 0x%x\n", sd_emmc_reg->gcmd_arg); |
| printf("sd_emmc_reg->gcmd_dat = 0x%x\n", sd_emmc_reg->gcmd_dat); |
| printf("sd_emmc_reg->gcmd_rsp0 = 0x%x\n", sd_emmc_reg->gcmd_rsp0); |
| printf("sd_emmc_reg->gcmd_rsp1 = 0x%x\n", sd_emmc_reg->gcmd_rsp1); |
| printf("sd_emmc_reg->gcmd_rsp2 = 0x%x\n", sd_emmc_reg->gcmd_rsp2); |
| printf("sd_emmc_reg->gcmd_rsp3 = 0x%x\n", sd_emmc_reg->gcmd_rsp3); |
| return 0; |
| } |
| |
| static int do_amlmmc_response(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int dev; |
| struct mmc *mmc; |
| struct aml_card_sd_info *aml_priv; |
| struct sd_emmc_global_regs *sd_emmc_reg; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) |
| return 1; |
| if (!mmc->has_init) { |
| printf("mmc dev %d has not been initialed\n", dev); |
| return 1; |
| } |
| |
| aml_priv = mmc->priv; |
| sd_emmc_reg = aml_priv->sd_emmc_reg; |
| |
| printf("last cmd = %d, response0 = 0x%x\n", |
| (sd_emmc_reg->gcmd_cfg & 0x3f), sd_emmc_reg->gcmd_rsp0); |
| return 0; |
| } |
| |
| static int do_amlmmc_status(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int rc = 0; |
| int dev; |
| struct mmc *mmc; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) |
| return 1; |
| if (!mmc->has_init) { |
| printf("mmc dev %d has not been initialed\n", dev); |
| return 1; |
| } |
| rc = mmc_read_status(mmc, 1000); |
| if (rc) |
| return 1; |
| else |
| return 0; |
| } |
| |
| static int do_amlmmc_part(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int dev; |
| block_dev_desc_t *mmc_dev; |
| struct mmc *mmc; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| mmc_dev = mmc_get_dev(dev); |
| if (mmc_dev != NULL && |
| mmc_dev->type != DEV_TYPE_UNKNOWN) { |
| print_part(mmc_dev); |
| return 0; |
| } |
| puts("get mmc type error!\n"); |
| return 1; |
| } |
| |
| static int do_amlmmc_rescan(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int dev; |
| struct mmc *mmc; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) |
| return 1; |
| |
| return mmc_init(mmc); |
| } |
| |
| #ifdef CONFIG_SECURITYKEY |
| static int do_amlmmc_key(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| struct mmc *mmc; |
| int dev; |
| |
| //char *name = "logo"; |
| dev = CONFIG_SYS_MMC_BOOT_DEV; |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("device %d is invalid\n",dev); |
| return 1; |
| } |
| //mmc->key_protect = 0; |
| #ifdef CONFIG_STORE_COMPATIBLE |
| info_disprotect |= DISPROTECT_KEY; //disprotect |
| printf("emmc disprotect key\n"); |
| #endif |
| return 0; |
| } |
| #endif |
| |
| static int set_write_prot(struct mmc *mmc, u64 start) |
| { |
| struct mmc_cmd cmd; |
| int err; |
| |
| cmd.cmdidx = MMC_CMD_SET_WRITE_PROTECT; |
| cmd.cmdarg = start; |
| cmd.resp_type = MMC_RSP_R1b; |
| |
| err = mmc_send_cmd(mmc, &cmd, NULL); |
| if (err) |
| goto err_out; |
| |
| return 0; |
| |
| err_out: |
| puts("Failed: mmc write protect failed\n"); |
| return err; |
| } |
| |
| static int set_us_wp_en(struct mmc *mmc, u8 *ext_csd, u8 wp_enable_type) |
| { |
| u8 index = EXT_CSD_USER_WP; |
| u8 user_wp = ext_csd[index]; |
| user_wp = user_wp & (~WP_ENABLE_MASK); |
| user_wp = user_wp | wp_enable_type; |
| int err = 0; |
| err = mmc_set_ext_csd(mmc, index, user_wp); |
| if (err) |
| printf("Failed: set write protect enable failed\n"); |
| return err; |
| } |
| |
| static int mmc_set_us_perm_wp_dis(struct mmc *mmc, u8 *ext_csd) |
| { |
| u8 usr_wp = ext_csd[EXT_CSD_USER_WP]; |
| u8 perm_disable_bit = US_PERM_WP_DIS_BIT; |
| |
| int err; |
| if (usr_wp & perm_disable_bit) |
| return 0; |
| |
| usr_wp = usr_wp | perm_disable_bit; |
| err = mmc_set_ext_csd(mmc, EXT_CSD_USER_WP, usr_wp); |
| if (err) { |
| printf("Failed: set permanent write protect disable failed\n"); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int mmc_is_us_pwr_wp_dis(u8 user_wp) |
| { |
| return user_wp & US_PWR_WP_DIS_BIT; |
| } |
| |
| static int mmc_is_us_perm_wp_dis(u8 user_wp) |
| { |
| return user_wp & US_PERM_WP_DIS_BIT; |
| } |
| |
| static int check_wp_type(u8 *addr, u8 wp_type, u64 set_protect_cnt) |
| { |
| u8 type_mask = WP_TYPE_MASK; |
| u64 cnt = set_protect_cnt; |
| u8 times = 0; |
| u8 index = 7; |
| u8 cur_group_wp_type = addr[index]; |
| |
| while (cnt != 0) { |
| if (wp_type != ((type_mask)&(cur_group_wp_type))) { |
| return 1; |
| } |
| if (times == 3) { |
| times = 0; |
| index--; |
| cur_group_wp_type = addr[index]; |
| } |
| else { |
| cur_group_wp_type = cur_group_wp_type >> 2; |
| times++; |
| } |
| cnt--; |
| } |
| return 0; |
| } |
| |
| static int set_register_to_temporary(struct mmc *mmc, u8 *ext_csd) |
| { |
| int err; |
| u8 wp_enable_type = WP_TEMPORARY_EN_BIT; |
| err = set_us_wp_en(mmc, ext_csd, wp_enable_type); |
| if (err) |
| printf("Failed: set temporary write protect failed\n"); |
| return err; |
| } |
| |
| static int set_register_to_pwr(struct mmc *mmc, u8 *ext_csd) |
| { |
| int err; |
| u8 user_wp = ext_csd[EXT_CSD_USER_WP]; |
| u8 wp_enable_type = WP_POWER_ON_EN_BIT; |
| if (mmc_is_us_pwr_wp_dis(user_wp)) { |
| printf("Failed: power on protection had been disabled\n"); |
| return 1; |
| } |
| |
| err = mmc_set_us_perm_wp_dis(mmc, ext_csd); |
| if (err) { |
| printf("Failed: set permanent protection diable failed\n"); |
| return 1; |
| } |
| |
| err = set_us_wp_en(mmc, ext_csd, wp_enable_type); |
| if (err) { |
| printf("Failed: set power on write protect enable failed\n"); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int set_register_to_perm(struct mmc *mmc, u8 *ext_csd) |
| { |
| int err; |
| u8 wp_enable_type = WP_PERM_EN_BIT; |
| u8 user_wp = ext_csd[EXT_CSD_USER_WP]; |
| |
| if (mmc_is_us_perm_wp_dis(user_wp)) { |
| printf("Failed: Permanent protection had been disabled\n"); |
| return 1; |
| } |
| |
| err = set_us_wp_en(mmc, ext_csd, wp_enable_type); |
| if (err) { |
| printf("Failed: set permanent write protect enable failed\n"); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int set_wp_register(struct mmc *mmc, u8 *ext_csd, u8 wp_type) |
| { |
| int ret = 1; |
| if (wp_type == WP_POWER_ON_TYPE) |
| ret = set_register_to_pwr(mmc, ext_csd); |
| else if (wp_type == WP_PERMANENT_TYPE) |
| ret = set_register_to_perm(mmc, ext_csd); |
| else if (wp_type == WP_TEMPORARY_TYPE) |
| ret = set_register_to_temporary(mmc, ext_csd); |
| return ret; |
| } |
| |
| static u64 write_protect_group_size(struct mmc *mmc, u8 *ext_csd) |
| { |
| int erase_group_def = ext_csd[EXT_CSD_ERASE_GROUP_DEF]; |
| u64 write_protect_group_size; |
| int wp_grp_size = mmc->csd[2] & WP_GRP_SIZE_MASK; |
| int hc_wp_grp_size = ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; |
| |
| if (erase_group_def == 0) |
| write_protect_group_size = (u64)(wp_grp_size + 1) * mmc->erase_grp_size; |
| else |
| write_protect_group_size = (u64)hc_wp_grp_size * mmc->erase_grp_size; |
| |
| return write_protect_group_size; |
| } |
| |
| int is_write_protect_valid(u8 *ext_csd) |
| { |
| u8 class_6_ctrl = ext_csd[EXT_CSD_CLASS_6_CTRL]; |
| if (class_6_ctrl == 0) |
| return 1; |
| return 0; |
| } |
| |
| static int compute_write_protect_range(struct mmc *mmc, char *name, |
| u8 *ext_csd, u64 *wp_grp_size_addr, u64 *start_addr, u64 *end) |
| { |
| int blk_shift; |
| struct partitions *part_info; |
| u64 cnt; |
| u64 start; |
| u64 align_start; |
| u64 wp_grp_size; |
| u64 group_num ; |
| u64 partition_end; |
| |
| wp_grp_size = write_protect_group_size(mmc, ext_csd); |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| |
| part_info = find_mmc_partition_by_name(name); |
| if (part_info == NULL) |
| return 1; |
| |
| start = part_info->offset >> blk_shift; |
| if ((start % wp_grp_size)) { |
| align_start = (start + wp_grp_size - 1) / wp_grp_size * wp_grp_size; |
| printf("Caution! The partition start address isn't' aligned" |
| "to group size\n" |
| "the start address is change from 0x%llx to 0x%llx\n", |
| start, align_start); |
| } else { |
| align_start = start; |
| } |
| if (emmc_cur_partition && !strncmp(name, "bootloader", strlen("bootloader"))) |
| cnt = mmc->boot_size >> blk_shift; |
| else |
| cnt = part_info->size >> blk_shift; |
| if (cnt < wp_grp_size) { |
| printf("Caution: The partition size is 0x%llx sector smaller than " |
| "the group size 0x%llx sector, \n" |
| "so the partition can't be protect\n", cnt, wp_grp_size); |
| return 1; |
| } |
| |
| *start_addr = align_start; |
| *wp_grp_size_addr = wp_grp_size; |
| partition_end = start + cnt - 1; |
| group_num = (cnt - (align_start - start)) / wp_grp_size; |
| *end = align_start + group_num * wp_grp_size - 1; |
| |
| if (partition_end != *end) { |
| printf("Caution! The boundary of partition isn't aligned with write " |
| "protected group,\n" |
| "so the write protected boundry of the " |
| "partition is 0x%llx, rather than 0x%llx\n", |
| *end, partition_end); |
| } |
| |
| printf("write_protect group size is 0x%llx sector\n", wp_grp_size); |
| printf("The %s partition write protect group number is %lld\n", name, group_num); |
| #ifdef WP_DEBUG |
| printf("the start address is 0x%llx, group size is 0x%llx, end is 0x%llx\n", |
| *start_addr, *wp_grp_size_addr, *end); |
| #endif |
| return 0; |
| } |
| |
| static int send_wp_prot_type(struct mmc *mmc, void *dst, u64 blk) |
| { |
| struct mmc_cmd cmd; |
| int err; |
| struct mmc_data data; |
| |
| cmd.cmdidx = MMC_CMD_SEND_WRITE_PROT_TYPE; |
| cmd.cmdarg = blk; |
| cmd.resp_type = MMC_RSP_R1; |
| |
| data.dest = dst; |
| data.blocks = 1; |
| data.blocksize = 8; |
| data.flags = MMC_DATA_READ; |
| |
| err = mmc_send_cmd(mmc, &cmd, &data); |
| if (err) |
| goto err_out; |
| |
| return 0; |
| |
| err_out: |
| puts("Failed: mmc send write protect type failed\n"); |
| return err; |
| } |
| |
| static int is_wp_set_failed(struct mmc *mmc, u8 wp_type, u64 start, u64 group_cnt) |
| { |
| u8 *addr = NULL; |
| u8 err = 0; |
| |
| addr = malloc(sizeof(u64)); |
| if (addr == NULL) { |
| printf("Failed: malloc failed\n"); |
| return 1; |
| } |
| |
| err = send_wp_prot_type(mmc, addr, start); |
| if (err) |
| goto err_out; |
| |
| #ifdef WP_DEBUG |
| int i; |
| for (i = 0; i < 8; i++) |
| printf("write_protect status is %x\n", ((u8 *)addr)[i]); |
| #endif |
| if (check_wp_type(addr, wp_type, group_cnt)) { |
| printf("Failed: Write Protection set failed\n"); |
| goto err_out; |
| } |
| return 0; |
| |
| err_out: |
| free(addr); |
| return 1; |
| } |
| |
| static int send_part_wp_type(struct mmc *mmc, char *name) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u64 wp_grp_size, start, part_end; |
| u64 group_start; |
| void *addr = NULL; |
| int i; |
| int ret; |
| |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| if (ret) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| addr = malloc(sizeof(u64)); |
| if (addr == NULL) { |
| printf("Failed: malloc failed\n"); |
| return 1; |
| } |
| |
| err = compute_write_protect_range(mmc, name, ext_csd, |
| &wp_grp_size, &start, &part_end); |
| if (err) |
| goto _out; |
| |
| group_start = start; |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = send_wp_prot_type(mmc, addr, group_start); |
| if (err) |
| goto _out; |
| printf("The write protect type for the 32 groups after 0x%llx is: \n0x", |
| group_start); |
| for (i = 0; i < 8; i++) |
| printf("%02x", ((u8*)addr)[i]); |
| printf("\n"); |
| group_start += 32 * wp_grp_size; |
| } |
| _out: |
| free(addr); |
| return 0; |
| } |
| |
| static int send_add_wp_type(struct mmc *mmc, u64 start, u64 cnt) |
| { |
| u8 ext_csd[512] = {0}; |
| u64 wp_grp_size = 0; |
| u64 part_end = 0; |
| u64 group_start; |
| u64 mmc_boundary; |
| int blk_shift; |
| void *addr = NULL; |
| int i; |
| int ret; |
| |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| if (ret) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| addr = malloc(sizeof(u64)); |
| if (addr == NULL) { |
| printf("Failed: malloc failed\n"); |
| return 1; |
| } |
| |
| wp_grp_size = write_protect_group_size(mmc, ext_csd); |
| |
| if ((start % wp_grp_size)) { |
| group_start = (start + wp_grp_size - 1) / wp_grp_size * wp_grp_size; |
| printf("Caution! The partition start address isn't' aligned" |
| "to group size\n" |
| "the start address is change from 0x%llx to 0x%llx\n", |
| start, group_start); |
| part_end = group_start + (cnt - 1) * wp_grp_size - 1; |
| printf("The write protect group number is 0x%llx, rather than 0x%lld\n", |
| cnt - 1, cnt); |
| } else { |
| group_start = start; |
| part_end = group_start + cnt * wp_grp_size - 1; |
| } |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| mmc_boundary = mmc->capacity>>blk_shift; |
| |
| if ((part_end + 1) > mmc_boundary) { |
| printf("Error: the operation cross the boundary of mmc\n"); |
| free(addr); |
| return 1; |
| } |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| ret = send_wp_prot_type(mmc, addr, group_start); |
| if (ret) { |
| free(addr); |
| return 1; |
| } |
| printf("The write protect type for the 32 groups after 0x%llx is: \n0x", |
| group_start); |
| for (i = 0; i < 8; i++) |
| printf("%02x", ((u8*)addr)[i]); |
| printf("\n"); |
| group_start += 32 * wp_grp_size; |
| } |
| free(addr); |
| return 0; |
| } |
| |
| static int set_part_write_protect(struct mmc *mmc, u8 wp_type, char *name) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u8 group_num = 32; |
| u64 wp_grp_size, start, part_end; |
| u64 group_start; |
| u64 check_group_start; |
| u64 set_protect_cnt = 0; |
| |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| if (err) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| err = compute_write_protect_range(mmc, name, ext_csd, |
| &wp_grp_size, &start, &part_end); |
| if (err) |
| return 1; |
| |
| group_start = start; |
| err = set_wp_register(mmc, ext_csd, wp_type); |
| if (err) |
| return 1; |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = set_write_prot(mmc, group_start); |
| if (err) |
| return 1; |
| group_start += wp_grp_size; |
| set_protect_cnt++; |
| //check write protect type every 32 group |
| if (set_protect_cnt % 32 == 0) { |
| check_group_start = group_start - group_num * wp_grp_size; |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| } |
| |
| group_num = set_protect_cnt % 32; |
| check_group_start = group_start - group_num * wp_grp_size; |
| |
| if (group_num) { |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int set_add_write_protect(struct mmc *mmc, u8 wp_type, u64 start, u64 cnt) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| int group_num = 32; |
| u64 wp_grp_size, part_end; |
| u64 group_start; |
| u64 check_group_start; |
| u64 set_protect_cnt = 0; |
| u64 mmc_boundary = 0; |
| int blk_shift; |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| |
| if (err) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| wp_grp_size = write_protect_group_size(mmc, ext_csd); |
| |
| if ((start % wp_grp_size)) { |
| group_start = (start + wp_grp_size - 1) / wp_grp_size * wp_grp_size; |
| printf("Caution! The partition start address isn't' aligned" |
| "to group size\n" |
| "the start address is change from 0x%llx to 0x%llx\n", |
| start, group_start); |
| part_end = group_start + (cnt - 1) * wp_grp_size - 1; |
| printf("The write protect group number is 0x%llx, rather than 0x%lld\n", |
| cnt - 1, cnt); |
| } else { |
| group_start = start; |
| part_end = group_start + cnt * wp_grp_size - 1; |
| } |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| mmc_boundary = mmc->capacity>>blk_shift; |
| |
| if ((part_end + 1) > mmc_boundary) { |
| printf("Error: the operation cross the boundary of mmc\n"); |
| return 1; |
| } |
| |
| err = set_wp_register(mmc, ext_csd, wp_type); |
| if (err) |
| return 1; |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = set_write_prot(mmc, group_start); |
| if (err) |
| return 1; |
| group_start += wp_grp_size; |
| set_protect_cnt++; |
| //check write protect type every 32 group |
| if (set_protect_cnt % 32 == 0) { |
| check_group_start = group_start - group_num * wp_grp_size; |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| } |
| |
| group_num = set_protect_cnt % 32; |
| check_group_start = group_start - group_num * wp_grp_size; |
| |
| if (group_num) { |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int do_amlmmc_write_protect(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| struct mmc *mmc; |
| int dev = 1; |
| char *name = NULL; |
| char *wp_type_str = NULL; |
| u8 write_protect_type; |
| u64 start, cnt; |
| |
| if (argc > 5 || argc < 4) |
| return ret; |
| if (argc == 4) { |
| name = argv[2]; |
| wp_type_str = argv[3]; |
| dev = find_dev_num_by_partition_name(name); |
| if (dev < 0) { |
| printf("Error: Cannot find dev.\n"); |
| return CMD_RET_USAGE; |
| } |
| } else { |
| start = simple_strtoull(argv[2], NULL, 16); |
| cnt = simple_strtoull(argv[3], NULL, 0); |
| wp_type_str = argv[4]; |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| if (IS_SD(mmc)) { |
| mmc = find_mmc_device(~dev); |
| if (!mmc) |
| return 1; |
| if (IS_SD(mmc)) { |
| printf("SD card can not be write protect\n"); |
| return 1; |
| } |
| } |
| |
| mmc_init(mmc); |
| |
| if (strcmp(wp_type_str, "temporary") == 0) |
| write_protect_type = WP_TEMPORARY_TYPE; |
| else if (strcmp(wp_type_str, "power_on") == 0 ) |
| write_protect_type = WP_POWER_ON_TYPE; |
| else if (strcmp(wp_type_str, "permanent") == 0) |
| write_protect_type = WP_PERMANENT_TYPE; |
| else |
| return ret; |
| |
| if (argc == 4) |
| ret = set_part_write_protect(mmc, write_protect_type, name); |
| else |
| ret = set_add_write_protect(mmc, write_protect_type, start, cnt); |
| |
| return ret; |
| } |
| |
| static int clear_write_prot_per_group(struct mmc *mmc, u64 blk) |
| { |
| struct mmc_cmd cmd; |
| int err; |
| |
| cmd.cmdidx = MMC_CMD_CLR_WRITE_PROT; |
| cmd.cmdarg = blk; |
| cmd.resp_type = MMC_RSP_R1b; |
| |
| err = mmc_send_cmd(mmc, &cmd, NULL); |
| |
| return err; |
| } |
| |
| static int set_part_clear_wp(struct mmc *mmc, char *name) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u8 group_num = 32; |
| u64 wp_grp_size, start, part_end; |
| u64 group_start; |
| u64 check_group_start; |
| u64 set_protect_cnt = 0; |
| u8 wp_type = WP_CLEAR_TYPE; |
| |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| if (err) { |
| printf("get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("CLASS_6_CTRL isn't '0' write protect process is invalid\n"); |
| return 1; |
| } |
| |
| err = compute_write_protect_range(mmc, name, ext_csd, |
| &wp_grp_size, &start, &part_end); |
| if (err) |
| return 1; |
| |
| group_start = start; |
| /* |
| if (!is_wp_type_temporary(ext_csd)) { |
| printf("The write protect can't be clear\n"); |
| return 1; |
| } |
| */ |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = clear_write_prot_per_group(mmc, group_start); |
| if (err) { |
| printf("Error: The write protect can't be clear\n"); |
| return 1; |
| } |
| group_start += wp_grp_size; |
| set_protect_cnt++; |
| //check write protect type every 32 group |
| if (set_protect_cnt % 32 == 0) { |
| check_group_start = group_start - group_num * wp_grp_size; |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| } |
| |
| group_num = set_protect_cnt % 32; |
| check_group_start = group_start - group_num * wp_grp_size; |
| |
| if (group_num) { |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int set_add_clear_wp(struct mmc *mmc, u64 start, u64 cnt) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u8 group_num = 32; |
| u64 wp_grp_size, part_end; |
| u64 group_start; |
| u64 check_group_start; |
| u64 set_protect_cnt = 0; |
| u8 wp_type = WP_CLEAR_TYPE; |
| int blk_shift; |
| u64 mmc_boundary; |
| |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| if (err) { |
| printf("get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("CLASS_6_CTRL isn't '0' write protect process is invalid\n"); |
| return 1; |
| } |
| |
| wp_grp_size = write_protect_group_size(mmc, ext_csd); |
| |
| if ((start % wp_grp_size)) { |
| group_start = (start + wp_grp_size - 1) / wp_grp_size * wp_grp_size; |
| printf("Caution! The partition start address isn't' aligned" |
| "to group size\n" |
| "the start address is change from 0x%llx to 0x%llx\n", |
| start, group_start); |
| part_end = group_start + (cnt - 1) * wp_grp_size - 1; |
| printf("The write protect group number is 0x%llx, rather than 0x%lld\n", |
| cnt - 1, cnt); |
| } else { |
| group_start = start; |
| part_end = group_start + cnt * wp_grp_size - 1; |
| } |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| mmc_boundary = mmc->capacity>>blk_shift; |
| |
| if ((part_end + 1) > mmc_boundary) { |
| printf("Error: the operation cross the boundary of mmc\n"); |
| return 1; |
| } |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = clear_write_prot_per_group(mmc, group_start); |
| if (err) { |
| printf("Error: The write protect can't be clear\n"); |
| return 1; |
| } |
| group_start += wp_grp_size; |
| set_protect_cnt++; |
| //check write protect type every 32 group |
| if (set_protect_cnt % 32 == 0) { |
| check_group_start = group_start - group_num * wp_grp_size; |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| } |
| |
| group_num = set_protect_cnt % 32; |
| check_group_start = group_start - group_num * wp_grp_size; |
| |
| if (group_num) { |
| err = is_wp_set_failed(mmc, wp_type, check_group_start, group_num); |
| if (err) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int do_amlmmc_clear_wp(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| struct mmc *mmc; |
| int dev = 1; |
| char *name = NULL; |
| u64 start, cnt; |
| |
| if (argc < 3 || argc > 4) |
| return ret; |
| |
| if (argc == 3) { |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name(name); |
| if (dev < 0) { |
| printf("Error: Cannot find dev.\n"); |
| return CMD_RET_USAGE; |
| } |
| } else { |
| start = simple_strtoull(argv[2], NULL, 16); |
| cnt = simple_strtoull(argv[3], NULL, 10); |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| if (IS_SD(mmc)) { |
| mmc = find_mmc_device(~dev); |
| if (!mmc) |
| return 1; |
| if (IS_SD(mmc)) { |
| printf("SD card can not be write protect\n"); |
| return 1; |
| } |
| } |
| |
| mmc_init(mmc); |
| |
| if (argc == 3) |
| ret = set_part_clear_wp(mmc, name); |
| else |
| ret = set_add_clear_wp(mmc, start, cnt); |
| |
| return ret; |
| } |
| |
| static int send_write_prot_status_group(struct mmc *mmc, u64 blk) |
| { |
| struct mmc_cmd cmd; |
| struct mmc_data data; |
| int err; |
| void *addr = NULL; |
| int i = 0; |
| addr = malloc(4*sizeof(u8)); |
| |
| if (addr == NULL) { |
| printf("Failed: malloc failed\n"); |
| return 1; |
| } |
| |
| cmd.cmdidx = MMC_CMD_SEND_WRITE_PROT; |
| cmd.cmdarg = blk; |
| cmd.resp_type = MMC_RSP_R1; |
| |
| data.dest = addr; |
| data.blocks = 1; |
| data.blocksize = 4; |
| data.flags = MMC_DATA_READ; |
| |
| err = mmc_send_cmd(mmc, &cmd, &data); |
| if (err) |
| goto err_out; |
| |
| printf("The write protect type for the 32 groups after 0x%llx is:\n0x", |
| blk); |
| for (i = 0 ; i < 4; i++) |
| printf("%02x", ((u8 *)addr)[i]); |
| printf("\n"); |
| |
| free(addr); |
| return 0; |
| err_out: |
| free(addr); |
| return 1; |
| } |
| |
| static int send_part_wp_status(struct mmc *mmc, char *name) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u64 wp_grp_size, start, part_end; |
| u64 group_start; |
| |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| if (err) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| err = compute_write_protect_range(mmc, name, ext_csd, |
| &wp_grp_size, &start, &part_end); |
| if (err) |
| return 1; |
| |
| group_start = start; |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = send_write_prot_status_group(mmc, group_start); |
| if (err) |
| return 1; |
| group_start += 32 * wp_grp_size; |
| } |
| |
| return 0; |
| } |
| |
| static int send_add_wp_status(struct mmc *mmc, u64 start, u64 cnt) |
| { |
| int err = 0; |
| u8 ext_csd[512] = {0}; |
| u64 wp_grp_size, part_end; |
| u64 group_start; |
| int blk_shift; |
| u64 mmc_boundary; |
| err = mmc_get_ext_csd(mmc, ext_csd); |
| if (err) { |
| printf("Failed: get ext_csd failed\n"); |
| return 1; |
| } |
| |
| if (!is_write_protect_valid(ext_csd)) { |
| printf("Failed: CLASS_6_CTRL isn't '0' " |
| "write protect process is invalid\n"); |
| return 1; |
| } |
| |
| wp_grp_size = write_protect_group_size(mmc, ext_csd); |
| |
| if ((start % wp_grp_size)) { |
| group_start = (start + wp_grp_size - 1) / wp_grp_size * wp_grp_size; |
| printf("Caution! The partition start address isn't' aligned" |
| "to group size\n" |
| "the start address is change from 0x%llx to 0x%llx\n", |
| start, group_start); |
| part_end = group_start + (cnt - 1) * wp_grp_size - 1; |
| printf("The write protect group number is 0x%llx, rather than 0x%lld\n", |
| cnt - 1, cnt); |
| } else { |
| group_start = start; |
| part_end = group_start + cnt * wp_grp_size - 1; |
| } |
| |
| blk_shift = mmc->read_bl_len > 0 ? ffs(mmc->read_bl_len) - 1 : 0; |
| mmc_boundary = mmc->capacity>>blk_shift; |
| |
| if ((part_end + 1) > mmc_boundary) { |
| printf("Error: the operation cross the boundary of mmc\n"); |
| return 1; |
| } |
| |
| while ((group_start + wp_grp_size - 1) <= part_end) { |
| err = send_write_prot_status_group(mmc, group_start); |
| if (err) |
| return 1; |
| group_start += 32 * wp_grp_size; |
| } |
| |
| return 0; |
| } |
| |
| static int do_amlmmc_send_wp_status(cmd_tbl_t *cmdtp, |
| int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| struct mmc *mmc; |
| int dev = 1; |
| char *name = NULL; |
| u64 start, cnt; |
| |
| if (argc < 3 || argc > 4) |
| return ret; |
| |
| if (argc == 3) { |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name(name); |
| if (dev < 0) { |
| printf("Error: Cannot find dev.\n"); |
| return 1; |
| } |
| } else { |
| start = simple_strtoull(argv[2], NULL, 16); |
| cnt = simple_strtoull(argv[3], NULL, 0); |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return ret; |
| |
| if (IS_SD(mmc)) { |
| mmc = find_mmc_device(~dev); |
| if (!mmc) |
| return ret; |
| if (IS_SD(mmc)) { |
| printf("SD card can not be write protect\n"); |
| return 1; |
| } |
| } |
| |
| mmc_init(mmc); |
| |
| if (argc == 3) |
| ret = send_part_wp_status(mmc, name); |
| else |
| ret = send_add_wp_status(mmc, start, cnt); |
| |
| if (ret) { |
| printf("Failed: send partition write protect status failed\n"); |
| } |
| |
| return ret; |
| } |
| |
| static int do_amlmmc_send_wp_type(cmd_tbl_t *cmdtp, |
| int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| struct mmc *mmc; |
| int dev = 1; |
| char *name = NULL; |
| u64 start, cnt; |
| |
| if (argc < 3 || argc > 4) |
| return ret; |
| |
| if (argc == 3) { |
| name = argv[2]; |
| dev = find_dev_num_by_partition_name(name); |
| if (dev < 0) { |
| printf("Error: Cannot find dev.\n"); |
| return 1; |
| } |
| } else { |
| start = simple_strtoull(argv[2], NULL, 16); |
| cnt = simple_strtoull(argv[3], NULL, 0); |
| } |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) |
| return 1; |
| |
| if (IS_SD(mmc)) { |
| mmc = find_mmc_device(~dev); |
| if (!mmc) |
| return 1; |
| if (IS_SD(mmc)) { |
| printf("SD card can not be write protect\n"); |
| return 1; |
| } |
| } |
| |
| mmc_init(mmc); |
| |
| if (argc == 3) |
| ret = send_part_wp_type(mmc, name); |
| else |
| ret = send_add_wp_type(mmc, start, cnt); |
| |
| if (ret) { |
| printf("Failed: send parittion write protect type failed\n"); |
| } |
| |
| return ret; |
| } |
| |
| static int set_driver_strength(struct mmc *mmc, int strength) |
| { |
| int ret = 0; |
| u8 ext_csd[512] = {0}; |
| u8 strength_type = 0; |
| u8 driver_strength; |
| u8 hs_timing = 0; |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| if (ret) { |
| printf("get ext_csd failed\n"); |
| return ret; |
| } |
| strength_type = 1 << strength; |
| driver_strength = ext_csd[EXT_CSD_DRIVER_STRENGTH]; |
| if (0 == (strength_type & driver_strength)) { |
| printf("Failed: This device didn't support strength type %d\n", strength); |
| return 1; |
| } |
| |
| hs_timing = ext_csd[EXT_CSD_HS_TIMING]; |
| if ((hs_timing >> 4) > 0) { |
| printf("Failed: The driver strength has been set already, \ |
| please reset the device\n"); |
| return 1; |
| } |
| |
| hs_timing = hs_timing | (strength << 4); |
| |
| ret = mmc_set_ext_csd(mmc, EXT_CSD_HS_TIMING, hs_timing); |
| if (ret) { |
| printf("set ext_csd hs_timing field failed\n"); |
| return ret; |
| } |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| if (ret) { |
| printf("get ext_csd failed\n"); |
| return ret; |
| } |
| printf("The ext_csd[HS_TIMING] has been set to 0x%x\n", |
| ext_csd[EXT_CSD_HS_TIMING]); |
| return ret; |
| } |
| |
| static int get_driver_strength(struct mmc *mmc) |
| { |
| int ret = 0; |
| u8 ext_csd[512] = {0}; |
| u8 support_ds_type = 0; |
| u8 cur_driver_strength; |
| u8 hs_timing = 0; |
| ret = mmc_get_ext_csd(mmc, ext_csd); |
| if (ret) { |
| printf("get ext_csd failed\n"); |
| return ret; |
| } |
| |
| support_ds_type = ext_csd[EXT_CSD_DRIVER_STRENGTH]; |
| |
| hs_timing = ext_csd[EXT_CSD_HS_TIMING]; |
| cur_driver_strength = hs_timing >> 4; |
| |
| printf("current strength type is: "); |
| int strength_type = 0; |
| while (support_ds_type) { |
| if (support_ds_type & 1) { |
| if (cur_driver_strength == strength_type) |
| printf("[%d] ", strength_type); |
| else |
| printf("%d ", strength_type); |
| } |
| strength_type++; |
| support_ds_type = support_ds_type >> 1; |
| } |
| printf("\n"); |
| return ret; |
| } |
| |
| static int amlmmc_set_driver_strength(int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| int dev, strength; |
| struct mmc *mmc; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| strength = simple_strtoul(argv[3], NULL, 10); |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| |
| ret = set_driver_strength(mmc, strength); |
| |
| return ret; |
| } |
| |
| static int amlmmc_get_driver_strength(int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| int dev; |
| struct mmc *mmc; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| |
| ret = get_driver_strength(mmc); |
| |
| return ret; |
| } |
| |
| static int do_amlmmc_driver_strength(cmd_tbl_t *cmdtp, |
| int flag, int argc, char *const argv[]) |
| { |
| int ret = CMD_RET_USAGE; |
| |
| if (argc == 3) |
| ret = amlmmc_get_driver_strength(argc,argv); |
| else if (argc == 4) |
| ret = amlmmc_set_driver_strength(argc,argv); |
| |
| return ret; |
| } |
| |
| #ifdef MMC_HS200_MODE |
| static int do_amlmmc_reset_delay(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| int dev; |
| |
| struct aml_card_sd_info *aml_priv; |
| struct sd_emmc_global_regs *sd_emmc_regs; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| aml_priv = mmc->priv; |
| sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| sd_emmc_regs->gdelay = 0; |
| sd_emmc_regs->gdelay1 = 0; |
| |
| return CMD_RET_SUCCESS; |
| |
| } |
| |
| static int do_amlmmc_clktest(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| int dev; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| aml_sd_emmc_clktest(mmc); |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_amlmmc_set_rxdelay(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| int dev; |
| u32 delay1, delay2; |
| |
| if (argc != 5) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| delay1 = simple_strtoul(argv[3], NULL, 16); |
| delay2 = simple_strtoul(argv[4], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| struct aml_card_sd_info *aml_priv = mmc->priv; |
| struct sd_emmc_global_regs *sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| sd_emmc_regs->gdelay = delay1; |
| sd_emmc_regs->gdelay1 = delay2; |
| |
| update_all_line_eyetest(mmc); |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_amlmmc_set_txdelay(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| u32 delay; |
| u32 mask; |
| int dev; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| delay = simple_strtoul(argv[3], NULL, 16); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| if (delay < 0 || delay > 63) { |
| printf("error: tx delay is out of range\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| struct aml_card_sd_info *aml_priv = mmc->priv; |
| struct sd_emmc_global_regs *sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| mask = ~(0x3f<<16); |
| sd_emmc_regs->gclock = sd_emmc_regs->gclock & mask; |
| sd_emmc_regs->gclock |= delay << 16; |
| |
| update_all_line_eyetest(mmc); |
| return CMD_RET_SUCCESS; |
| } |
| |
| static void set_vddee_voltage(unsigned int target_voltage) |
| { |
| unsigned int to; |
| |
| for (to = 0; to < ARRAY_SIZE(pwm_voltage_table_ee); to++) { |
| if (pwm_voltage_table_ee[to][1] >= target_voltage) { |
| break; |
| } |
| } |
| |
| if (to >= ARRAY_SIZE(pwm_voltage_table_ee)) { |
| to = ARRAY_SIZE(pwm_voltage_table_ee) - 1; |
| } |
| |
| SD_EMMC_VDDEE_REG = pwm_voltage_table_ee[to][0]; |
| } |
| |
| static int send_vddee_voltage(unsigned int target_voltage) |
| { |
| unsigned int to; |
| int vddee; |
| |
| for (to = 0; to < ARRAY_SIZE(pwm_voltage_table_ee); to++) { |
| if (pwm_voltage_table_ee[to][0] <= target_voltage) { |
| break; |
| } |
| } |
| |
| if (to >= ARRAY_SIZE(pwm_voltage_table_ee)) { |
| to = ARRAY_SIZE(pwm_voltage_table_ee) - 1; |
| } |
| |
| vddee = pwm_voltage_table_ee[to][1]; |
| |
| return vddee; |
| } |
| |
| static int do_amlmmc_set_vddee(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| unsigned int vddee; |
| struct mmc *mmc; |
| int dev; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| vddee = simple_strtoul(argv[3], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| set_vddee_voltage(vddee); |
| return CMD_RET_SUCCESS; |
| |
| } |
| |
| static int do_amlmmc_show_vddee(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| int vddee; |
| unsigned int vddee_reg; |
| struct mmc *mmc; |
| int dev; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| vddee_reg = SD_EMMC_VDDEE_REG; |
| |
| vddee = send_vddee_voltage(vddee_reg); |
| |
| printf("meson-mmc: emmc: vddee is %d mv\n", vddee); |
| |
| return CMD_RET_SUCCESS; |
| |
| } |
| |
| static int do_amlmmc_refix(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| int err; |
| struct mmc *mmc; |
| int dev; |
| |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| err = aml_emmc_refix(mmc); |
| if (err) |
| printf("refix failed\n"); |
| return err; |
| |
| } |
| |
| static int do_amlmmc_move_all_delay(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| u32 delay1, delay2; |
| u8 count; |
| int dev; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| dev = simple_strtoul(argv[2], NULL, 10); |
| count = simple_strtoul(argv[3], NULL, 16); |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| struct aml_card_sd_info *aml_priv = mmc->priv; |
| struct sd_emmc_global_regs *sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| delay1 = sd_emmc_regs->gdelay; |
| delay2 = sd_emmc_regs->gdelay1; |
| |
| delay1 += (count<<0)|(count<<6)|(count<<12)|(count<<18)|(count<<24); |
| delay2 += (count<<0)|(count<<6)|(count<<12); |
| |
| sd_emmc_regs->gdelay = delay1; |
| sd_emmc_regs->gdelay1 = delay2; |
| |
| update_all_line_eyetest(mmc); |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_amlmmc_move_sig_delay(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| u32 delay1, delay2; |
| u8 line, count; |
| int dev; |
| |
| if (argc != 5) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| line = simple_strtoul(argv[3], NULL, 10); |
| count = simple_strtoul(argv[4], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| if (line > 9 || line < 0) { |
| printf("error: data line out of range\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| struct aml_card_sd_info *aml_priv = mmc->priv; |
| struct sd_emmc_global_regs *sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| delay1 = sd_emmc_regs->gdelay; |
| delay2 = sd_emmc_regs->gdelay1; |
| |
| if (line < 5) { |
| delay1 += (count<<(6*line)); |
| sd_emmc_regs->gdelay = delay1; |
| } else { |
| delay2 += (count<<(6 * (line - 5))); |
| sd_emmc_regs->gdelay1 = delay2; |
| } |
| |
| update_all_line_eyetest(mmc); |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_amlmmc_line_eyetest(cmd_tbl_t *cmdtp, int flag, |
| int argc, char * const argv[]) |
| { |
| struct mmc *mmc; |
| u32 delay1, delay2; |
| u8 line, count; |
| int dev; |
| u32 mask = 0x3f; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| dev = simple_strtoul(argv[2], NULL, 10); |
| line = simple_strtoul(argv[3], NULL, 10); |
| |
| if (dev < 0) { |
| printf("Cannot find dev.\n"); |
| return 1; |
| } |
| |
| mmc = find_mmc_device(dev); |
| |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| mmc_init(mmc); |
| if (!mmc) |
| return 1; |
| |
| if (line > 9 || line < 0) { |
| printf("error: data line out of range\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| struct aml_card_sd_info *aml_priv = mmc->priv; |
| struct sd_emmc_global_regs *sd_emmc_regs = aml_priv->sd_emmc_reg; |
| |
| delay1 = sd_emmc_regs->gdelay; |
| delay2 = sd_emmc_regs->gdelay1; |
| |
| if (line < 5) |
| delay1 &= ~(mask << (6 * line)); |
| else |
| delay2 &= ~(mask << (6 * (line - 5))); |
| |
| for (count = 0; count < 64; count++) { |
| if (line < 5) { |
| delay1 &= ~(mask << (6 * line)); |
| delay1 |= (count << (6 * line)); |
| sd_emmc_regs->gdelay = delay1; |
| } else { |
| delay2 &= ~(mask << (6 * (line - 5))); |
| delay2 |= (count << (6 * line)); |
| sd_emmc_regs->gdelay1 = delay2; |
| } |
| emmc_eyetest_log(mmc, line); |
| } |
| return CMD_RET_SUCCESS; |
| } |
| #endif |
| static cmd_tbl_t cmd_amlmmc[] = { |
| U_BOOT_CMD_MKENT(read, 6, 0, do_amlmmc_read, "", ""), |
| U_BOOT_CMD_MKENT(write, 6, 0, do_amlmmc_write, "", ""), |
| U_BOOT_CMD_MKENT(erase, 5, 0, do_amlmmc_erase, "", ""), |
| U_BOOT_CMD_MKENT(rescan, 3, 0, do_amlmmc_rescan, "", ""), |
| U_BOOT_CMD_MKENT(part, 3, 0, do_amlmmc_part, "", ""), |
| U_BOOT_CMD_MKENT(list, 2, 0, do_amlmmc_list, "", ""), |
| U_BOOT_CMD_MKENT(switch, 4, 0, do_amlmmc_switch, "", ""), |
| U_BOOT_CMD_MKENT(status, 3, 0, do_amlmmc_status, "", ""), |
| U_BOOT_CMD_MKENT(ext_csd, 5, 0, do_amlmmc_ext_csd, "", ""), |
| U_BOOT_CMD_MKENT(response, 3, 0, do_amlmmc_response, "", ""), |
| U_BOOT_CMD_MKENT(controller, 3, 0, do_amlmmc_controller, "", ""), |
| U_BOOT_CMD_MKENT(size, 4, 0, do_amlmmc_size, "", ""), |
| U_BOOT_CMD_MKENT(env, 2, 0, do_amlmmc_env, "", ""), |
| U_BOOT_CMD_MKENT(write_protect, 5, 0, do_amlmmc_write_protect, "", ""), |
| U_BOOT_CMD_MKENT(send_wp_status, 4, 0, do_amlmmc_send_wp_status, "", ""), |
| U_BOOT_CMD_MKENT(send_wp_type, 4, 0, do_amlmmc_send_wp_type, "", ""), |
| U_BOOT_CMD_MKENT(clear_wp, 4, 0, do_amlmmc_clear_wp, "", ""), |
| U_BOOT_CMD_MKENT(ds, 4, 0, do_amlmmc_driver_strength, "", ""), |
| #ifdef CONFIG_SECURITYKEY |
| U_BOOT_CMD_MKENT(key, 2, 0, do_amlmmc_key, "", ""), |
| #endif |
| #ifdef MMC_HS200_MODE |
| U_BOOT_CMD_MKENT(clktest, 3, 0, do_amlmmc_clktest, "", ""), |
| U_BOOT_CMD_MKENT(refix, 3, 0, do_amlmmc_refix, "", ""), |
| U_BOOT_CMD_MKENT(move_all, 4, 0, do_amlmmc_move_all_delay, "", ""), |
| U_BOOT_CMD_MKENT(move_single, 5, 0, do_amlmmc_move_sig_delay, "", ""), |
| U_BOOT_CMD_MKENT(set_rxdly, 5, 0, do_amlmmc_set_rxdelay, "", ""), |
| U_BOOT_CMD_MKENT(set_txdly, 4, 0, do_amlmmc_set_txdelay, "", ""), |
| U_BOOT_CMD_MKENT(set_vddee, 4, 0, do_amlmmc_set_vddee, "", ""), |
| U_BOOT_CMD_MKENT(show_vddee, 3, 0, do_amlmmc_show_vddee, "", ""), |
| U_BOOT_CMD_MKENT(reset_dly, 3, 0, do_amlmmc_reset_delay, "", ""), |
| U_BOOT_CMD_MKENT(line_eyetest, 4, 0, do_amlmmc_line_eyetest, "", ""), |
| #endif |
| }; |
| |
| static int do_amlmmcops(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| cmd_tbl_t *cp; |
| |
| cp = find_cmd_tbl(argv[1], cmd_amlmmc, ARRAY_SIZE(cmd_amlmmc)); |
| |
| if (cp == NULL || argc > cp->maxargs) |
| return CMD_RET_USAGE; |
| |
| if (flag == CMD_FLAG_REPEAT && !cp->repeatable) |
| return CMD_RET_SUCCESS; |
| |
| return cp->cmd(cmdtp, flag, argc, argv); |
| } |
| |
| U_BOOT_CMD( |
| amlmmc, 6, 1, do_amlmmcops, |
| "AMLMMC sub system", |
| "read <partition_name> ram_addr addr_byte# cnt_byte\n" |
| "amlmmc write <partition_name> ram_addr addr_byte# cnt_byte\n" |
| "amlmmc erase <partition_name> addr_byte# cnt_byte\n" |
| "amlmmc erase <partition_name>/<device num>\n" |
| "amlmmc rescan <device_num>\n" |
| "amlmmc part <device_num> - show partition infomation of mmc\n" |
| "amlmmc list - lists available devices\n" |
| "amlmmc env - display env partition offset\n" |
| "amlmmc switch <device_num> <part name> - part name : boot0, boot1, user\n" |
| "amlmmc status <device_num> - read sd/emmc device status\n" |
| "amlmmc ext_csd <device_num> <byte> - read sd/emmc device EXT_CSD [byte]\n" |
| "amlmmc ext_csd <device_num> <byte> <value> - write sd/emmc device EXT_CSD [byte] value\n" |
| "amlmmc response <device_num> - read sd/emmc last command response\n" |
| "amlmmc controller <device_num> - read sd/emmc controller register\n" |
| "amlmmc write_protect <partition_name> <write_protect_type>\n" |
| " - set write protect on partition through power_on or temporary\n" |
| "amlmmc write_protect <addr_base16> <cnt_base10> <write_protect_type>\n" |
| " - set write protect on specified address through power_on or temporary\n" |
| "amlmmc send_wp_status <partition_name> send protect status of partition\n" |
| "amlmmc send_wp_status <addr_base16> <cnt_base10> send protect status on specified address\n" |
| "amlmmc send_wp_type <partition_name> send protect type of partition\n" |
| "amlmmc send_wp_type <addr_base16> <cnt_base10> send protect type on specified address\n" |
| "amlmmc clear_wp <partition_name> clear write protect of partition\n" |
| "amlmmc clear_wp <addr_base16> <cnt_base10> clear write protect on specified addresst\n" |
| "amlmmc ds <dev_num> <val> set driver strength\n" |
| #ifdef CONFIG_SECURITYKEY |
| "amlmmc key - disprotect key partition\n" |
| #endif |
| #ifdef MMC_HS200_MODE |
| "amlmmc clktest <dev> - display info of delaycell and count\n" |
| "amlmmc reset_dly <dev> - reset all delay register\n" |
| "amlmmc set_rxdly <dev> <0xdelay1> <0xdelay2> - manually set rx delay value\n" |
| "amlmmc set_txdly <dev> <0xdelay> - manually set tx delay\n" |
| "amlmmc refix <dev> - fix adj\n" |
| "amlmmc move_all <dev> <count> - move all data delay line <count> steps\n" |
| "amlmmc move_single <dev> <line> <count> - move <line> <count> steps\n" |
| "amlmmc set_vddee <dev> <value> - set vddee\n" |
| "amlmmc show_vddee <dev> -show current vddee\n" |
| "amlmmc line_eyetest <dev> <line> single line eyetest\n" |
| #endif |
| ); |
| |
| static u32 _calc_dtb_checksum(struct aml_dtb_rsv * dtb) |
| { |
| int i = 0; |
| int size = sizeof(struct aml_dtb_rsv) - sizeof(u32); |
| u32 * buffer; |
| u32 checksum = 0; |
| |
| if ((u64)dtb % 4 != 0) { |
| BUG(); |
| } |
| |
| size = size >> 2; |
| buffer = (u32*) dtb; |
| while (i < size) |
| checksum += buffer[i++]; |
| |
| return checksum; |
| } |
| |
| static int _verify_dtb_checksum(struct aml_dtb_rsv * dtb) |
| { |
| u32 checksum; |
| |
| checksum = _calc_dtb_checksum(dtb); |
| dtb_info("calc %x, store %x\n", checksum, dtb->checksum); |
| |
| return !(checksum == dtb->checksum); |
| } |
| |
| static int _dtb_read(struct mmc *mmc, u64 blk, u64 cnt, void * addr) |
| { |
| int dev = EMMC_DTB_DEV; |
| u64 n; |
| n = mmc->block_dev.block_read(dev, blk, cnt, addr); |
| if (n != cnt) { |
| dtb_err("%s: dev # %d, block # %#llx, count # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| } |
| |
| return (n != cnt); |
| } |
| |
| static int _dtb_write(struct mmc *mmc, u64 blk, u64 cnt, void * addr) |
| { |
| int dev = EMMC_DTB_DEV; |
| u64 n; |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| if (n != cnt) { |
| dtb_err("%s: dev # %d, block # %#llx, count # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| } |
| |
| return (n != cnt); |
| } |
| |
| static struct mmc *_dtb_init(void) |
| { |
| struct mmc *mmc = find_mmc_device(EMMC_DTB_DEV); |
| if (!mmc) { |
| dtb_err("not find mmc\n"); |
| return NULL; |
| } |
| |
| if (mmc_init(mmc)) { |
| dtb_err("mmc init failed\n"); |
| return NULL; |
| } |
| return mmc; |
| } |
| |
| static int dtb_read_shortcut(struct mmc * mmc, void *addr) |
| { |
| u64 blk, cnt, dtb_glb_offset; |
| int dev = EMMC_DTB_DEV; |
| struct aml_dtb_info *info = &dtb_infos; |
| struct partitions * part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| dtb_glb_offset = part->offset + vpart->offset; |
| /* short cut */ |
| if (info->valid[0]) { |
| dtb_info("short cut in...\n"); |
| blk = dtb_glb_offset / mmc->read_bl_len; |
| cnt = vpart->size / mmc->read_bl_len; |
| if (_dtb_read(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| /*try dtb2 if it's valid */ |
| if (info->valid[1]) { |
| blk = (dtb_glb_offset + vpart->size) / mmc->read_bl_len; |
| cnt = vpart->size / mmc->read_bl_len; |
| if (_dtb_read(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx, cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| return -1; |
| } |
| } |
| } |
| return 0; |
| } |
| return -2; |
| } |
| |
| static int update_dtb_info(struct mmc *mmc, void *addr) |
| { |
| int ret = 0, dev = EMMC_DTB_DEV; |
| u64 blk, cnt, dtb_glb_offset; |
| struct aml_dtb_rsv * dtb = (struct aml_dtb_rsv *) addr; |
| struct aml_dtb_info *info = &dtb_infos; |
| int cpy = 1, valid = 0; |
| struct partitions * part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| dtb_glb_offset = part->offset + vpart->offset; |
| |
| while (cpy >= 0) { |
| blk = (dtb_glb_offset + cpy * (vpart->size)) / mmc->read_bl_len; |
| cnt = vpart->size / mmc->read_bl_len; |
| ret = _dtb_read(mmc, blk, cnt, addr); |
| if (ret) { |
| dtb_err("%s: dev # %d, block # %#llx, cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| } else { |
| ret = _verify_dtb_checksum(dtb); |
| /* check magic avoid whole 0 issue */ |
| if (!ret && (dtb->magic != 0)) { |
| info->stamp[cpy] = dtb->timestamp; |
| info->valid[cpy] = 1; |
| } |
| else |
| dtb_wrn("cpy %d is not valid\n", cpy); |
| } |
| valid += info->valid[cpy]; |
| cpy --; |
| } |
| return valid; |
| } |
| |
| static int update_invalid_dtb(struct mmc *mmc, void *addr) |
| { |
| int ret = 0, dev = EMMC_DTB_DEV; |
| u64 blk, cnt, dtb_glb_offset; |
| struct aml_dtb_rsv * dtb = (struct aml_dtb_rsv *) addr; |
| struct aml_dtb_info *info = &dtb_infos; |
| struct partitions * part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| dtb_glb_offset = part->offset + vpart->offset; |
| cnt = vpart->size / mmc->read_bl_len; |
| |
| if (info->valid[1]) { |
| blk = (dtb_glb_offset + vpart->size) / mmc->read_bl_len; |
| if (_dtb_read(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -2; |
| } |
| /* fixme, update the invalid one - dtb1 */ |
| blk = (dtb_glb_offset) / mmc->read_bl_len; |
| if (_dtb_write(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -4; |
| } |
| info->valid[0] = 1; |
| info->stamp[0] = dtb->timestamp; |
| } else { |
| dtb_info("update dtb2"); |
| blk = (dtb_glb_offset + vpart->size) / mmc->read_bl_len; |
| if (_dtb_write(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -2; |
| } |
| info->valid[1] = 1; |
| info->stamp[1] = dtb->timestamp; |
| } |
| return ret; |
| } |
| |
| int update_old_dtb(struct mmc *mmc, void *addr) |
| { |
| int ret = 0, dev = EMMC_DTB_DEV; |
| u64 blk, cnt, dtb_glb_offset; |
| struct aml_dtb_rsv * dtb = (struct aml_dtb_rsv *) addr; |
| struct aml_dtb_info *info = &dtb_infos; |
| struct partitions * part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| dtb_glb_offset = part->offset + vpart->offset; |
| cnt = vpart->size / mmc->read_bl_len; |
| if (stamp_after(info->stamp[1], info->stamp[0])) { |
| blk = (dtb_glb_offset + vpart->size) / mmc->read_bl_len; |
| if (_dtb_read(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -3; |
| } |
| /*update dtb1*/ |
| blk = dtb_glb_offset / mmc->read_bl_len; |
| if (_dtb_write(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -3; |
| } |
| info->stamp[0] = dtb->timestamp; |
| } else if (stamp_after(info->stamp[0], info->stamp[1])) { |
| /*update dtb2*/ |
| blk = (dtb_glb_offset + vpart->size) / mmc->read_bl_len; |
| if (_dtb_write(mmc, blk, cnt, addr)) { |
| dtb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| ret = -3; |
| } |
| info->stamp[1] = dtb->timestamp; |
| } else { |
| dtb_info("do nothing\n"); |
| } |
| return ret; |
| } |
| |
| int dtb_read(void *addr) |
| { |
| int ret = 0; |
| int valid = 0; |
| struct mmc *mmc; |
| |
| mmc = _dtb_init(); |
| if (mmc == NULL) |
| return -10; |
| |
| if (dtb_read_shortcut(mmc, addr) == 0) |
| return ret; |
| |
| valid = update_dtb_info(mmc, addr); |
| dtb_info("total valid %d\n", valid); |
| /* check valid */ |
| switch (valid) { |
| /* none is valid, using the 1st one for compatibility*/ |
| case 0: |
| ret = -1; |
| goto _out; |
| break; |
| /* only 1 is valid, using the valid one */ |
| case 1: |
| update_invalid_dtb(mmc, addr); |
| break; |
| /* both are valid, pickup new one. */ |
| case 2: |
| update_old_dtb(mmc, addr); |
| break; |
| default: |
| dtb_err("impossble valid values.\n"); |
| BUG(); |
| break; |
| } |
| _out: |
| return ret; |
| } |
| |
| |
| int dtb_write(void *addr) |
| { |
| int ret = 0; |
| struct aml_dtb_rsv * dtb = (struct aml_dtb_rsv *) addr; |
| struct aml_dtb_info *info = &dtb_infos; |
| u64 blk, cnt, dtb_glb_offset; |
| int cpy, valid; |
| struct mmc * mmc; |
| struct partitions * part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| dtb_glb_offset = part->offset + vpart->offset; |
| |
| mmc = _dtb_init(); |
| if (NULL == mmc) |
| return -10; |
| |
| /* stamp */ |
| valid = info->valid[0] + info->valid[1]; |
| dtb_info("valid %d\n", valid); |
| if (0 == valid) |
| dtb->timestamp = 0; |
| else if (1 == valid) { |
| dtb->timestamp = 1 + info->stamp[info->valid[0]?0:1]; |
| } else { |
| /* both are valid */ |
| if (info->stamp[0] != info->stamp[1]) { |
| dtb_wrn("timestamp are not same %d:%d\n", |
| info->stamp[0], info->stamp[1]); |
| dtb->timestamp = 1 + ((info->stamp[1] > info->stamp[0]) ? |
| info->stamp[1]:info->stamp[0]); |
| } else |
| dtb->timestamp = 1 + info->stamp[0]; |
| } |
| /*setting version and magic*/ |
| dtb->version = 1; /* base version */ |
| dtb->magic = 0x00447e41; /*A~D\0*/ |
| dtb->checksum = _calc_dtb_checksum(dtb); |
| dtb_info("new stamp %d, checksum 0x%x, version %d, magic %s\n", |
| dtb->timestamp, dtb->checksum, dtb->version, (char *)&dtb->magic); |
| |
| for (cpy = 0; cpy < DTB_COPIES; cpy++) { |
| blk = (dtb_glb_offset + cpy * (vpart->size)) / mmc->read_bl_len; |
| cnt = vpart->size / mmc->read_bl_len; |
| ret |= _dtb_write(mmc, blk, cnt, addr); |
| info->valid[cpy] = 1; |
| info->stamp[cpy] = dtb->timestamp; |
| } |
| |
| return ret; |
| } |
| |
| extern int check_valid_dts(unsigned char *buffer, unsigned char **dts); |
| int renew_partition_tbl(unsigned char *buffer) |
| { |
| int ret = 0; |
| unsigned char *temp = NULL; |
| |
| if (!buffer) |
| return 1; |
| temp = malloc (AML_DTB_IMG_MAX_SZ); |
| if (!temp) |
| return 1; |
| memcpy(temp, buffer, AML_DTB_IMG_MAX_SZ); |
| /* todo, check new dts imcoming.... */ |
| ret = check_valid_dts(temp, NULL); |
| free(temp); |
| |
| /* only the dts new is valid */ |
| if (!ret) { |
| free_partitions(); |
| ret = get_partition_from_dts(buffer); |
| if (ret == -1) |
| goto _out; |
| |
| if (0 == mmc_device_init(_dtb_init())) { |
| printf("partition table success\n"); |
| ret = 0; |
| goto _out; |
| } |
| printf("partition table error\n"); |
| ret = 1; |
| } |
| |
| _out: |
| return ret; |
| } |
| |
| int do_amlmmc_dtb_key(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int dev, ret = 0; |
| void *addr = NULL; |
| u64 cnt = 0, n = 0, blk = 0; |
| //u64 size; |
| struct partitions *part = NULL; |
| struct virtual_partition *vpart = NULL; |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| |
| switch (argc) { |
| case 3: |
| if (strcmp(argv[1], "erase") == 0) { |
| if (strcmp(argv[2], "dtb") == 0) { |
| printf("start erase dtb......\n"); |
| dev = EMMC_DTB_DEV; |
| struct mmc *mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("not find mmc\n"); |
| return 1; |
| } |
| blk = (part->offset + vpart->offset) / mmc->read_bl_len; |
| cnt = (vpart->size * 2) / mmc->read_bl_len; |
| if (cnt != 0) |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, (flag == 0) ? " ":(argv[2]),(n == 0) ? "OK" : "ERROR"); |
| return (n == 0) ? 0 : 1; |
| }else if (strcmp(argv[2], "key") == 0){ |
| printf("start erase key......\n"); |
| dev = 1; |
| struct mmc *mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("not find mmc\n"); |
| return 1; |
| } |
| n = mmc_key_erase(); |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, (flag == 0) ? " ":(argv[2]),(n == 0) ? "OK" : "ERROR"); |
| return (n == 0) ? 0 : 1; |
| } |
| } else if (strcmp(argv[1], "cali_pattern") == 0) { |
| |
| if (strcmp(argv[2], "write") == 0) { |
| dev = EMMC_DTB_DEV; |
| struct mmc *mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("not find mmc\n"); |
| return 1; |
| } |
| vpart = aml_get_virtual_partition_by_name(MMC_PATTERN_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| addr = (void *)malloc(vpart->size); |
| if (addr == NULL) { |
| printf("cali_pattern malloc fail\n"); |
| return 1; |
| } |
| mmc_write_cali_mattern(addr); |
| blk = (part->offset + vpart->offset) / mmc->read_bl_len; |
| cnt = vpart->size / mmc->read_bl_len; |
| if (cnt != 0) |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| printf("dev # %d, %s, several calibration pattern blocks write %s\n", |
| dev, (flag == 0) ? " ":(argv[2]),(n == cnt) ? "OK" : "ERROR"); |
| free(addr); |
| return (n == cnt) ? 0 : 1; |
| } |
| } |
| return 0; |
| case 4: |
| addr = (void *)simple_strtoul(argv[2], NULL, 16); |
| if (strcmp(argv[1], "dtb_read") == 0) { |
| /* fixme, */ |
| ret = dtb_read(addr); |
| return 0; |
| |
| } else if (strcmp(argv[1], "dtb_write") == 0) { |
| /* fixme, should we check the return value? */ |
| ret = dtb_write(addr); |
| ret |= renew_partition_tbl(addr); |
| return ret; |
| } |
| return 0; |
| default: |
| break; |
| } |
| return 1; |
| } |
| |
| /* update partition table in reserved partition. */ |
| __weak int emmc_update_ept(unsigned char *buffer) |
| { |
| int ret = 0; |
| |
| #ifndef DTB_BIND_KERNEL |
| dtb_write(buffer); |
| #endif |
| ret = renew_partition_tbl(buffer); |
| return ret; |
| } |
| |
| /* fixme, should use renew_partition_tbl here! */ |
| __weak int emmc_update_mbr(unsigned char *buffer) |
| { |
| int ret = 0; |
| cpu_id_t cpu_id = get_cpu_id(); |
| |
| if (cpu_id.family_id < MESON_CPU_MAJOR_ID_GXL) { |
| ret = -1; |
| printf("MBR not support, try dtb\n"); |
| goto _out; |
| } |
| #ifndef DTB_BIND_KERNEL |
| dtb_write(buffer); |
| #endif |
| ret = get_partition_from_dts(buffer); |
| if (ret) { |
| printf("Fail to get partition talbe from dts\n"); |
| goto _out; |
| } |
| ret = mmc_device_init(_dtb_init()); |
| printf("%s: update mbr %s\n", __func__, ret?"Fail":"Success"); |
| _out: |
| return ret; |
| } |
| |
| int do_emmc_erase(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int dev; |
| u64 cnt = 0, n = 0, blk = 0; |
| //u64 size; |
| struct partitions *part = NULL; |
| struct virtual_partition *vpart = NULL; |
| struct mmc *mmc; |
| if (argc != 3) |
| return CMD_RET_USAGE; |
| |
| vpart = aml_get_virtual_partition_by_name(MMC_DTB_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| if (strcmp(argv[2], "dtb") == 0) { |
| printf("start erase dtb......\n"); |
| dev = EMMC_DTB_DEV; |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("not find mmc\n"); |
| return 1; |
| } |
| blk = (part->offset + vpart->offset) / mmc->read_bl_len; |
| cnt = (vpart->size * 2) / mmc->read_bl_len; |
| if (cnt != 0) |
| n = mmc->block_dev.block_erase(dev, blk, cnt); |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, (flag == 0) ? " ":(argv[2]),(n == 0) ? "OK" : "ERROR"); |
| return (n == 0) ? 0 : 1; |
| } else if (strcmp(argv[2], "key") == 0) { |
| printf("start erase key......\n"); |
| dev = 1; |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| printf("not find mmc\n"); |
| return 1; |
| } |
| n = mmc_key_erase(); |
| printf("dev # %d, %s, several blocks erased %s\n", |
| dev, (flag == 0) ? " ":(argv[2]),(n == 0) ? "OK" : "ERROR"); |
| return (n == 0) ? 0 : 1; |
| } |
| return 1; |
| } |
| |
| int do_emmc_dtb_read(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int ret = 0; |
| void *addr = NULL; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| addr = (void *)simple_strtoul(argv[2], NULL, 16); |
| ret = dtb_read(addr); |
| return ret; |
| } |
| |
| int do_emmc_dtb_write(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int ret = 0; |
| void *addr = NULL; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| addr = (void *)simple_strtoul(argv[2], NULL, 16); |
| ret = dtb_write(addr); |
| ret |= renew_partition_tbl(addr); |
| return ret; |
| } |
| |
| static int _fastboot_context_read(struct mmc *mmc, u64 blk, |
| u64 cnt, void *addr) |
| { |
| int dev = EMMC_FASTBOOT_CONTEXT_DEV; |
| u64 n; |
| |
| n = mmc->block_dev.block_read(dev, blk, cnt, addr); |
| if (n != cnt) { |
| fb_err("%s: dev # %d, block # %#llx, count # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| } |
| |
| return n != cnt; |
| } |
| |
| int fastboot_context_read(void *buf, size_t size) |
| { |
| uint32_t crc_result; |
| struct mmc *mmc; |
| struct FastbootContext *fb_cont; |
| int fb_size = sizeof(struct FastbootContext); |
| u64 blk, cnt, fb_glb_offset; |
| int dev = EMMC_FASTBOOT_CONTEXT_DEV; |
| struct partitions *part = NULL; |
| struct virtual_partition *vpart = NULL; |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| if (mmc_init(mmc)) { |
| printf("%s() %d: emmc init failed\n", __func__, __LINE__); |
| return 1; |
| } |
| |
| vpart = aml_get_virtual_partition_by_name(MMC_FASTBOOT_CONTEXT_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| fb_glb_offset = part->offset + vpart->offset; |
| |
| blk = fb_glb_offset / mmc->read_bl_len; |
| cnt = size / mmc->read_bl_len; |
| |
| if (_fastboot_context_read(mmc, blk, cnt, buf)) |
| return 1; |
| |
| fb_cont = (struct FastbootContext *)buf; |
| crc_result = crc32(0, buf, fb_size - 4); |
| |
| if (crc_result != fb_cont->crc32) { |
| printf("%s %d: crc checksum ERROR!\n", __func__, __LINE__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| int do_emmc_fb_read(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int ret = 0; |
| void *addr = NULL; |
| u64 size; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| addr = (void *)simple_strtoul(argv[2], NULL, 16); |
| size = simple_strtoull(argv[3], NULL, 16); |
| ret = fastboot_context_read(addr, size); |
| return ret; |
| } |
| |
| static int _fastboot_context_write(struct mmc *mmc, u64 blk, |
| u64 cnt, void *addr) |
| { |
| int dev = EMMC_FASTBOOT_CONTEXT_DEV; |
| int n; |
| |
| n = mmc->block_dev.block_write(dev, blk, cnt, addr); |
| |
| if (n != cnt) { |
| fb_err("%s: dev # %d, block # %#llx,cnt # %#llx ERROR!\n", |
| __func__, dev, blk, cnt); |
| } |
| |
| return n != cnt; |
| } |
| |
| int fastboot_context_write(void *buf, size_t size) |
| { |
| int ret = 0; |
| struct FastbootContext *fb_cont = (struct FastbootContext *)buf; |
| u64 blk, cnt, fb_glb_offset; |
| struct mmc *mmc; |
| struct partitions *part = NULL; |
| struct virtual_partition *vpart = NULL; |
| int dev = EMMC_FASTBOOT_CONTEXT_DEV; |
| int fb_size = sizeof(struct FastbootContext); |
| |
| mmc = find_mmc_device(dev); |
| if (!mmc) { |
| puts("no mmc devices available\n"); |
| return 1; |
| } |
| |
| ret = mmc_init(mmc); |
| if (ret) { |
| printf("%s() %d: emmc init failed\n", __func__, __LINE__); |
| return 1; |
| } |
| |
| vpart = aml_get_virtual_partition_by_name(MMC_FASTBOOT_CONTEXT_NAME); |
| part = aml_get_partition_by_name(MMC_RESERVED_NAME); |
| fb_glb_offset = part->offset + vpart->offset; |
| fb_cont->crc32 = crc32(0, buf, fb_size - 4); |
| blk = fb_glb_offset / mmc->read_bl_len; |
| cnt = size / mmc->read_bl_len; |
| ret = _fastboot_context_write(mmc, blk, cnt, buf); |
| |
| return ret; |
| } |
| |
| int do_emmc_fb_write(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| int ret = 0; |
| void *addr = NULL; |
| u64 size; |
| |
| if (argc != 4) |
| return CMD_RET_USAGE; |
| |
| addr = (void *)simple_strtoul(argv[2], NULL, 16); |
| size = simple_strtoull(argv[3], NULL, 16); |
| ret = fastboot_context_write(addr, size); |
| return ret; |
| } |
| |
| |
| static cmd_tbl_t cmd_emmc[] = { |
| U_BOOT_CMD_MKENT(dtb_read, 4, 0, do_emmc_dtb_read, "", ""), |
| U_BOOT_CMD_MKENT(dtb_write, 4, 0, do_emmc_dtb_write, "", ""), |
| U_BOOT_CMD_MKENT(erase, 3, 0, do_emmc_erase, "", ""), |
| U_BOOT_CMD_MKENT(fastboot_read, 4, 0, do_emmc_fb_read, "", ""), |
| U_BOOT_CMD_MKENT(fastboot_write, 4, 0, do_emmc_fb_write, "", ""), |
| }; |
| |
| static int do_emmc_dtb_key(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| cmd_tbl_t *cp; |
| |
| cp = find_cmd_tbl(argv[1], cmd_emmc, ARRAY_SIZE(cmd_emmc)); |
| |
| if (cp == NULL || argc > cp->maxargs) |
| return CMD_RET_USAGE; |
| if (flag == CMD_FLAG_REPEAT && !cp->repeatable) |
| return CMD_RET_SUCCESS; |
| return cp->cmd(cmdtp, flag, argc, argv); |
| } |
| |
| |
| U_BOOT_CMD( |
| emmc, 4, 1, do_emmc_dtb_key, |
| "EMMC sub system", |
| "dtb_read addr size\n" |
| "emmc dtb_write addr size\n" |
| "emmc erase dtb\n" |
| "emmc erase key\n" |
| "emmc fastboot_read addr size\n" |
| "emmc fastboot_write addr size\n"); |
| |