📄 sdhci.c
字号:
/* Wait max 10 ms */ timeout = 10; mask = SDHCI_CMD_INHIBIT; if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) mask |= SDHCI_DATA_INHIBIT; /* We shouldn't wait for data inihibit for stop commands, even though they might use busy signaling */ if (host->mrq->data && (cmd == host->mrq->data->stop)) mask &= ~SDHCI_DATA_INHIBIT; while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) { if (timeout == 0) { printk(KERN_ERR "%s: Controller never released " "inhibit bit(s). Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); return; } timeout--; mdelay(1); } mod_timer(&host->timer, jiffies + 10 * HZ); host->cmd = cmd; sdhci_prepare_data(host, cmd->data); writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT); sdhci_set_transfer_mode(host, cmd->data); if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { printk(KERN_ERR "%s: Unsupported response type! " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); cmd->error = MMC_ERR_INVALID; tasklet_schedule(&host->finish_tasklet); return; } if (!(cmd->flags & MMC_RSP_PRESENT)) flags = SDHCI_CMD_RESP_NONE; else if (cmd->flags & MMC_RSP_136) flags = SDHCI_CMD_RESP_LONG; else if (cmd->flags & MMC_RSP_BUSY) flags = SDHCI_CMD_RESP_SHORT_BUSY; else flags = SDHCI_CMD_RESP_SHORT; if (cmd->flags & MMC_RSP_CRC) flags |= SDHCI_CMD_CRC; if (cmd->flags & MMC_RSP_OPCODE) flags |= SDHCI_CMD_INDEX; if (cmd->data) flags |= SDHCI_CMD_DATA; writew(SDHCI_MAKE_CMD(cmd->opcode, flags), host->ioaddr + SDHCI_COMMAND);}static void sdhci_finish_command(struct sdhci_host *host){ int i; BUG_ON(host->cmd == NULL); if (host->cmd->flags & MMC_RSP_PRESENT) { if (host->cmd->flags & MMC_RSP_136) { /* CRC is stripped so we need to do some shifting. */ for (i = 0;i < 4;i++) { host->cmd->resp[i] = readl(host->ioaddr + SDHCI_RESPONSE + (3-i)*4) << 8; if (i != 3) host->cmd->resp[i] |= readb(host->ioaddr + SDHCI_RESPONSE + (3-i)*4-1); } } else { host->cmd->resp[0] = readl(host->ioaddr + SDHCI_RESPONSE); } } host->cmd->error = MMC_ERR_NONE; DBG("Ending cmd (%x)\n", host->cmd->opcode); if (host->cmd->data) host->data = host->cmd->data; else tasklet_schedule(&host->finish_tasklet); host->cmd = NULL;}static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock){ int div; u8 ctrl; u16 clk; unsigned long timeout; if (clock == host->clock) return; writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); if (clock > 25000000) ctrl |= SDHCI_CTRL_HISPD; else ctrl &= ~SDHCI_CTRL_HISPD; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); if (clock == 0) goto out; for (div = 1;div < 256;div *= 2) { if ((host->max_clk / div) <= clock) break; } div >>= 1; clk = div << SDHCI_DIVIDER_SHIFT; clk |= SDHCI_CLOCK_INT_EN; writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); /* Wait max 10 ms */ timeout = 10; while (!((clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { if (timeout == 0) { printk(KERN_ERR "%s: Internal clock never stabilised. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } timeout--; mdelay(1); } clk |= SDHCI_CLOCK_CARD_EN; writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL);out: host->clock = clock;}static void sdhci_set_power(struct sdhci_host *host, unsigned short power){ u8 pwr; if (host->power == power) return; writeb(0, host->ioaddr + SDHCI_POWER_CONTROL); if (power == (unsigned short)-1) goto out; pwr = SDHCI_POWER_ON; switch (power) { case MMC_VDD_170: case MMC_VDD_180: case MMC_VDD_190: pwr |= SDHCI_POWER_180; break; case MMC_VDD_290: case MMC_VDD_300: case MMC_VDD_310: pwr |= SDHCI_POWER_300; break; case MMC_VDD_320: case MMC_VDD_330: case MMC_VDD_340: pwr |= SDHCI_POWER_330; break; default: BUG(); } writeb(pwr, host->ioaddr + SDHCI_POWER_CONTROL);out: host->power = power;}/*****************************************************************************\ * * * MMC callbacks * * *\*****************************************************************************/static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq){ struct sdhci_host *host; unsigned long flags; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); WARN_ON(host->mrq != NULL); sdhci_activate_led(host); host->mrq = mrq; if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { host->mrq->cmd->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet); } else sdhci_send_command(host, mrq->cmd); mmiowb(); spin_unlock_irqrestore(&host->lock, flags);}static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios){ struct sdhci_host *host; unsigned long flags; u8 ctrl; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); /* * Reset the chip on each power off. * Should clear out any weird states. */ if (ios->power_mode == MMC_POWER_OFF) { writel(0, host->ioaddr + SDHCI_SIGNAL_ENABLE); sdhci_init(host); } sdhci_set_clock(host, ios->clock); if (ios->power_mode == MMC_POWER_OFF) sdhci_set_power(host, -1); else sdhci_set_power(host, ios->vdd); ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); if (ios->bus_width == MMC_BUS_WIDTH_4) ctrl |= SDHCI_CTRL_4BITBUS; else ctrl &= ~SDHCI_CTRL_4BITBUS; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); mmiowb(); spin_unlock_irqrestore(&host->lock, flags);}static int sdhci_get_ro(struct mmc_host *mmc){ struct sdhci_host *host; unsigned long flags; int present; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); present = readl(host->ioaddr + SDHCI_PRESENT_STATE); spin_unlock_irqrestore(&host->lock, flags); return !(present & SDHCI_WRITE_PROTECT);}static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro,};/*****************************************************************************\ * * * Tasklets * * *\*****************************************************************************/static void sdhci_tasklet_card(unsigned long param){ struct sdhci_host *host; unsigned long flags; host = (struct sdhci_host*)param; spin_lock_irqsave(&host->lock, flags); if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { if (host->mrq) { printk(KERN_ERR "%s: Card removed during transfer!\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Resetting controller.\n", mmc_hostname(host->mmc)); sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); host->mrq->cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); } } spin_unlock_irqrestore(&host->lock, flags); mmc_detect_change(host->mmc, msecs_to_jiffies(500));}static void sdhci_tasklet_finish(unsigned long param){ struct sdhci_host *host; unsigned long flags; struct mmc_request *mrq; host = (struct sdhci_host*)param; spin_lock_irqsave(&host->lock, flags); del_timer(&host->timer); mrq = host->mrq; DBG("Ending request, cmd (%x)\n", mrq->cmd->opcode); /* * The controller needs a reset of internal state machines * upon error conditions. */ if ((mrq->cmd->error != MMC_ERR_NONE) || (mrq->data && ((mrq->data->error != MMC_ERR_NONE) || (mrq->data->stop && (mrq->data->stop->error != MMC_ERR_NONE))))) { /* Some controllers need this kick or reset won't work here */ if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { unsigned int clock; /* This is to force an update */ clock = host->clock; host->clock = 0; sdhci_set_clock(host, clock); } /* Spec says we should do both at the same time, but Ricoh controllers do not like that. */ sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } host->mrq = NULL; host->cmd = NULL; host->data = NULL; sdhci_deactivate_led(host); mmiowb(); spin_unlock_irqrestore(&host->lock, flags); mmc_request_done(host->mmc, mrq);}static void sdhci_timeout_timer(unsigned long data){ struct sdhci_host *host; unsigned long flags; host = (struct sdhci_host*)data; spin_lock_irqsave(&host->lock, flags); if (host->mrq) { printk(KERN_ERR "%s: Timeout waiting for hardware interrupt. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); if (host->data) { host->data->error = MMC_ERR_TIMEOUT; sdhci_finish_data(host); } else { if (host->cmd) host->cmd->error = MMC_ERR_TIMEOUT; else host->mrq->cmd->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet); } } mmiowb(); spin_unlock_irqrestore(&host->lock, flags);}/*****************************************************************************\ * * * Interrupt handling * * *\*****************************************************************************/static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask){ BUG_ON(intmask == 0); if (!host->cmd) { printk(KERN_ERR "%s: Got command interrupt even though no " "command operation was in progress.\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); else { if (intmask & SDHCI_INT_TIMEOUT) host->cmd->error = MMC_ERR_TIMEOUT; else if (intmask & SDHCI_INT_CRC) host->cmd->error = MMC_ERR_BADCRC; else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) host->cmd->error = MMC_ERR_FAILED; else host->cmd->error = MMC_ERR_INVALID; tasklet_schedule(&host->finish_tasklet); }}static void sdhci_data_irq(struct sdhci_host *host, u32 intmask){ BUG_ON(intmask == 0); if (!host->data) { /* * A data end interrupt is sent together with the response * for the stop command. */ if (intmask & SDHCI_INT_DATA_END) return; printk(KERN_ERR "%s: Got data interrupt even though no " "data operation was in progress.\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_DATA_TIMEOUT) host->data->error = MMC_ERR_TIMEOUT; else if (intmask & SDHCI_INT_DATA_CRC) host->data->error = MMC_ERR_BADCRC; else if (intmask & SDHCI_INT_DATA_END_BIT) host->data->error = MMC_ERR_FAILED; if (host->data->error != MMC_ERR_NONE) sdhci_finish_data(host); else { if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) sdhci_transfer_pio(host); if (intmask & SDHCI_INT_DATA_END) sdhci_finish_data(host); }}static irqreturn_t sdhci_irq(int irq, void *dev_id){ irqreturn_t result; struct sdhci_host* host = dev_id; u32 intmask; spin_lock(&host->lock); intmask = readl(host->ioaddr + SDHCI_INT_STATUS); if (!intmask) { result = IRQ_NONE; goto out; } DBG("*** %s got interrupt: 0x%08x\n", host->slot_descr, intmask); if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { writel(intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), host->ioaddr + SDHCI_INT_STATUS); tasklet_schedule(&host->card_tasklet); } intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); if (intmask & SDHCI_INT_CMD_MASK) { writel(intmask & SDHCI_INT_CMD_MASK, host->ioaddr + SDHCI_INT_STATUS); sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); } if (intmask & SDHCI_INT_DATA_MASK) { writel(intmask & SDHCI_INT_DATA_MASK, host->ioaddr + SDHCI_INT_STATUS); sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -