sdhci.c
来自「linux 内核源代码」· C语言 代码 · 共 1,668 行 · 第 1/3 页
C
1,668 行
sdhci_send_command(host, data->stop); } else tasklet_schedule(&host->finish_tasklet);}static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd){ int flags; u32 mask; unsigned long timeout; WARN_ON(host->cmd); /* 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).\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); cmd->error = -EIO; 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!\n", mmc_hostname(host->mmc)); cmd->error = -EINVAL; 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 = 0; if (host->data && host->data_early) sdhci_finish_data(host); if (!host->cmd->data) tasklet_schedule(&host->finish_tasklet); host->cmd = NULL;}static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock){ int div; u16 clk; unsigned long timeout; if (clock == host->clock) return; writew(0, host->ioaddr + SDHCI_CLOCK_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.\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; if (power == (unsigned short)-1) { writeb(0, host->ioaddr + SDHCI_POWER_CONTROL); goto out; } /* * Spec says that we should clear the power reg before setting * a new value. Some controllers don't seem to like this though. */ if (!(host->chip->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE)) writeb(0, host->ioaddr + SDHCI_POWER_CONTROL); pwr = SDHCI_POWER_ON; switch (1 << power) { case MMC_VDD_165_195: pwr |= SDHCI_POWER_180; break; case MMC_VDD_29_30: case MMC_VDD_30_31: pwr |= SDHCI_POWER_300; break; case MMC_VDD_32_33: case MMC_VDD_33_34: 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 = -ENOMEDIUM; 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; if (ios->timing == MMC_TIMING_SD_HS) ctrl |= SDHCI_CTRL_HISPD; else ctrl &= ~SDHCI_CTRL_HISPD; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); /* * Some (ENE) controllers go apeshit on some ios operation, * signalling timeout and CRC errors even on CMD0. Resetting * it on each ios seems to solve the problem. */ if(host->chip->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); 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 void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable){ struct sdhci_host *host; unsigned long flags; u32 ier; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); ier = readl(host->ioaddr + SDHCI_INT_ENABLE); ier &= ~SDHCI_INT_CARD_INT; if (enable) ier |= SDHCI_INT_CARD_INT; writel(ier, host->ioaddr + SDHCI_INT_ENABLE); writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); mmiowb(); spin_unlock_irqrestore(&host->lock, flags);}static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, .enable_sdio_irq = sdhci_enable_sdio_irq,};/*****************************************************************************\ * * * 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 = -ENOMEDIUM; 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; /* * The controller needs a reset of internal state machines * upon error conditions. */ if (mrq->cmd->error || (mrq->data && (mrq->data->error || (mrq->data->stop && mrq->data->stop->error))) || (host->chip->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) { /* 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.\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); if (host->data) { host->data->error = -ETIMEDOUT; sdhci_finish_data(host); } else { if (host->cmd) host->cmd->error = -ETIMEDOUT; else host->mrq->cmd->error = -ETIMEDOUT; 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 0x%08x even " "though no command operation was in progress.\n", mmc_hostname(host->mmc), (unsigned)intmask); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_TIMEOUT) host->cmd->error = -ETIMEDOUT; else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) host->cmd->error = -EILSEQ; if (host->cmd->error) tasklet_schedule(&host->finish_tasklet); else if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host);}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 0x%08x even " "though no data operation was in progress.\n", mmc_hostname(host->mmc), (unsigned)intmask); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_DATA_TIMEOUT) host->data->error = -ETIMEDOUT; else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) host->data->error = -EILSEQ; if (host->data->error) sdhci_finish_data(host); else { if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) sdhci_transfer_pio(host); /* * We currently don't do anything fancy with DMA * boundaries, but as we can't disable the feature * we need to at least restart the transfer. */ if (intmask & SDHCI_INT_DMA_END) writel(readl(host->ioaddr + SDHCI_DMA_ADDRESS), host->ioaddr + SDHCI_DMA_ADDRESS); if (intmask & SDHCI_INT_DATA_END) { if (host->cmd) { /* * Data managed to finish before the * command completed. Make sure we do * things in the proper order. */ host->data_early = 1; } else { 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; int cardint = 0; spin_lock(&host->lock); intmask = readl(host->ioaddr + SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { 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);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?