📄 wbsd.c
字号:
/* * linux/drivers/mmc/wbsd.c * * Copyright (C) 2004 Pierre Ossman, All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */#include <linux/config.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/ioport.h>#include <linux/device.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/blkdev.h>#include <linux/mmc/host.h>#include <linux/mmc/protocol.h>#include <asm/io.h>#include <asm/dma.h>#include "wbsd.h"#define DRIVER_NAME "wbsd"#define DRIVER_VERSION "1.0"#ifdef CONFIG_MMC_DEBUG#define DBG(x...) \ printk(KERN_DEBUG DRIVER_NAME ": " x)#define DBGF(f, x...) \ printk(KERN_DEBUG DRIVER_NAME " [%s()]: " f, __func__, ##x)#else#define DBG(x...) do { } while (0)#define DBGF(x...) do { } while (0)#endifstatic unsigned int io = 0x248;static unsigned int irq = 6;static int dma = 2;#ifdef CONFIG_MMC_DEBUGvoid DBG_REG(int reg, u8 value){ int i; printk(KERN_DEBUG "wbsd: Register %d: 0x%02X %3d '%c' ", reg, (int)value, (int)value, (value < 0x20)?'.':value); for (i = 7;i >= 0;i--) { if (value & (1 << i)) printk("x"); else printk("."); } printk("\n");}#else#define DBG_REG(r, v) do {} while (0)#endif/* * Basic functions */static inline void wbsd_unlock_config(struct wbsd_host* host){ outb(host->unlock_code, host->config); outb(host->unlock_code, host->config);}static inline void wbsd_lock_config(struct wbsd_host* host){ outb(LOCK_CODE, host->config);}static inline void wbsd_write_config(struct wbsd_host* host, u8 reg, u8 value){ outb(reg, host->config); outb(value, host->config + 1);}static inline u8 wbsd_read_config(struct wbsd_host* host, u8 reg){ outb(reg, host->config); return inb(host->config + 1);}static inline void wbsd_write_index(struct wbsd_host* host, u8 index, u8 value){ outb(index, host->base + WBSD_IDXR); outb(value, host->base + WBSD_DATAR);}static inline u8 wbsd_read_index(struct wbsd_host* host, u8 index){ outb(index, host->base + WBSD_IDXR); return inb(host->base + WBSD_DATAR);}/* * Common routines */static void wbsd_init_device(struct wbsd_host* host){ u8 setup, ier; /* * Reset chip (SD/MMC part) and fifo. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * Read back default clock. */ host->clk = wbsd_read_index(host, WBSD_IDX_CLK); /* * Power down port. */ outb(WBSD_POWER_N, host->base + WBSD_CSR); /* * Set maximum timeout. */ wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F); /* * Enable interesting interrupts. */ ier = 0; ier |= WBSD_EINT_CARD; ier |= WBSD_EINT_FIFO_THRE; ier |= WBSD_EINT_CCRC; ier |= WBSD_EINT_TIMEOUT; ier |= WBSD_EINT_CRC; ier |= WBSD_EINT_TC; outb(ier, host->base + WBSD_EIR); /* * Clear interrupts. */ inb(host->base + WBSD_ISR);}static void wbsd_reset(struct wbsd_host* host){ u8 setup; printk(KERN_ERR DRIVER_NAME ": Resetting chip\n"); /* * Soft reset of chip (SD/MMC part). */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_SOFT_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup);}static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq){ unsigned long dmaflags; DBGF("Ending request, cmd (%x)\n", mrq->cmd->opcode); if (host->dma >= 0) { /* * Release ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); release_dma_lock(dmaflags); /* * Disable DMA on host. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); } host->mrq = NULL; /* * MMC layer might call back into the driver so first unlock. */ spin_unlock(&host->lock); mmc_request_done(host->mmc, mrq); spin_lock(&host->lock);}/* * Scatter/gather functions */static inline void wbsd_init_sg(struct wbsd_host* host, struct mmc_data* data){ struct request* req = data->req; /* * Get info. about SG list from data structure. */ host->cur_sg = data->sg; host->num_sg = data->sg_len; host->offset = 0; host->remain = host->cur_sg->length;}static inline int wbsd_next_sg(struct wbsd_host* host){ /* * Skip to next SG entry. */ host->cur_sg++; host->num_sg--; /* * Any entries left? */ if (host->num_sg > 0) { host->offset = 0; host->remain = host->cur_sg->length; } return host->num_sg;}static inline char* wbsd_kmap_sg(struct wbsd_host* host){ return kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ) + host->cur_sg->offset;}static inline void wbsd_kunmap_sg(struct wbsd_host* host){ kunmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ);}static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data){ unsigned int len, i, size; struct scatterlist* sg; char* dmabuf = host->dma_buffer; char* sgbuf; size = host->size; sg = data->sg; len = data->sg_len; /* * Just loop through all entries. Size might not * be the entire list though so make sure that * we do not transfer too much. */ for (i = 0;i < len;i++) { sgbuf = kmap_atomic(sg[i].page, KM_BIO_SRC_IRQ) + sg[i].offset; if (size < sg[i].length) memcpy(dmabuf, sgbuf, size); else memcpy(dmabuf, sgbuf, sg[i].length); kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ); dmabuf += sg[i].length; if (size < sg[i].length) size = 0; else size -= sg[i].length; if (size == 0) break; } /* * Check that we didn't get a request to transfer * more data than can fit into the SG list. */ BUG_ON(size != 0); host->size -= size;}static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data){ unsigned int len, i, size; struct scatterlist* sg; char* dmabuf = host->dma_buffer; char* sgbuf; size = host->size; sg = data->sg; len = data->sg_len; /* * Just loop through all entries. Size might not * be the entire list though so make sure that * we do not transfer too much. */ for (i = 0;i < len;i++) { sgbuf = kmap_atomic(sg[i].page, KM_BIO_SRC_IRQ) + sg[i].offset; if (size < sg[i].length) memcpy(sgbuf, dmabuf, size); else memcpy(sgbuf, dmabuf, sg[i].length); kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ); dmabuf += sg[i].length; if (size < sg[i].length) size = 0; else size -= sg[i].length; if (size == 0) break; } /* * Check that we didn't get a request to transfer * more data than can fit into the SG list. */ BUG_ON(size != 0); host->size -= size;}/* * Command handling */ static inline void wbsd_get_short_reply(struct wbsd_host* host, struct mmc_command* cmd){ /* * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) { cmd->error = MMC_ERR_INVALID; return; } cmd->resp[0] = wbsd_read_index(host, WBSD_IDX_RESP12) << 24; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0; cmd->resp[1] = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;}static inline void wbsd_get_long_reply(struct wbsd_host* host, struct mmc_command* cmd){ int i; /* * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) { cmd->error = MMC_ERR_INVALID; return; } for (i = 0;i < 4;i++) { cmd->resp[i] = wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0; }}static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs);static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd){ int i; u8 status, eir, isr; DBGF("Sending cmd (%x)\n", cmd->opcode); /* * Disable interrupts as the interrupt routine * will destroy the contents of ISR. */ eir = inb(host->base + WBSD_EIR); outb(0, host->base + WBSD_EIR); /* * Send the command (CRC calculated by host). */ outb(cmd->opcode, host->base + WBSD_CMDR); for (i = 3;i >= 0;i--) outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR); cmd->error = MMC_ERR_NONE; /* * Wait for the request to complete. */ do { status = wbsd_read_index(host, WBSD_IDX_STATUS); } while (status & WBSD_CARDTRAFFIC); /* * Do we expect a reply? */ if ((cmd->flags & MMC_RSP_MASK) != MMC_RSP_NONE) { /* * Read back status. */ isr = inb(host->base + WBSD_ISR); /* Card removed? */ if (isr & WBSD_INT_CARD) cmd->error = MMC_ERR_TIMEOUT; /* Timeout? */ else if (isr & WBSD_INT_TIMEOUT) cmd->error = MMC_ERR_TIMEOUT; /* CRC? */ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC)) cmd->error = MMC_ERR_BADCRC; /* All ok */ else { if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_SHORT) wbsd_get_short_reply(host, cmd); else wbsd_get_long_reply(host, cmd); } } /* * Restore interrupt mask to previous value. */ outb(eir, host->base + WBSD_EIR); /* * Call the interrupt routine to jump start * interrupts. */ wbsd_irq(0, host, NULL); DBGF("Sent cmd (%x), res %d\n", cmd->opcode, cmd->error);}/* * Data functions */static void wbsd_empty_fifo(struct wbsd_host* host){ struct mmc_data* data = host->mrq->cmd->data; char* buffer; /* * Handle excessive data. */ if (data->bytes_xfered == host->size) return; buffer = wbsd_kmap_sg(host) + host->offset; /* * Drain the fifo. This has a tendency to loop longer * than the FIFO length (usually one block). */ while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_EMPTY)) { *buffer = inb(host->base + WBSD_DFR); buffer++; host->offset++; host->remain--; data->bytes_xfered++; /* * Transfer done? */ if (data->bytes_xfered == host->size) { wbsd_kunmap_sg(host); return; } /* * End of scatter list entry? */ if (host->remain == 0) { wbsd_kunmap_sg(host); /* * Get next entry. Check if last. */ if (!wbsd_next_sg(host)) { /* * We should never reach this point. * It means that we're trying to * transfer more blocks than can fit * into the scatter list. */ BUG_ON(1); host->size = data->bytes_xfered; return; } buffer = wbsd_kmap_sg(host); } } wbsd_kunmap_sg(host);}static void wbsd_fill_fifo(struct wbsd_host* host){ struct mmc_data* data = host->mrq->cmd->data; char* buffer; /* * Check that we aren't being called after the * entire buffer has been transfered. */ if (data->bytes_xfered == host->size) return; buffer = wbsd_kmap_sg(host) + host->offset; /* * Fill the fifo. This has a tendency to loop longer * than the FIFO length (usually one block). */ while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_FULL)) { outb(*buffer, host->base + WBSD_DFR); buffer++; host->offset++; host->remain--; data->bytes_xfered++; /* * Transfer done? */ if (data->bytes_xfered == host->size) { wbsd_kunmap_sg(host); return; } /* * End of scatter list entry? */ if (host->remain == 0) { wbsd_kunmap_sg(host); /* * Get next entry. Check if last. */ if (!wbsd_next_sg(host)) { /* * We should never reach this point. * It means that we're trying to * transfer more blocks than can fit * into the scatter list. */ BUG_ON(1); host->size = data->bytes_xfered; return; } buffer = wbsd_kmap_sg(host); } } wbsd_kunmap_sg(host);}static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data){ u16 blksize; u8 setup; unsigned long dmaflags; DBGF("blksz %04x blks %04x flags %08x\n", 1 << data->blksz_bits, data->blocks, data->flags); DBGF("tsac %d ms nsac %d clk\n", data->timeout_ns / 1000000, data->timeout_clks); /* * Calculate size. */ host->size = data->blocks << data->blksz_bits; /* * Check timeout values for overflow. * (Yes, some cards cause this value to overflow). */ if (data->timeout_ns > 127000000) wbsd_write_index(host, WBSD_IDX_TAAC, 127); else wbsd_write_index(host, WBSD_IDX_TAAC, data->timeout_ns/1000000); if (data->timeout_clks > 255) wbsd_write_index(host, WBSD_IDX_NSAC, 255); else wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks); /* * Inform the chip of how large blocks will be * sent. It needs this to determine when to * calculate CRC. * * Space for CRC must be included in the size. */ blksize = (1 << data->blksz_bits) + 2; wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0); wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); /* * Clear the FIFO. This is needed even for DMA * transfers since the chip still uses the FIFO * 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); else set_dma_mode(host->dma, DMA_MODE_WRITE); 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_SINGLE | 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; WARN_ON(host->mrq == NULL); /* * Send a stop command if needed. */ if (data->stop) wbsd_send_command(host, data->stop); /* * 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 DRIVER_NAME ": Incomplete DMA " "transfer. %d bytes left.\n", 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; } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -