/* kernel headers */ #include #include #include #include /* usr headers */ #include #include #include #include #include #include #include /* local headers */ #include "mmclog.h" #include "mmchost.h" /* header imported from netbsd */ #include "sdmmcreg.h" #include "sdmmcreg.h" #include "sdhcreg.h" /* omap /hardware related */ #include "omap_mmc.h" /* * Define a structure to be used for logging */ static struct mmclog log = { .name = "mmc_host_mmchs", .log_level = LEVEL_INFO, .log_func = default_log }; #define REG(x)(*((volatile uint32_t *)(x))) #define BIT(x)(0x1 << x) /* Write a uint32_t value to a memory address. */ inline void write32(uint32_t address, uint32_t value) { REG(address) = value; } /* Read an uint32_t from a memory address */ inline uint32_t read32(uint32_t address) { return REG(address); } /* Set a 32 bits value depending on a mask */ inline void set32(uint32_t address, uint32_t mask, uint32_t value) { uint32_t val; val = read32(address); /* clear the bits */ val &= ~(mask); /* apply the value using the mask */ val |= (value & mask); write32(address, val); } static uint32_t base_address; /* * Initialize the MMC controller given a certain * instance. this driver only handles a single * mmchs controller at a given time. */ int mmchs_init(uint32_t instance) { int counter; uint32_t value; counter = 0; value = 0; struct minix_mem_range mr; mr.mr_base = MMCHS1_REG_BASE; mr.mr_limit = MMCHS1_REG_BASE + 0x400; if (sys_privctl(SELF, SYS_PRIV_ADD_MEM, &mr) != 0) { panic("Unable to request permission to map memory"); } /* Set the base address to use */ base_address = (uint32_t) vm_map_phys(SELF, (void *) MMCHS1_REG_BASE, 0x400); if (base_address == (uint32_t) MAP_FAILED) panic("Unable to map MMC memory"); base_address = (unsigned long) base_address - 0x100; /* Soft reset of the controller. This section is documented in the TRM */ /* Write 1 to sysconfig[0] to trigger a reset */ set32(base_address + MMCHS_SD_SYSCONFIG, MMCHS_SD_SYSCONFIG_SOFTRESET, MMCHS_SD_SYSCONFIG_SOFTRESET); /* Read sysstatus to know when it's done */ while (!(read32(base_address + MMCHS_SD_SYSSTATUS) & MMCHS_SD_SYSSTATUS_RESETDONE)) { /* TODO:Add proper delay and escape route */ counter++; } /* Set SD default capabilities */ set32(base_address + MMCHS_SD_CAPA, MMCHS_SD_CAPA_VS_MASK, MMCHS_SD_CAPA_VS18 | MMCHS_SD_CAPA_VS30); /* TRM mentions MMCHS_SD_CUR_CAPA but does not describe how to limit * the current */ uint32_t mask = MMCHS_SD_SYSCONFIG_AUTOIDLE | MMCHS_SD_SYSCONFIG_ENAWAKEUP | MMCHS_SD_SYSCONFIG_STANDBYMODE | MMCHS_SD_SYSCONFIG_CLOCKACTIVITY | MMCHS_SD_SYSCONFIG_SIDLEMODE; /* Automatic clock gating strategy */ value = MMCHS_SD_SYSCONFIG_AUTOIDLE_EN; /* Enable wake-up capability */ value |= MMCHS_SD_SYSCONFIG_ENAWAKEUP_EN; /* Smart-idle */ value |= MMCHS_SD_SYSCONFIG_SIDLEMODE_IDLE; /* Booth the interface and functional can be switched off */ value |= MMCHS_SD_SYSCONFIG_CLOCKACTIVITY_OFF; /* Go into wake-up mode when possible */ value |= MMCHS_SD_SYSCONFIG_STANDBYMODE_WAKEUP_INTERNAL; /* * wake-up configuration */ set32(base_address + MMCHS_SD_SYSCONFIG, mask, value); /* Wake-up on sd interrupt for SDIO */ set32(base_address + MMCHS_SD_HCTL, MMCHS_SD_HCTL_IWE, MMCHS_SD_HCTL_IWE_EN); /* * MMC host and bus configuration */ /* Configure data and command transfer (1 bit mode) */ set32(base_address + MMCHS_SD_CON, MMCHS_SD_CON_DW8, MMCHS_SD_CON_DW8_1BIT); set32(base_address + MMCHS_SD_HCTL, MMCHS_SD_HCTL_DTW, MMCHS_SD_HCTL_DTW_1BIT); /* Configure card voltage to 3.0 volt */ set32(base_address + MMCHS_SD_HCTL, MMCHS_SD_HCTL_SDVS, MMCHS_SD_HCTL_SDVS_VS30); /* Power on the host controller and wait for the * MMCHS_SD_HCTL_SDBP_POWER_ON to be set */ set32(base_address + MMCHS_SD_HCTL, MMCHS_SD_HCTL_SDBP, MMCHS_SD_HCTL_SDBP_ON); /* TODO: Add padconf stuff here as documented in the TRM */ while ((read32(base_address + MMCHS_SD_HCTL) & MMCHS_SD_HCTL_SDBP) != MMCHS_SD_HCTL_SDBP_ON) { /* TODO:Add proper delay and escape route */ counter++; } /* Enable internal clock and clock to the card */ set32(base_address + MMCHS_SD_SYSCTL, MMCHS_SD_SYSCTL_ICE, MMCHS_SD_SYSCTL_ICE_EN); // @TODO Fix external clock enable , this one is very slow set32(base_address + MMCHS_SD_SYSCTL, MMCHS_SD_SYSCTL_CLKD, (0x3ff << 6)); set32(base_address + MMCHS_SD_SYSCTL, MMCHS_SD_SYSCTL_CEN, MMCHS_SD_SYSCTL_CEN_EN); counter = 0; while ((read32(base_address + MMCHS_SD_SYSCTL) & MMCHS_SD_SYSCTL_ICS) != MMCHS_SD_SYSCTL_ICS_STABLE) { /* TODO:Add proper delay and escape route */ counter++; } /* * See spruh73e page 3576 Card Detection, Identification, and Selection */ /* Enable command interrupt */ set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_CC_ENABLE, MMCHS_SD_IE_CC_ENABLE_ENABLE); /* Enable transfer complete interrupt */ set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_TC_ENABLE, MMCHS_SD_IE_TC_ENABLE_ENABLE); /* enable error interrupts */ /* NOTE: We are currently skipping the BADA interrupt it does get * raised for unknown reasons */ set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_ERROR_MASK, 0x0fffffffu); /* clean the error interrupts */ set32(base_address + MMCHS_SD_STAT, MMCHS_SD_STAT_ERROR_MASK, 0xffffffffu); /* send a init signal to the host controller. This does not actually * send a command to a card manner */ set32(base_address + MMCHS_SD_CON, MMCHS_SD_CON_INIT, MMCHS_SD_CON_INIT_INIT); /* command 0 , type other commands not response etc) */ write32(base_address + MMCHS_SD_CMD, 0x00); counter = 0; while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_STAT_CC) != MMCHS_SD_STAT_CC_RAISED) { if (read32(base_address + MMCHS_SD_STAT) & 0x8000) { mmc_log_warn(&log, "%s, error stat %x\n", __FUNCTION__, read32(base_address + MMCHS_SD_STAT)); return 1; } counter++; } /* clear the cc interrupt status */ set32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_CC_ENABLE, MMCHS_SD_IE_CC_ENABLE_ENABLE); /* * Set Set SD_CON[1] INIT bit to 0x0 to end the initialization sequence */ set32(base_address + MMCHS_SD_CON, MMCHS_SD_CON_INIT, MMCHS_SD_CON_INIT_NOINIT); /* Clean the MMCHS_SD_STAT register */ write32(base_address + MMCHS_SD_STAT, 0xffffffffu); return 0; } int mmchs_send_cmd(uint32_t command, uint32_t arg) { int count = 0; /* Read current interrupt status and fail it an interrupt is already * asserted */ if ((read32(base_address + MMCHS_SD_STAT) & 0xffffu)) { mmc_log_warn(&log, "%s, interrupt already raised stat %08x\n", __FUNCTION__, read32(base_address + MMCHS_SD_STAT)); write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_CC_ENABLE_CLEAR); // return 1; } /* Set arguments */ write32(base_address + MMCHS_SD_ARG, arg); /* Set command */ set32(base_address + MMCHS_SD_CMD, MMCHS_SD_CMD_MASK, command); /* Wait for completion */ while ((read32(base_address + MMCHS_SD_STAT) & 0xffffu) == 0x0) { count++; } if (read32(base_address + MMCHS_SD_STAT) & 0x8000) { mmc_log_warn(&log, "%s, error stat %08x\n", __FUNCTION__, read32(base_address + MMCHS_SD_STAT)); set32(base_address + MMCHS_SD_STAT, MMCHS_SD_STAT_ERROR_MASK, 0xffffffffu); return 1; } if ((command & MMCHS_SD_CMD_RSP_TYPE) == MMCHS_SD_CMD_RSP_TYPE_48B_BUSY) { /* * Command with busy response *CAN* also set the TC bit if they exit busy */ while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_IE_TC_ENABLE_ENABLE) == 0) { count++; } write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_TC_ENABLE_CLEAR); } /* clear the cc status */ write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_CC_ENABLE_CLEAR); return 0; } int mmc_send_cmd(struct mmc_command *c) { /* convert the command to a hsmmc command */ int ret; uint32_t cmd, arg; cmd = MMCHS_SD_CMD_INDX_CMD(c->cmd); arg = c->args; switch (c->resp_type) { case RESP_LEN_48_CHK_BUSY: cmd |= MMCHS_SD_CMD_RSP_TYPE_48B_BUSY; break; case RESP_LEN_48: cmd |= MMCHS_SD_CMD_RSP_TYPE_48B; break; case RESP_LEN_136: cmd |= MMCHS_SD_CMD_RSP_TYPE_136B; break; case NO_RESPONSE: cmd |= MMCHS_SD_CMD_RSP_TYPE_NO_RESP; break; default: return 1; } ret = mmchs_send_cmd(cmd, arg); /* copy response into cmd->resp */ switch (c->resp_type) { case RESP_LEN_48_CHK_BUSY: case RESP_LEN_48: c->resp[0] = read32(base_address + MMCHS_SD_RSP10); break; case RESP_LEN_136: c->resp[0] = read32(base_address + MMCHS_SD_RSP10); c->resp[1] = read32(base_address + MMCHS_SD_RSP32); c->resp[2] = read32(base_address + MMCHS_SD_RSP54); c->resp[3] = read32(base_address + MMCHS_SD_RSP76); break; case NO_RESPONSE: break; default: return 1; } return ret; } static struct mmc_command command; int card_goto_idle_state() { command.cmd = MMC_GO_IDLE_STATE; command.resp_type = NO_RESPONSE; command.args = 0x00; if (mmc_send_cmd(&command)) { // Failure return 1; } return 0; } int card_identification() { command.cmd = MMC_SEND_EXT_CSD; command.resp_type = RESP_LEN_48; command.args = MMCHS_SD_ARG_CMD8_VHS | MMCHS_SD_ARG_CMD8_CHECK_PATTERN; if (mmc_send_cmd(&command)) { // We currently only support 2.0, return 1; } if (!(command.resp[0] == (MMCHS_SD_ARG_CMD8_VHS | MMCHS_SD_ARG_CMD8_CHECK_PATTERN))) { mmc_log_warn(&log, "%s, check pattern check failed %08x\n", __FUNCTION__, command.resp[0]); return 1; } return 0; } int card_query_voltage_and_type(struct sd_card_regs *card) { command.cmd = MMC_APP_CMD; command.resp_type = RESP_LEN_48; command.args = MMC_ARG_RCA(0x0); /* RCA=0000 */ if (mmc_send_cmd(&command)) { return 1; } command.cmd = SD_APP_OP_COND; command.resp_type = RESP_LEN_48; /* 0x1 << 30 == send HCS (Host capacity support) and get OCR register */ command.args = MMC_OCR_3_3V_3_4V | MMC_OCR_3_2V_3_3V | MMC_OCR_3_1V_3_2V | MMC_OCR_3_0V_3_1V | MMC_OCR_2_9V_3_0V | MMC_OCR_2_8V_2_9V | MMC_OCR_2_7V_2_8V; command.args |= MMC_OCR_HCS; /* RCA=0000 */ if (mmc_send_cmd(&command)) { return 1; } /* @todo wait for max 1 ms */ while (!(command.resp[0] & MMC_OCR_MEM_READY)) { command.cmd = MMC_APP_CMD; command.resp_type = RESP_LEN_48; command.args = MMC_ARG_RCA(0x0); /* RCA=0000 */ if (mmc_send_cmd(&command)) { return 1; } /* Send ADMD41 */ /* 0x1 << 30 == send HCS (Host capacity support) and get OCR * register */ command.cmd = SD_APP_OP_COND; command.resp_type = RESP_LEN_48; /* 0x1 << 30 == send HCS (Host capacity support) */ command.args = MMC_OCR_3_3V_3_4V | MMC_OCR_3_2V_3_3V | MMC_OCR_3_1V_3_2V | MMC_OCR_3_0V_3_1V | MMC_OCR_2_9V_3_0V | MMC_OCR_2_8V_2_9V | MMC_OCR_2_7V_2_8V; command.args |= MMC_OCR_HCS; /* RCA=0000 */ if (mmc_send_cmd(&command)) { return 1; } /* if bit 31 is set the response is valid */ if ((command.resp[0] & MMC_OCR_MEM_READY)) { break; } } card->ocr = command.resp[3]; return 0; } int card_identify(struct sd_card_regs *card) { /* Send cmd 2 (all_send_cid) and expect 136 bits response */ command.cmd = MMC_ALL_SEND_CID; command.resp_type = RESP_LEN_136; command.args = MMC_ARG_RCA(0x0); /* RCA=0000 */ if (mmc_send_cmd(&command)) { return 1; } card->cid[0] = command.resp[0]; card->cid[1] = command.resp[1]; card->cid[2] = command.resp[2]; card->cid[3] = command.resp[3]; command.cmd = MMC_SET_RELATIVE_ADDR; command.resp_type = RESP_LEN_48; command.args = 0x0; /* RCA=0000 */ /* R6 response */ if (mmc_send_cmd(&command)) { return 1; } card->rca = SD_R6_RCA(command.resp); /* MMHCS only supports a single card so sending MMCHS_SD_CMD_CMD2 is * useless Still we should make it possible in the API to support * multiple cards */ return 0; } int card_csd(struct sd_card_regs *card) { /* send_csd -> r2 response */ command.cmd = MMC_SEND_CSD; command.resp_type = RESP_LEN_136; command.args = MMC_ARG_RCA(card->rca); /* card rca */ if (mmc_send_cmd(&command)) { return 1; } card->csd[0] = command.resp[0]; card->csd[1] = command.resp[1]; card->csd[2] = command.resp[2]; card->csd[3] = command.resp[3]; if (SD_CSD_CSDVER(card->csd) != SD_CSD_CSDVER_2_0) { mmc_log_warn(&log, "Version 2.0 of CSD register expected\n"); return 1; } /* sanity check */ // mmc_log_warn(&log,"size = %llu bytes\n", (long long // unsigned)SD_CSD_V2_CAPACITY( card->csd) * 512); return 0; } int select_card(struct sd_card_regs *card) { command.cmd = MMC_SELECT_CARD; command.resp_type = RESP_LEN_48_CHK_BUSY; command.args = MMC_ARG_RCA(card->rca); /* card rca */ if (mmc_send_cmd(&command)) { return 1; } return 0; } int read_single_block(struct sd_card_regs *card, uint32_t blknr, unsigned char *buf) { uint32_t count; uint32_t value; count = 0; set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_BRR_ENABLE, MMCHS_SD_IE_BRR_ENABLE_ENABLE); set32(base_address + MMCHS_SD_BLK, MMCHS_SD_BLK_BLEN, 512); /* read single block */ if (mmchs_send_cmd(MMCHS_SD_CMD_INDX_CMD(MMC_READ_BLOCK_SINGLE) | MMCHS_SD_CMD_DP_DATA /* Command with data transfer */ | MMCHS_SD_CMD_RSP_TYPE_48B /* type (R1) */ | MMCHS_SD_CMD_MSBS_SINGLE /* single block */ | MMCHS_SD_CMD_DDIR_READ /* read data from card */ , blknr)) { return 1; } while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_IE_BRR_ENABLE_ENABLE) == 0) { count++; } if (!(read32(base_address + MMCHS_SD_PSTATE) & MMCHS_SD_PSTATE_BRE_EN)) { return 1; /* We are not allowed to read data from the * data buffer */ } for (count = 0; count < 512; count += 4) { value = read32(base_address + MMCHS_SD_DATA); buf[count] = *((char *) &value); buf[count + 1] = *((char *) &value + 1); buf[count + 2] = *((char *) &value + 2); buf[count + 3] = *((char *) &value + 3); } /* Wait for TC */ while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_IE_TC_ENABLE_ENABLE) == 0) { count++; } write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_TC_ENABLE_CLEAR); /* clear and disable the bbr interrupt */ write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_BRR_ENABLE_CLEAR); set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_BRR_ENABLE, MMCHS_SD_IE_BRR_ENABLE_DISABLE); return 0; } int write_single_block(struct sd_card_regs *card, uint32_t blknr, unsigned char *buf) { uint32_t count; uint32_t value; count = 0; set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_BWR_ENABLE, MMCHS_SD_IE_BWR_ENABLE_ENABLE); // set32(base_address + MMCHS_SD_IE, 0xfff , 0xfff); set32(base_address + MMCHS_SD_BLK, MMCHS_SD_BLK_BLEN, 512); /* Set timeout */ set32(base_address + MMCHS_SD_SYSCTL, MMCHS_SD_SYSCTL_DTO, MMCHS_SD_SYSCTL_DTO_2POW27); /* write single block */ if (mmchs_send_cmd(MMCHS_SD_CMD_INDX_CMD(MMC_WRITE_BLOCK_SINGLE) | MMCHS_SD_CMD_DP_DATA /* Command with data transfer */ | MMCHS_SD_CMD_RSP_TYPE_48B /* type (R1b) */ | MMCHS_SD_CMD_MSBS_SINGLE /* single block */ | MMCHS_SD_CMD_DDIR_WRITE /* write to the card */ , blknr)) { return 1; } /* Wait for the MMCHS_SD_IE_BWR_ENABLE interrupt */ while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_IE_BWR_ENABLE) == 0) { count++; } if (!(read32(base_address + MMCHS_SD_PSTATE) & MMCHS_SD_PSTATE_BWE_EN)) { return 1; /* not ready to write data */ } for (count = 0; count < 512; count += 4) { *((char *) &value) = buf[count]; *((char *) &value + 1) = buf[count + 1]; *((char *) &value + 2) = buf[count + 2]; *((char *) &value + 3) = buf[count + 3]; write32(base_address + MMCHS_SD_DATA, value); } /* Wait for TC */ while ((read32(base_address + MMCHS_SD_STAT) & MMCHS_SD_IE_TC_ENABLE_ENABLE) == 0) { count++; } write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_TC_ENABLE_CLEAR); write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_CC_ENABLE_CLEAR); /* finished. */ /* clear the bwr interrupt FIXME is this right when writing? */ write32(base_address + MMCHS_SD_STAT, MMCHS_SD_IE_BWR_ENABLE_CLEAR); set32(base_address + MMCHS_SD_IE, MMCHS_SD_IE_BWR_ENABLE, MMCHS_SD_IE_BWR_ENABLE_DISABLE); return 0; } int mmchs_host_init(struct mmc_host *host) { mmchs_init(1); return 0; } void mmchs_set_log_level(int level) { if (level >= 0 && level <= 4) { log.log_level = level; } } int mmchs_host_set_instance(struct mmc_host *host, int instance) { mmc_log_info(&log, "Using instance number %d\n", instance); if (instance != 0) { return EIO; } return OK; } int mmchs_host_reset(struct mmc_host *host) { mmchs_init(1); return 0; } int mmchs_card_detect(struct sd_slot *slot) { /* @TODO implement proper card detect */ return 1; } struct sd_card * mmchs_card_initialize(struct sd_slot *slot) { mmchs_init(1); struct sd_card *card; card = &slot->card; memset(card, 0, sizeof(struct sd_card)); card->slot = slot; if (card_goto_idle_state()) { mmc_log_warn(&log, "Failed to go idle state\n"); return NULL; } if (card_identification()) { mmc_log_warn(&log, "Failed to do card_identification\n"); return NULL; } if (card_query_voltage_and_type(&slot->card.regs)) { mmc_log_warn(&log, "Failed to do card_query_voltage_and_type\n"); return NULL; } if (card_identify(&slot->card.regs)) { mmc_log_warn(&log, "Failed to identify card\n"); return NULL; } /* We have now initialized the hardware identified the card */ if (card_csd(&slot->card.regs)) { mmc_log_warn(&log, "failed to read csd (card specific data)\n"); return NULL; } if (select_card(&slot->card.regs)) { mmc_log_warn(&log, "Failed to select card\n"); return NULL; } if (SD_CSD_READ_BL_LEN(slot->card.regs.csd) != 0x09) { /* for CSD version 2.0 the value is fixed to 0x09 and means a * block size of 512 */ mmc_log_warn(&log, "Block size expect to be 512\n"); return NULL; } slot->card.blk_size = 512; /* HARDCODED value */ slot->card.blk_count = SD_CSD_V2_CAPACITY(slot->card.regs.csd); slot->card.state = SD_MODE_DATA_TRANSFER_MODE; memset(slot->card.part, 0, sizeof(slot->card.part)); memset(slot->card.subpart, 0, sizeof(slot->card.subpart)); slot->card.part[0].dv_base = 0; slot->card.part[0].dv_size = (unsigned long long) SD_CSD_V2_CAPACITY(slot->card.regs.csd) * 512; return &slot->card; } /* read count blocks into existing buf */ static int mmchs_host_read(struct sd_card *card, uint32_t blknr, uint32_t count, unsigned char *buf) { uint32_t i; i = count; for (i = 0; i < count; i++) { read_single_block(&card->regs, blknr + i, buf + (i * card->blk_size)); } return OK; } /* write count blocks */ static int mmchs_host_write(struct sd_card *card, uint32_t blknr, uint32_t count, unsigned char *buf) { uint32_t i; i = count; for (i = 0; i < count; i++) { write_single_block(&card->regs, blknr + i, buf + (i * card->blk_size)); } return OK; } int mmchs_card_release(struct sd_card *card) { assert(card->open_ct == 1); card->open_ct--; card->state = SD_MODE_UNINITIALIZED; /* TODO:Set card state */ return OK; } void host_initialize_host_structure_mmchs(struct mmc_host *host) { /* Initialize the basic data structures host slots and cards */ int i; host->host_set_instance = mmchs_host_set_instance; host->host_init = mmchs_host_init; host->set_log_level = mmchs_set_log_level; host->host_reset = mmchs_host_reset; host->card_detect = mmchs_card_detect; host->card_initialize = mmchs_card_initialize; host->card_release = mmchs_card_release; host->read = mmchs_host_read; host->write = mmchs_host_write; /* initialize data structures */ for (i = 0; i < sizeof(host->slot) / sizeof(host->slot[0]); i++) { // @TODO set initial card and slot state host->slot[i].host = host; host->slot[i].card.slot = &host->slot[i]; } }