📄 wbsd.c
字号:
* internally. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * DMA transfer? */ if (host->dma >= 0) { /* * The buffer for DMA is only 64 kB. */ BUG_ON(host->size > 0x10000); if (host->size > 0x10000) { data->error = MMC_ERR_INVALID; return; } /* * Transfer data from the SG list to * the DMA buffer. */ if (data->flags & MMC_DATA_WRITE) wbsd_sg_to_dma(host, data); /* * Initialise the ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); if (data->flags & MMC_DATA_READ) set_dma_mode(host->dma, DMA_MODE_READ & ~0x40); else set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40); set_dma_addr(host->dma, host->dma_addr); set_dma_count(host->dma, host->size); enable_dma(host->dma); release_dma_lock(dmaflags); /* * Enable DMA on the host. */ wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE); } else { /* * This flag is used to keep printk * output to a minimum. */ host->firsterr = 1; /* * Initialise the SG list. */ wbsd_init_sg(host, data); /* * Turn off DMA. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); /* * Set up FIFO threshold levels (and fill * buffer if doing a write). */ if (data->flags & MMC_DATA_READ) { wbsd_write_index(host, WBSD_IDX_FIFOEN, WBSD_FIFOEN_FULL | 8); } else { wbsd_write_index(host, WBSD_IDX_FIFOEN, WBSD_FIFOEN_EMPTY | 8); wbsd_fill_fifo(host); } } data->error = MMC_ERR_NONE;}static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data){ unsigned long dmaflags; int count; u8 status; WARN_ON(host->mrq == NULL); /* * Send a stop command if needed. */ if (data->stop) wbsd_send_command(host, data->stop); /* * Wait for the controller to leave data * transfer state. */ do { status = wbsd_read_index(host, WBSD_IDX_STATUS); } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE)); /* * DMA transfer? */ if (host->dma >= 0) { /* * Disable DMA on the host. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); /* * Turn of ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); count = get_dma_residue(host->dma); release_dma_lock(dmaflags); /* * Any leftover data? */ if (count) { printk(KERN_ERR "%s: Incomplete DMA transfer. " "%d bytes left.\n", mmc_hostname(host->mmc), count); data->error = MMC_ERR_FAILED; } else { /* * Transfer data from DMA buffer to * SG list. */ if (data->flags & MMC_DATA_READ) wbsd_dma_to_sg(host, data); data->bytes_xfered = host->size; } } DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered); wbsd_request_end(host, host->mrq);}/*****************************************************************************\ * * * MMC layer callbacks * * *\*****************************************************************************/static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq){ struct wbsd_host *host = mmc_priv(mmc); struct mmc_command *cmd; /* * Disable tasklets to avoid a deadlock. */ spin_lock_bh(&host->lock); BUG_ON(host->mrq != NULL); cmd = mrq->cmd; host->mrq = mrq; /* * If there is no card in the slot then * timeout immediatly. */ if (!(host->flags & WBSD_FCARD_PRESENT)) { cmd->error = MMC_ERR_TIMEOUT; goto done; } /* * Does the request include data? */ if (cmd->data) { wbsd_prepare_data(host, cmd->data); if (cmd->data->error != MMC_ERR_NONE) goto done; } wbsd_send_command(host, cmd); /* * If this is a data transfer the request * will be finished after the data has * transfered. */ if (cmd->data && (cmd->error == MMC_ERR_NONE)) { /* * Dirty fix for hardware bug. */ if (host->dma == -1) tasklet_schedule(&host->fifo_tasklet); spin_unlock_bh(&host->lock); return; }done: wbsd_request_end(host, mrq); spin_unlock_bh(&host->lock);}static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios){ struct wbsd_host *host = mmc_priv(mmc); u8 clk, setup, pwr; spin_lock_bh(&host->lock); /* * Reset the chip on each power off. * Should clear out any weird states. */ if (ios->power_mode == MMC_POWER_OFF) wbsd_init_device(host); if (ios->clock >= 24000000) clk = WBSD_CLK_24M; else if (ios->clock >= 16000000) clk = WBSD_CLK_16M; else if (ios->clock >= 12000000) clk = WBSD_CLK_12M; else clk = WBSD_CLK_375K; /* * Only write to the clock register when * there is an actual change. */ if (clk != host->clk) { wbsd_write_index(host, WBSD_IDX_CLK, clk); host->clk = clk; } /* * Power up card. */ if (ios->power_mode != MMC_POWER_OFF) { pwr = inb(host->base + WBSD_CSR); pwr &= ~WBSD_POWER_N; outb(pwr, host->base + WBSD_CSR); } /* * MMC cards need to have pin 1 high during init. * It wreaks havoc with the card detection though so * that needs to be disabled. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); if (ios->chip_select == MMC_CS_HIGH) { BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1); setup |= WBSD_DAT3_H; host->flags |= WBSD_FIGNORE_DETECT; } else { if (setup & WBSD_DAT3_H) { setup &= ~WBSD_DAT3_H; /* * We cannot resume card detection immediatly * because of capacitance and delays in the chip. */ mod_timer(&host->ignore_timer, jiffies + HZ / 100); } } wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * Store bus width for later. Will be used when * setting up the data transfer. */ host->bus_width = ios->bus_width; spin_unlock_bh(&host->lock);}static int wbsd_get_ro(struct mmc_host *mmc){ struct wbsd_host *host = mmc_priv(mmc); u8 csr; spin_lock_bh(&host->lock); csr = inb(host->base + WBSD_CSR); csr |= WBSD_MSLED; outb(csr, host->base + WBSD_CSR); mdelay(1); csr = inb(host->base + WBSD_CSR); csr &= ~WBSD_MSLED; outb(csr, host->base + WBSD_CSR); spin_unlock_bh(&host->lock); return csr & WBSD_WRPT;}static const struct mmc_host_ops wbsd_ops = { .request = wbsd_request, .set_ios = wbsd_set_ios, .get_ro = wbsd_get_ro,};/*****************************************************************************\ * * * Interrupt handling * * *\*****************************************************************************//* * Helper function to reset detection ignore */static void wbsd_reset_ignore(unsigned long data){ struct wbsd_host *host = (struct wbsd_host *)data; BUG_ON(host == NULL); DBG("Resetting card detection ignore\n"); spin_lock_bh(&host->lock); host->flags &= ~WBSD_FIGNORE_DETECT; /* * Card status might have changed during the * blackout. */ tasklet_schedule(&host->card_tasklet); spin_unlock_bh(&host->lock);}/* * Tasklets */static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host){ WARN_ON(!host->mrq); if (!host->mrq) return NULL; WARN_ON(!host->mrq->cmd); if (!host->mrq->cmd) return NULL; WARN_ON(!host->mrq->cmd->data); if (!host->mrq->cmd->data) return NULL; return host->mrq->cmd->data;}static void wbsd_tasklet_card(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; u8 csr; int delay = -1; spin_lock(&host->lock); if (host->flags & WBSD_FIGNORE_DETECT) { spin_unlock(&host->lock); return; } csr = inb(host->base + WBSD_CSR); WARN_ON(csr == 0xff); if (csr & WBSD_CARDPRESENT) { if (!(host->flags & WBSD_FCARD_PRESENT)) { DBG("Card inserted\n"); host->flags |= WBSD_FCARD_PRESENT; delay = 500; } } else if (host->flags & WBSD_FCARD_PRESENT) { DBG("Card removed\n"); host->flags &= ~WBSD_FCARD_PRESENT; if (host->mrq) { printk(KERN_ERR "%s: Card removed during transfer!\n", mmc_hostname(host->mmc)); wbsd_reset(host); host->mrq->cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); } delay = 0; } /* * Unlock first since we might get a call back. */ spin_unlock(&host->lock); if (delay != -1) mmc_detect_change(host->mmc, msecs_to_jiffies(delay));}static void wbsd_tasklet_fifo(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; if (data->flags & MMC_DATA_WRITE) wbsd_fill_fifo(host); else wbsd_empty_fifo(host); /* * Done? */ if (host->size == data->bytes_xfered) { wbsd_write_index(host, WBSD_IDX_FIFOEN, 0); tasklet_schedule(&host->finish_tasklet); }end: spin_unlock(&host->lock);}static void wbsd_tasklet_crc(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; tasklet_schedule(&host->finish_tasklet);end: spin_unlock(&host->lock);}static void wbsd_tasklet_timeout(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("Timeout\n"); data->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet);end: spin_unlock(&host->lock);}static void wbsd_tasklet_finish(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; struct mmc_data *data; spin_lock(&host->lock); WARN_ON(!host->mrq); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; wbsd_finish_data(host, data);end: spin_unlock(&host->lock);}static void wbsd_tasklet_block(unsigned long param){ struct wbsd_host *host = (struct wbsd_host *)param; struct mmc_data *data; spin_lock(&host->lock); if ((wbsd_read_index(host, WBSD_IDX_CRCSTATUS) & WBSD_CRC_MASK) != WBSD_CRC_OK) { data = wbsd_get_data(host); if (!data) goto end; DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; tasklet_schedule(&host->finish_tasklet); }end: spin_unlock(&host->lock);}/* * Interrupt handling */static irqreturn_t wbsd_irq(int irq, void *dev_id){ struct wbsd_host *host = dev_id; int isr; isr = inb(host->base + WBSD_ISR); /* * Was it actually our hardware that caused the interrupt? */ if (isr == 0xff || isr == 0x00) return IRQ_NONE; host->isr |= isr; /* * Schedule tasklets as needed. */ if (isr & WBSD_INT_CARD) tasklet_schedule(&host->card_tasklet); if (isr & WBSD_INT_FIFO_THRE) tasklet_schedule(&host->fifo_tasklet); if (isr & WBSD_INT_CRC) tasklet_hi_schedule(&host->crc_tasklet); if (isr & WBSD_INT_TIMEOUT) tasklet_hi_schedule(&host->timeout_tasklet); if (isr & WBSD_INT_BUSYEND) tasklet_hi_schedule(&host->block_tasklet); if (isr & WBSD_INT_TC) tasklet_schedule(&host->finish_tasklet); return IRQ_HANDLED;}/*****************************************************************************\ * * * Device initialisation and shutdown * * *\*****************************************************************************//* * Allocate/free MMC structure. */static int __devinit wbsd_alloc_mmc(struct device *dev){ struct mmc_host *mmc; struct wbsd_host *host; /* * Allocate MMC structure. */ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev); if (!mmc) return -ENOMEM; host = mmc_priv(mmc); host->mmc = mmc; host->dma = -1; /* * Set host parameters. */ mmc->ops = &wbsd_ops; mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; spin_lock_init(&host->lock); /* * Set up timers */ init_timer(&host->ignore_timer); host->ignore_timer.data = (unsigned long)host; host->ignore_timer.function = wbsd_reset_ignore; /* * Maximum number of segments. Worst case is one sector per segment * so this will be 64kB/512. */ mmc->max_hw_segs = 128; mmc->max_phys_segs = 128; /* * Maximum number of sectors in one transfer. Also limited by 64kB * buffer. */ mmc->max_sectors = 128; /* * Maximum segment size. Could be one segment with the maximum number * of segments. */ mmc->max_seg_size = mmc->max_sectors * 512; dev_set_drvdata(dev, mmc); return 0;}static void __devexit wbsd_free_mmc(struct device *dev){ struct mmc_host *mmc; struct wbsd_host *host; mmc = dev_get_drvdata(dev); if (!mmc) return; host = mmc_priv(mmc); BUG_ON(host == NULL); del_timer_sync(&host->ignore_timer); mmc_free_host(mmc); dev_set_drvdata(dev, NULL);}/* * Scan for known chip id:s */static int __devinit wbsd_scan(struct wbsd_host *host){ int i, j, k; int id; /* * Iterate through all ports, all codes to * find hardware that is in our known list. */ for (i = 0; i < ARRAY_SIZE(config_ports); i++) { if (!request_region(config_ports[i], 2, DRIVER_NAME)) continue; for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) { id = 0xFFFF; host->config = config_ports[i]; host->unlock_code = unlock_codes[j]; wbsd_unlock_config(host); outb(WBSD_CONF_ID_HI, config_ports[i]); id = inb(config_ports[i] + 1) << 8; outb(WBSD_CONF_ID_LO, config_ports[i]); id |= inb(config_ports[i] + 1); wbsd_lock_config(host); for (k = 0; k < ARRAY_SIZE(valid_ids); k++) { if (id == valid_ids[k]) { host->chip_id = id; return 0; } } if (id != 0xFFFF) { DBG("Unknown hardware (id %x) found at %x\n", id, config_ports[i]); } } release_region(config_ports[i], 2); } host->config = 0; host->unlock_code = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -