static vmm_irq_return_t sdhci_irq_handler(int irq_no, void *dev) { u32 intmask, unexpected = 0; vmm_irq_return_t result; struct sdhci_host *host = dev; intmask = sdhci_readl(host, SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { result = VMM_IRQ_NONE; goto out; } if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; /* * There is a observation on i.mx esdhc. INSERT bit will be * immediately set again when it gets cleared, if a card is * inserted. We have to mask the irq to prevent interrupt * storm which will freeze the system. And the REMOVE gets * the same situation. * * More testing are needed here to ensure it works for other * platforms though. */ sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT : SDHCI_INT_CARD_REMOVE); sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT); sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); mmc_detect_card_change(host->mmc, 200); } if (intmask & SDHCI_INT_CMD_MASK) { sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS); sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); } if (intmask & SDHCI_INT_DATA_MASK) { sdhci_writel(host, intmask & SDHCI_INT_DATA_MASK, SDHCI_INT_STATUS); sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); } intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); intmask &= ~SDHCI_INT_ERROR; if (intmask & SDHCI_INT_BUS_POWER) { vmm_printf("%s: Card is consuming too much power!\n", mmc_hostname(host->mmc)); sdhci_writel(host, SDHCI_INT_BUS_POWER, SDHCI_INT_STATUS); } intmask &= ~SDHCI_INT_BUS_POWER; if (intmask) { unexpected |= intmask; sdhci_writel(host, intmask, SDHCI_INT_STATUS); } result = VMM_IRQ_HANDLED; out: return result; }
/* * Software emulation of the SD card insertion/removal. Set insert=1 for insert * and insert=0 for removal */ static int bcm_kona_sd_card_emulate(struct sdio_dev *dev, int insert) { struct sdhci_host *host = dev->host; uint32_t val; unsigned long flags; int ret = 0; #ifndef CONFIG_ARCH_ISLAND /* * The clock enabled here will be disabled in * sdhci_tasklet_card */ if (sdhci_pltfm_rpm_enabled(dev)) { /* * The code below can be executed just * after a device resume. pm core * enables the runtime pm for the device * when the resume callback returns. * It is possible that we reach here before * that. One option is to wait for the RPM * to be enabled, but this can create an * obvious issue when we perform card * insert/removal during the probe. We cant * remove the pm_runtime_get_sync with a * direct clock enable here, because the * sdhci_irq will complain about "interrupt * while runtime suspened". We cant even check * for pm_runtime status and take an appropriate * action, because if the status changes by the * time the clcok disable is done in card tasklet, * it will result in unbalanced RPM usage. Moreover * why to wait for something in an ISR when we have * an option. * So a better option is to do both. */ pm_runtime_get_sync(dev->dev); } /* Enable clock once irrespective of RPM state */ ret = sdhci_pltfm_clk_enable(dev, 1); if (ret) { dev_err(dev->dev, "enable clock during card emulate failed\n"); return -EAGAIN; } #endif /* this function can be called from various contexts including ISR */ spin_lock_irqsave(&host->lock, flags); /* Ensure SD bus scanning to detect media change */ host->mmc->rescan_disable = 0; /* Back-to-Back register write needs a delay of min 10uS. * We keep 20uS */ udelay(20); val = sdhci_readl(host, KONA_SDHOST_CORESTAT); if (insert) { val |= KONA_SDHOST_CD_SW; sdhci_writel(host, val, KONA_SDHOST_CORESTAT); } else { val &= ~KONA_SDHOST_CD_SW; sdhci_writel(host, val, KONA_SDHOST_CORESTAT); } spin_unlock_irqrestore(&host->lock, flags); return 0; }
int sdhci_send_command(struct mmc_host *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { bool present; u32 mask, flags, mode; int ret = 0, trans_bytes = 0, is_aligned = 1; u32 timeout, retry = 10000, stat = 0, start_addr = 0; struct sdhci_host *host = mmc_priv(mmc); /* If polling, assume that the card is always present. */ if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) { present = TRUE; } else { present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; } /* If card not present then return error */ if (!present) { return VMM_EIO; } /* Wait max 10 ms */ timeout = 10; sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT; /* We shouldn't wait for data inihibit for stop commands, even though they might use busy signaling */ if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) { mask &= ~SDHCI_DATA_INHIBIT; } while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) { if (timeout == 0) { vmm_printf("%s: Controller never released " "inhibit bit(s).\n", __func__); return VMM_EIO; } timeout--; vmm_udelay(1000); } mask = SDHCI_INT_RESPONSE; if (!(cmd->resp_type & MMC_RSP_PRESENT)) { flags = SDHCI_CMD_RESP_NONE; } else if (cmd->resp_type & MMC_RSP_136) { flags = SDHCI_CMD_RESP_LONG; } else if (cmd->resp_type & MMC_RSP_BUSY) { flags = SDHCI_CMD_RESP_SHORT_BUSY; mask |= SDHCI_INT_DATA_END; } else { flags = SDHCI_CMD_RESP_SHORT; } if (cmd->resp_type & MMC_RSP_CRC) { flags |= SDHCI_CMD_CRC; } if (cmd->resp_type & MMC_RSP_OPCODE) { flags |= SDHCI_CMD_INDEX; } if (data) { flags |= SDHCI_CMD_DATA; } /* Set Transfer mode regarding to data flag */ if (data != 0) { sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); mode = SDHCI_TRNS_BLK_CNT_EN; trans_bytes = data->blocks * data->blocksize; if (data->blocks > 1) { mode |= SDHCI_TRNS_MULTI; } if (data->flags == MMC_DATA_READ) { mode |= SDHCI_TRNS_READ; } if (host->sdhci_caps & SDHCI_CAN_DO_SDMA) { if (data->flags == MMC_DATA_READ) { start_addr = (u32)data->dest; } else { start_addr = (u32)data->src; } if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && (start_addr & 0x7) != 0x0) { is_aligned = 0; start_addr = (u32)host->aligned_buffer; if (data->flags != MMC_DATA_READ) { memcpy(host->aligned_buffer, data->src, trans_bytes); } } sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS); mode |= SDHCI_TRNS_DMA; vmm_flush_cache_range(start_addr, start_addr + trans_bytes); } sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, data->blocksize), SDHCI_BLOCK_SIZE); sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); } sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT); sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND); do { stat = sdhci_readl(host, SDHCI_INT_STATUS); if (stat & SDHCI_INT_ERROR) { break; } if (--retry == 0) { break; } } while ((stat & mask) != mask); if (retry == 0) { if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) return VMM_OK; else { vmm_printf("%s: Status update timeout!\n", __func__); return VMM_ETIMEDOUT; } } if ((stat & (SDHCI_INT_ERROR | mask)) == mask) { sdhci_cmd_done(host, cmd); sdhci_writel(host, mask, SDHCI_INT_STATUS); } else { ret = VMM_EFAIL; } if (!ret && data) { ret = sdhci_transfer_data(host, data, start_addr); } if (host->quirks & SDHCI_QUIRK_WAIT_SEND_CMD) { vmm_udelay(1000); } stat = sdhci_readl(host, SDHCI_INT_STATUS); sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); if (!ret) { if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && !is_aligned && (data->flags == MMC_DATA_READ)) { memcpy(data->dest, host->aligned_buffer, trans_bytes); } return VMM_OK; } sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); if (stat & SDHCI_INT_TIMEOUT) { return VMM_ETIMEDOUT; } else { return VMM_EIO; } }
int sdhci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { struct sdhci_host *host = (struct sdhci_host *)mmc->priv; unsigned int stat = 0; int ret = 0; int trans_bytes = 0, is_aligned = 1; u32 mask, flags, mode; unsigned int timeout, start_addr = 0; unsigned int retry = 10000; /* Wait max 10 ms */ timeout = 10; sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT; /* We shouldn't wait for data inihibit for stop commands, even though they might use busy signaling */ if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION || cmd->cmdidx == MMC_CMD_SEND_STATUS) mask &= ~SDHCI_DATA_INHIBIT; while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) { if (timeout == 0) { printf("Controller never released inhibit bit(s).\n"); return COMM_ERR; } timeout--; udelay(1000); } mask = SDHCI_INT_RESPONSE; if (!(cmd->resp_type & MMC_RSP_PRESENT)) flags = SDHCI_CMD_RESP_NONE; else if (cmd->resp_type & MMC_RSP_136) flags = SDHCI_CMD_RESP_LONG; else if (cmd->resp_type & MMC_RSP_BUSY) { flags = SDHCI_CMD_RESP_SHORT_BUSY; mask |= SDHCI_INT_DATA_END; } else flags = SDHCI_CMD_RESP_SHORT; if (cmd->resp_type & MMC_RSP_CRC) flags |= SDHCI_CMD_CRC; if (cmd->resp_type & MMC_RSP_OPCODE) flags |= SDHCI_CMD_INDEX; if (data) flags |= SDHCI_CMD_DATA; /*Set Transfer mode regarding to data flag*/ if (data != 0) { sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); mode = SDHCI_TRNS_BLK_CNT_EN; trans_bytes = data->blocks * data->blocksize; if (data->blocks > 1) mode |= (SDHCI_TRNS_MULTI | SDHCI_TRNS_ACMD12); if (data->flags == MMC_DATA_READ) mode |= SDHCI_TRNS_READ; #ifdef CONFIG_MMC_SDMA if (data->flags == MMC_DATA_READ) start_addr = (unsigned int)data->dest; else start_addr = (unsigned int)data->src; if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && (start_addr & 0x7) != 0x0) { is_aligned = 0; start_addr = (unsigned int)aligned_buffer; if (data->flags != MMC_DATA_READ) memcpy(aligned_buffer, data->src, trans_bytes); } sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS); mode |= SDHCI_TRNS_DMA; #endif sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, data->blocksize), SDHCI_BLOCK_SIZE); sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); } sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT); #ifdef CONFIG_MMC_SDMA flush_cache(start_addr, trans_bytes); #endif sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND); do { stat = sdhci_readl(host, SDHCI_INT_STATUS); if (stat & SDHCI_INT_ERROR) break; if (--retry == 0) break; } while ((stat & mask) != mask); if (retry == 0) { if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) return 0; else { printf("Timeout for status update!\n"); return TIMEOUT; } } if ((stat & (SDHCI_INT_ERROR | mask)) == mask) { sdhci_cmd_done(host, cmd); sdhci_writel(host, mask, SDHCI_INT_STATUS); } else ret = -1; if (!ret && data) ret = sdhci_transfer_data(host, data, start_addr); stat = sdhci_readl(host, SDHCI_INT_STATUS); sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); if (!ret) { if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && !is_aligned && (data->flags == MMC_DATA_READ)) memcpy(data->dest, aligned_buffer, trans_bytes); return 0; } sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); if (stat & SDHCI_INT_TIMEOUT) return TIMEOUT; else return COMM_ERR; }