| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| #include "mmc.h" |
| #include "services.h" |
| #include <string.h> |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #define DBG_INFO "info:" |
| |
| //#define DEBUG |
| #undef DEBUG |
| #ifdef DEBUG |
| #define D(x, ...) printf(__VA_ARGS__) |
| #else |
| #define D(...) do{}while(0) |
| #endif |
| |
| #define CSD_VERSION_1 0 |
| #define CSD_VERSION_2_AND_3 1 |
| |
| struct mmc_completion_token { |
| struct mmc_card *card; |
| mmc_cb cb; |
| void *token; |
| }; |
| |
| |
| static uint32_t slice_bits(uint32_t *val, int start, int size) |
| { |
| int idx; |
| int high, low; |
| uint32_t ret = 0; |
| |
| /* Can not return more than 32 bits. */ |
| assert(size <= 32); |
| |
| idx = start / 32; |
| low = start % 32; |
| high = (start + size) % 32; |
| |
| if (high == 0 && low == 0) { |
| ret = val[idx]; |
| } else if (high == 0 && low != 0) { |
| ret = val[idx] >> low; |
| } else { |
| if (high > low) { |
| ret = val[idx] & ((1U << high) - 1); |
| ret = ret >> low; |
| } else { |
| ret = val[idx] >> low; |
| ret |= (val[idx + 1] & ((1U << high) - 1)) << (32 - low); |
| } |
| |
| } |
| |
| return ret; |
| } |
| |
| #if 0 /* Commenting this out as it appears unused. */ |
| static int mmc_decode_cid(mmc_card_t mmc_card, struct cid *cid) |
| { |
| if (mmc_card == NULL || cid == NULL) { |
| return -1; |
| } |
| |
| if (mmc_card->type == CARD_TYPE_SD) { |
| cid->manfid = slice_bits(mmc_card->raw_cid, 120, 8); |
| cid->sd_cid.oemid = slice_bits(mmc_card->raw_cid, 104, 16); |
| cid->sd_cid.name[0] = slice_bits(mmc_card->raw_cid, 96, 8); |
| cid->sd_cid.name[1] = slice_bits(mmc_card->raw_cid, 88, 8); |
| cid->sd_cid.name[2] = slice_bits(mmc_card->raw_cid, 80, 8); |
| cid->sd_cid.name[3] = slice_bits(mmc_card->raw_cid, 72, 8); |
| cid->sd_cid.name[4] = slice_bits(mmc_card->raw_cid, 64, 8); |
| cid->sd_cid.rev = slice_bits(mmc_card->raw_cid, 56, 8); |
| cid->sd_cid.serial = slice_bits(mmc_card->raw_cid, 24, 32); |
| cid->sd_cid.date = slice_bits(mmc_card->raw_cid, 8, 12); |
| |
| printf("manfid(%x), oemid(%x), name(%c%c%c%c%c), rev(%x), serial(%x), date(%x)\n", |
| cid->manfid, cid->sd_cid.oemid, |
| cid->sd_cid.name[0], cid->sd_cid.name[1], cid->sd_cid.name[2], |
| cid->sd_cid.name[3], cid->sd_cid.name[4], |
| cid->sd_cid.rev, cid->sd_cid.serial, cid->sd_cid.date); |
| } else { |
| printf("Not Implemented!\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int mmc_decode_csd(mmc_card_t mmc_card, struct csd *csd) |
| { |
| if (mmc_card == NULL || csd == NULL) { |
| return -1; |
| } |
| |
| #define CSD_BITS(start, size) \ |
| slice_bits(mmc_card->raw_csd, start, size) |
| |
| csd->structure = CSD_BITS(126, 2); |
| |
| if (csd->structure == CSD_VERSION_1) { |
| printf("CSD Version 1.0\n"); |
| csd->c_size = CSD_BITS(62, 12); |
| csd->c_size_mult = CSD_BITS(47, 3); |
| csd->read_bl_len = CSD_BITS(80, 4); |
| csd->tran_speed = CSD_BITS(96, 8); |
| } else if (csd->structure == CSD_VERSION_2_AND_3) { |
| printf("CSD Version 2.0\n"); |
| csd->c_size = CSD_BITS(48, 22); |
| csd->c_size_mult = 0; |
| csd->read_bl_len = CSD_BITS(80, 4); |
| csd->tran_speed = CSD_BITS(96, 8); |
| } else { |
| printf("Unknown CSD version!\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static struct mmc_cmd *mmc_cmd_new(uint32_t index, uint32_t arg, int rsp_type) |
| { |
| struct mmc_cmd *cmd; |
| cmd = malloc(sizeof(*cmd)); |
| if (cmd) { |
| /* Command */ |
| cmd->index = index; |
| cmd->arg = arg; |
| cmd->rsp_type = rsp_type; |
| cmd->data = NULL; |
| /* Transaction maintenance */ |
| cmd->cb = NULL; |
| cmd->token = NULL; |
| cmd->next = NULL; |
| cmd->complete = 0; |
| } |
| return cmd; |
| } |
| |
| static int mmc_cmd_add_data(struct mmc_cmd *cmd, void *vbuf, uintptr_t pbuf, uint32_t addr, |
| uint32_t block_size, uint32_t blocks) |
| { |
| struct mmc_data *d; |
| assert(cmd->data == NULL); |
| d = (struct mmc_data *)malloc(sizeof(*d)); |
| if (d) { |
| d->pbuf = pbuf; |
| d->vbuf = vbuf; |
| d->data_addr = addr; |
| d->block_size = block_size; |
| d->blocks = blocks; |
| cmd->data = d; |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| static void mmc_cmd_destroy(struct mmc_cmd *cmd) |
| { |
| if (cmd->data) { |
| free(cmd->data); |
| } |
| free(cmd); |
| } |
| |
| static struct mmc_completion_token *mmc_new_completion_token(mmc_card_t mmc_card, mmc_cb cb, void *token) |
| { |
| struct mmc_completion_token *t; |
| t = (struct mmc_completion_token *)malloc(sizeof(*t)); |
| if (t) { |
| t->card = mmc_card; |
| t->cb = cb; |
| t->token = token; |
| } |
| return t; |
| } |
| |
| static void mmc_completion_token_destroy(struct mmc_completion_token *t) |
| { |
| free(t); |
| } |
| |
| /** |
| * MMC/SD/SDIO card registry. |
| */ |
| static int mmc_card_registry(mmc_card_t card) |
| { |
| struct mmc_cmd cmd = {.data = NULL}; |
| int ret; |
| |
| D(DBG_INFO, "\n"); |
| |
| /* Get card ID */ |
| cmd.index = MMC_ALL_SEND_CID; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R2; |
| ret = host_send_command(card, &cmd, NULL, NULL); |
| if (ret) { |
| D(DBG_ERR, "No response!\n"); |
| card->status = CARD_STS_INACTIVE; |
| return -1; |
| } else { |
| card->status = CARD_STS_ACTIVE; |
| } |
| |
| /* Left shift the response by 8. Consult SDHC manual. */ |
| cmd.response[3] = ((cmd.response[3] << 8) | (cmd.response[2] >> 24)); |
| cmd.response[2] = ((cmd.response[2] << 8) | (cmd.response[1] >> 24)); |
| cmd.response[1] = ((cmd.response[1] << 8) | (cmd.response[0] >> 24)); |
| cmd.response[0] = (cmd.response[0] << 8); |
| memcpy(card->raw_cid, cmd.response, sizeof(card->raw_cid)); |
| |
| |
| /* Retrieve RCA number. */ |
| cmd.index = MMC_SEND_RELATIVE_ADDR; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R6; |
| host_send_command(card, &cmd, NULL, NULL); |
| card->raw_rca = (cmd.response[0] >> 16); |
| D(DBG_INFO, "New Card RCA: %x\n", card->raw_rca); |
| |
| /* Read CSD, Status */ |
| cmd.index = MMC_SEND_CSD; |
| cmd.arg = card->raw_rca << 16; |
| cmd.rsp_type = MMC_RSP_TYPE_R2; |
| host_send_command(card, &cmd, NULL, NULL); |
| |
| /* Left shift the response by 8. Consult SDHC manual. */ |
| cmd.response[3] = ((cmd.response[3] << 8) | (cmd.response[2] >> 24)); |
| cmd.response[2] = ((cmd.response[2] << 8) | (cmd.response[1] >> 24)); |
| cmd.response[1] = ((cmd.response[1] << 8) | (cmd.response[0] >> 24)); |
| cmd.response[0] = (cmd.response[0] << 8); |
| memcpy(card->raw_csd, cmd.response, sizeof(card->raw_csd)); |
| |
| cmd.index = MMC_SEND_STATUS; |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| host_send_command(card, &cmd, NULL, NULL); |
| |
| /* Select the card */ |
| cmd.index = MMC_SELECT_CARD; |
| cmd.arg = card->raw_rca << 16; |
| cmd.rsp_type = MMC_RSP_TYPE_R1b; |
| host_send_command(card, &cmd, NULL, NULL); |
| |
| /* Set Bus width */ |
| cmd.index = MMC_APP_CMD; |
| cmd.arg = card->raw_rca << 16; |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| host_send_command(card, &cmd, NULL, NULL); |
| cmd.index = SD_SET_BUS_WIDTH; |
| host_send_command(card, &cmd, NULL, NULL); |
| |
| /* Set read/write block length for byte addressed standard capacity cards */ |
| if (!card->high_capacity) { |
| cmd.index = MMC_SET_BLOCKLEN; |
| cmd.arg = mmc_block_size(card); |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| ret = host_send_command(card, &cmd, NULL, NULL); |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Card voltage validation. |
| */ |
| static int mmc_voltage_validation(mmc_card_t card) |
| { |
| struct mmc_cmd cmd = {.data = NULL}; |
| int voltage; |
| int ret; |
| |
| /* Send CMD55 to issue an application specific command. */ |
| cmd.index = MMC_APP_CMD; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| ret = host_send_command(card, &cmd, NULL, NULL); |
| if (!ret) { |
| /* It is a SD card. */ |
| cmd.index = SD_SD_APP_OP_COND; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R3; |
| card->type = CARD_TYPE_SD; |
| } else { |
| /* It is a MMC card. */ |
| cmd.index = MMC_SEND_OP_COND; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R3; |
| card->type = CARD_TYPE_MMC; |
| } |
| ret = host_send_command(card, &cmd, NULL, NULL); |
| if (ret) { |
| card->type = CARD_TYPE_UNKNOWN; |
| /* TODO: Be nicer */ |
| assert(0); |
| } |
| card->ocr = cmd.response[0]; |
| |
| /* TODO: Check uSDHC compatibility */ |
| voltage = MMC_VDD_29_30 | MMC_VDD_30_31; |
| if (host_is_voltage_compatible(card, 3300) && (card->ocr & voltage)) { |
| /* Voltage compatible */ |
| voltage |= (1 << 30); |
| voltage |= (1 << 25); |
| voltage |= (1 << 24); |
| } |
| |
| /* Wait until the voltage level is set. */ |
| do { |
| if (card->type == CARD_TYPE_SD) { |
| cmd.index = MMC_APP_CMD; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| host_send_command(card, &cmd, NULL, NULL); |
| } |
| |
| cmd.index = SD_SD_APP_OP_COND; |
| cmd.arg = voltage; |
| cmd.rsp_type = MMC_RSP_TYPE_R3; |
| host_send_command(card, &cmd, NULL, NULL); |
| udelay(100000); |
| } while (!(cmd.response[0] & (1U << 31))); |
| card->ocr = cmd.response[0]; |
| |
| /* Check CCS bit */ |
| if (card->ocr & (1 << 30)) { |
| card->high_capacity = 1; |
| } else { |
| card->high_capacity = 0; |
| } |
| |
| D(DBG_INFO, "Voltage set!\n"); |
| return 0; |
| } |
| |
| |
| static int mmc_reset(mmc_card_t card) |
| { |
| /* Reset the card with CMD0 */ |
| struct mmc_cmd cmd = {.data = NULL}; |
| cmd.index = MMC_GO_IDLE_STATE; |
| cmd.arg = 0; |
| cmd.rsp_type = MMC_RSP_TYPE_NONE; |
| host_send_command(card, &cmd, NULL, NULL); |
| |
| /* TODO: review this command. */ |
| cmd.index = MMC_SEND_EXT_CSD; |
| cmd.arg = 0x1AA; |
| cmd.rsp_type = MMC_RSP_TYPE_R1; |
| host_send_command(card, &cmd, NULL, NULL); |
| return 0; |
| } |
| |
| static void mmc_blockop_completion_cb(struct sdio_host_dev *sdio, int stat, struct mmc_cmd *cmd, |
| void *token) |
| { |
| struct mmc_completion_token *t; |
| size_t bytes; |
| |
| t = (struct mmc_completion_token *)token; |
| if (stat == 0) { |
| bytes = cmd->data->block_size * cmd->data->blocks; |
| } else { |
| bytes = 0; |
| } |
| /* Call the registered function */ |
| t->cb(t->card, stat, bytes, t->token); |
| /* Free memory */ |
| mmc_cmd_destroy(cmd); |
| mmc_completion_token_destroy(t); |
| } |
| |
| int mmc_init(sdio_host_dev_t *sdio, ps_io_ops_t *io_ops, mmc_card_t *mmc_card) |
| { |
| mmc_card_t mmc; |
| |
| /* Allocate the mmc card structure */ |
| mmc = (mmc_card_t)malloc(sizeof(*mmc)); |
| assert(mmc); |
| if (!mmc) { |
| return -1; |
| } |
| mmc->dalloc = &io_ops->dma_manager; |
| mmc->sdio = sdio; |
| /* Reset the host controller */ |
| if (host_reset(mmc)) { |
| LOG_ERROR("Failed to reset host controller\n"); |
| free(mmc); |
| return -1; |
| } |
| /* Initialise the card */ |
| if (mmc_reset(mmc)) { |
| LOG_ERROR("Failed to reset SD/MMC card\n"); |
| free(mmc); |
| return -1; |
| } |
| if (mmc_voltage_validation(mmc)) { |
| LOG_ERROR("Failed to perform voltage validation\n"); |
| free(mmc); |
| return -1; |
| } |
| /* Register the card */ |
| if (mmc_card_registry(mmc)) { |
| LOG_ERROR("Failed to register card\n"); |
| free(mmc); |
| return -1; |
| } |
| |
| *mmc_card = mmc; |
| assert(mmc); |
| return 0; |
| } |
| |
| static |
| long transfer_data( |
| mmc_card_t mmc_card, |
| unsigned long start, |
| int nblocks, |
| void *vbuf, |
| uintptr_t pbuf, |
| mmc_cb cb, |
| void *token, |
| uint32_t command) |
| { |
| struct mmc_cmd *cmd; |
| const int block_size = mmc_block_size(mmc_card); |
| |
| /* Determine command argument */ |
| const uint32_t arg = (mmc_card->high_capacity) |
| ? start |
| : (start * block_size); |
| |
| /* Allocate command structure |
| * |
| * Please note that `cmd` is dynamically allocated, so it must be destroyed. |
| * |
| * Clean up will be done prior to exiting this function OR in the |
| * `mmc_blockop_completion_cb` if callback was given. |
| * |
| * In case of an unexpected error, there will be a jump to |
| * `exit_transfer_data` label, so that memory leak can be avoided. |
| */ |
| cmd = mmc_cmd_new(command, arg, MMC_RSP_TYPE_R1); |
| if (cmd == NULL) { |
| // `cmd` was NOT allocated, so we are exiting without destroying it. |
| return -1; |
| } |
| |
| long ret = -1; |
| struct mmc_completion_token *mmc_token = NULL; |
| |
| /* Add a data segment */ |
| ret = mmc_cmd_add_data(cmd, vbuf, pbuf, start, block_size, nblocks); |
| if (ret < 0) { |
| goto exit_transfer_data; |
| } |
| |
| if (cb) { |
| mmc_token = mmc_new_completion_token(mmc_card, cb, token); |
| |
| if (NULL == mmc_token) { |
| ret = -1; |
| goto exit_transfer_data; |
| } |
| } |
| |
| ret = host_send_command( |
| mmc_card, |
| cmd, |
| cb ? &mmc_blockop_completion_cb : NULL, |
| cb ? mmc_token : NULL); |
| |
| exit_transfer_data: |
| ; |
| const bool is_success = (0 == ret); |
| |
| // Clean up usually will happen during the callback, so we only clean up |
| // here if no callback was given or failure has been encountered. |
| if (!cb || !is_success) { |
| if (mmc_token) { |
| mmc_completion_token_destroy(mmc_token); |
| } |
| if (cmd) { |
| mmc_cmd_destroy(cmd); |
| } |
| } |
| |
| const size_t bytes_transferred = cb ? 0 : (block_size * nblocks); |
| return is_success ? bytes_transferred : ret; |
| } |
| |
| long mmc_block_read(mmc_card_t mmc_card, unsigned long start, |
| int nblocks, void *vbuf, uintptr_t pbuf, mmc_cb cb, void *token) |
| { |
| return transfer_data( |
| mmc_card, |
| start, |
| nblocks, |
| vbuf, |
| pbuf, |
| cb, |
| token, |
| MMC_READ_SINGLE_BLOCK); |
| } |
| |
| long mmc_block_write(mmc_card_t mmc_card, unsigned long start, int nblocks, |
| const void *vbuf, uintptr_t pbuf, mmc_cb cb, void *token) |
| { |
| // vbuf's `const` gets dropped during the cast as the underlying layer |
| // accepts only non-const buffer, however it is ok, as we are sending the |
| // write command, what quarantees that the buffer won't be overwritten. |
| return transfer_data( |
| mmc_card, |
| start, |
| nblocks, |
| (void *)vbuf, |
| pbuf, |
| cb, |
| token, |
| MMC_WRITE_BLOCK); |
| } |
| |
| long long mmc_card_capacity(mmc_card_t mmc_card) |
| { |
| int ret; |
| struct csd csd; |
| |
| ret = mmc_decode_csd(mmc_card, &csd); |
| if (ret) { |
| return -1; |
| } |
| |
| long long c_size = (long long)csd.c_size; |
| switch (csd.structure) { |
| case CSD_VERSION_1: { |
| return (c_size + 1) * (1U << (csd.c_size_mult + 2)) |
| * (1U << csd.read_bl_len); |
| } |
| case CSD_VERSION_2_AND_3: { |
| return (c_size + 1) * 512 * 1024; |
| } |
| default: { |
| return -1; |
| } |
| } |
| } |
| |
| |
| int mmc_nth_irq(mmc_card_t mmc, int n) |
| { |
| return host_nth_irq(mmc, n); |
| } |
| |
| int mmc_handle_irq(mmc_card_t mmc, int irq) |
| { |
| return host_handle_irq(mmc, irq); |
| } |
| |
| |