📄 bvd_mmc.c
字号:
/* * drivers/mmc/bvd_mmc.c * Low-level MMC/SD functions for the Intel Bulverde MMC/SD on-chip controller * Mostly based on: drivers/mmc/omap_mmc.c * * Copyright 2003 MontaVista Software Inc. * Author: MontaVista Software, Inc. * source@mvista.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. * * References to technical documents used in comments: * [1] - Intel(R) Bulverde Processor (B-Stepping) Developer's Manual *//* * Copyright 2004 Motorola, Inc. All Rights Reserved. * Revision History: Modification Changed by Date Description of Changes---------------- ------------ -------------------------Zhu Zhifu 04/05/2004 change for write protectionzhou qiong 06/08/2004 change for pm callbackJiang Lili 08/24/2005 change for add card detect when call pm resumeJiang Lili 10/25/2005 change for sleep power consume isue*/#include <linux/module.h>#include <linux/version.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/delay.h>#include <linux/timer.h>#include <linux/pm.h>#include <linux/mmc/mmc_ll.h>#include <linux/pci.h>#include <asm/irq.h> #include <asm/unaligned.h>#include <asm/io.h>#include <asm/arch/hardware.h>#include <asm/arch/mainstone.h>#include <asm/arch/ezx.h>#include <asm/dma.h>#ifdef CONFIG_ARCH_EZX_HAINANextern u8 ezx_mmc_get_slot_state(void);#endif #define MMC_CLK_OFF_TIMEOUT 1000#define MMC_COMMAND_TIMEOUT HZ/* Device-specific data */typedef struct bvd_mmc_data { struct mmc_request * request; int sd; int dma_rx_ch; int dma_tx_ch; u32 event_mask; int use_4bit; struct timer_list timeout;} bvd_mmc_data_t;#define MMC_EVENT_RESPONSE 0x01 /* Command response */#define MMC_EVENT_DATA_DONE 0x02 /* Data Transfer done */#define MMC_EVENT_RX_DMA_DONE 0x04 /* Rx DMA done */#define MMC_EVENT_TX_DMA_DONE 0x08 /* Tx DMA done */#define MMC_EVENT_PROG_DONE 0x10 /* Programming is done */#define MMC_EVENT_CLK_OFF 0x80 /* Clock is off */static bvd_mmc_data_t bvd_mmc_data;extern void ezx_detect_handler(unsigned long data);static int mmc_crc_error = 0;#ifdef CONFIG_ARCH_EZX_E680/* dont call hotplug funciton if phone resumes from sleeping */ static void e680_reconfig_gpio(){ set_GPIO_mode(GPIO_MMC_CLK | GPIO_ALT_FN_2_OUT); set_GPIO_mode(GPIO_MMC_CMD | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA0 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA1 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA2 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA3 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT);}#endif/* Stop the MMC clock and wait while it is happens */static inline int bvd_mmc_stop_clock_and_wait(void){ int timeout; DEBUG(2, "stop MMC clock\n"); MMC_STRPCL = MMC_STRPCL_STOP_CLK; for (timeout = 0; !(MMC_I_REG & MMC_I_CLK_IS_OFF); timeout++) { if (timeout > MMC_CLK_OFF_TIMEOUT) { DEBUG(3, "Timeout on stop clock waiting\n"); return MMC_ERROR_TIMEOUT; } udelay(1); } DEBUG(2, "clock off time is %d microsec\n", timeout); return MMC_NO_ERROR;}/* Stop the clock */static inline void bvd_mmc_stop_clock(void){ MMC_STRPCL = MMC_STRPCL_STOP_CLK;}/* Start the MMC clock */static inline int bvd_mmc_start_clock(void){ DEBUG(2, "start MMC clock\n"); MMC_STRPCL = MMC_STRPCL_STRT_CLK; return MMC_NO_ERROR;}/* Select the MMC clock frequency */int bvd_mmc_set_clock(u32 rate){ DEBUG(2, "set clock to %u Hz\n", rate); bvd_mmc_stop_clock_and_wait(); if (rate < 304000) return MMC_ERROR_OUT_OF_RANGE; /* It seems, MMC controller can not operate correctly at highest clock frequency */ MMC_CLKRT = (rate >= 19500000 ? MMC_CLKRT_FREQ_19_5MHZ : rate >= 9750000 ? MMC_CLKRT_FREQ_9_75MHZ : rate >= 4880000 ? MMC_CLKRT_FREQ_4_88MHZ : rate >= 2440000 ? MMC_CLKRT_FREQ_2_44MHZ : rate >= 1220000 ? MMC_CLKRT_FREQ_1_22MHZ : rate >= 609000 ? MMC_CLKRT_FREQ_609KHZ : MMC_CLKRT_FREQ_304KHZ); return MMC_NO_ERROR;}/* Initialize the MMC controller to up the slot, assuming the card is in slot */void bvd_mmc_slot_up(void) { DEBUG(2, "Init MMC h/w\n"); /* Turn on core clock signal for the MMC controller ([2], 3.8.2.2) */ CKEN |= CKEN12_MMC; DEBUG(3, " ...core MMC clock OK\n"); /* Configure MMCLK bus clock output signal ([2], 15.3, 24.4.2) */ set_GPIO_mode(GPIO_MMC_CLK | GPIO_ALT_FN_2_OUT); DEBUG(3, " ...MMCLK signal OK\n"); /* Configure MMCMD command/response bidirectional signal ([2], 15.3, 24.4.2) */ set_GPIO_mode(GPIO_MMC_CMD | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); DEBUG(3, " ...MMCMD signal OK\n"); /* Configure MMDAT[0123] data bidirectional signals ([2], 15.3, 24.4.2) */ set_GPIO_mode(GPIO_MMC_DATA0 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); // for dat3 used to detect card insert and remove on A780, // so we only use 1 bit transfer mode ---zq#ifndef CONFIG_ARCH_EZX_A780 //from Barbados P3, we will use 4 bit mode --jll set_GPIO_mode(GPIO_MMC_DATA1 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA2 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT); set_GPIO_mode(GPIO_MMC_DATA3 | GPIO_ALT_FN_1_IN | GPIO_ALT_FN_1_OUT);#endif DEBUG(3, " ...MMDATx signals OK\n"); /* One of Intel's hardware hackers recommend me to wait 1ms here. Ok, it's not so complex :-) */ mdelay(1); /* * Ok, looks like basic h/w initialized, let's talk with MMC itself */ /* Stop the MMC clock before 1st command ([2], 15.6) */ bvd_mmc_stop_clock_and_wait(); enable_irq(IRQ_MMC); DEBUG(2, "MMC h/w initialized\n");}/* Shut down the slot */void bvd_mmc_slot_down(void){ DEBUG(2, "down MMC h/w\n"); /* Turn off core clock signal for the MMC controller ([2], 3.8.2.2) */ CKEN &= ~CKEN12_MMC;}/* Halt Tx and Rx DMA channels */static inline void bvd_mmc_dma_halt(struct bvd_mmc_data *mmc){ DCSR(mmc->dma_tx_ch) = DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR; DCSR(mmc->dma_rx_ch) = DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR;}/* Establish set of events we are waiting for command completion */static void bvd_mmc_wait_for(u32 event_set){ unsigned long flags; local_irq_save(flags); bvd_mmc_data.event_mask = event_set; MMC_I_MASK = MMC_I_ALL & ~((event_set & MMC_EVENT_DATA_DONE ? MMC_I_DATA_TRAN_DONE : 0) | (event_set & MMC_EVENT_RESPONSE ? MMC_I_END_CMD_RES : 0) | (event_set & MMC_EVENT_PROG_DONE ? MMC_I_PRG_DONE : 0) | (event_set == MMC_EVENT_CLK_OFF ? MMC_I_CLK_IS_OFF : 0) | (event_set != 0 ? MMC_I_RES_ERR | MMC_I_DAT_ERR | MMC_I_TINT : 0)); local_irq_restore(flags);}/* Complete the request processing */static inline void bvd_mmc_request_complete(struct bvd_mmc_data *mmc, enum mmc_result_t result){ struct mmc_request *req = mmc->request; unsigned int flags; local_irq_save(flags); if (req != NULL) { bvd_mmc_dma_halt(mmc); del_timer(&mmc->timeout); req->result = result; mmc_cmd_complete(req); mmc->request = NULL; bvd_mmc_wait_for(0); } local_irq_restore(flags); if (result != MMC_NO_ERROR) bvd_mmc_stop_clock_and_wait();}/* Trigger the event(s). Complete commands if all expected events are occured */static void bvd_mmc_event(u32 event){ unsigned long flags; u32 events; struct bvd_mmc_data *mmc = &bvd_mmc_data; local_irq_save(flags); events = mmc->event_mask & ~event; bvd_mmc_wait_for(events); local_irq_restore(flags); if (events == MMC_EVENT_CLK_OFF) bvd_mmc_stop_clock(); else if (events == 0) { if (mmc_crc_error) bvd_mmc_request_complete(mmc, MMC_ERROR_CRC); else bvd_mmc_request_complete(mmc, MMC_NO_ERROR); }}static void bvd_mmc_dma_rx_start(struct bvd_mmc_data *mmc);/* Handle DMA data receive completion */static void bvd_mmc_dma_rx_callback(int channel, void *data, struct pt_regs *regs){ struct bvd_mmc_data *mmc = data; struct mmc_request *request = mmc->request; DEBUG(3, "DMA RX Callback: DCSR 0x%08x MMC_STAT 0x%08x " "I_REG 0x%08x I_MASK 0x%08x nob %d\n", DCSR(channel), MMC_STAT, MMC_I_REG, MMC_I_MASK, request->nob); request->buffer += request->block_len; if (DCSR(channel) & DCSR_BUSERR) { printk(KERN_DEBUG "bvd_mmc: MMC rx dma bus error.\n"); } DCSR(channel) = DCSR_STARTINTR | DCSR_ENDINTR | DCSR_BUSERR; if (--request->nob > 0) { bvd_mmc_dma_rx_start(mmc); } else { bvd_mmc_event(MMC_EVENT_RX_DMA_DONE); }}/* Prepare DMA to start data transfer from the MMC card */static void bvd_mmc_dma_rx_start(struct bvd_mmc_data *mmc){ struct mmc_request *request = mmc->request; int channel = mmc->dma_rx_ch; dma_addr_t dma_addr = virt_to_bus(request->buffer); DEBUG(3, "MMC DMA Rx start: chan %d buf 0x%08x phys 0x%08x " "blklen %d nob%d\n", channel, (u32)request->buffer, (u32)dma_addr, request->block_len, request->nob); consistent_sync(request->buffer, request->block_len, PCI_DMA_FROMDEVICE); DCSR(channel) = DCSR_NODESC; DSADR(channel) = __PREG(MMC_RXFIFO); DTADR(channel) = dma_addr; DCMD(channel) = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_WIDTH1 | DCMD_BURST32 | DCMD_ENDIRQEN | (request->block_len & DCMD_LENGTH); DRCMRRXMMC = (channel & DRCMR_CHLNUM) | DRCMR_MAPVLD; DCSR(channel) |= DCSR_RUN;}static void bvd_mmc_dma_tx_start(struct bvd_mmc_data *mmc);/* Handle transmit DMA competion */static void bvd_mmc_dma_tx_callback(int channel, void *data, struct pt_regs *regs){ struct bvd_mmc_data *mmc = data; struct mmc_request *request = mmc->request; DEBUG(3, "DMA TX Callback\n"); request->buffer += request->block_len; if (DCSR(channel) & DCSR_BUSERR) { printk(KERN_DEBUG "bvd_mmc: MMC tx dma bus error.\n"); } DCSR(channel) = DCSR_STARTINTR | DCSR_ENDINTR | DCSR_BUSERR; if (--request->nob > 0) bvd_mmc_dma_tx_start(mmc); else bvd_mmc_event(MMC_EVENT_TX_DMA_DONE);}/* Prepare DMA to start data transfer to the MMC card */static void bvd_mmc_dma_tx_start(struct bvd_mmc_data *mmc){ struct mmc_request *request = mmc->request; int channel = mmc->dma_tx_ch; dma_addr_t dma_addr = virt_to_bus(request->buffer); DEBUG(3, "MMC DMA Tx start: chan %d buf 0x%08x phys 0x%08x " "blklen %d nob%d\n", channel, (u32)request->buffer, (u32)dma_addr, request->block_len, request->nob); consistent_sync(request->buffer, request->block_len, PCI_DMA_TODEVICE); DCSR(channel) = DCSR_NODESC; DTADR(channel) = __PREG(MMC_TXFIFO); DSADR(channel) = dma_addr; DCMD(channel) = DCMD_INCSRCADDR | DCMD_FLOWTRG | DCMD_WIDTH1 | DCMD_BURST32 | DCMD_ENDIRQEN | (request->block_len & DCMD_LENGTH); DRCMRTXMMC = (channel & DRCMR_CHLNUM) | DRCMR_MAPVLD; DCSR(channel) |= DCSR_RUN;}/* Prepare MMC controller for card command execution */static int bvd_mmc_exec_command(struct mmc_request *request){ u32 cmdat = 0; u32 nob = 1; /* use 4-bit bus width when possible */ if (bvd_mmc_data.use_4bit) cmdat |= MMC_CMDAT_SD_4DAT; switch (request->cmd) { /* MMC core extra command */ case MMC_CIM_RESET: cmdat |= MMC_CMDAT_INIT; break; /* bc - broadcast - no response */ case MMC_GO_IDLE_STATE: case MMC_SET_DSR: break; /* bcr - broadcast with response */ case MMC_SEND_OP_COND: case MMC_ALL_SEND_CID: case MMC_GO_IRQ_STATE: break; /* adtc - addressed with data transfer */ case MMC_READ_DAT_UNTIL_STOP: case MMC_READ_SINGLE_BLOCK: case MMC_READ_MULTIPLE_BLOCK: cmdat |= MMC_CMDAT_DATA_EN | MMC_CMDAT_RD | MMC_CMDAT_DMA_EN; break; case SEND_SCR: cmdat |= MMC_CMDAT_DATA_EN | MMC_CMDAT_RD; break; case MMC_WRITE_DAT_UNTIL_STOP: case MMC_WRITE_BLOCK: case MMC_WRITE_MULTIPLE_BLOCK: case MMC_PROGRAM_CID: case MMC_PROGRAM_CSD: case MMC_SEND_WRITE_PROT: case MMC_GEN_CMD: cmdat |= MMC_CMDAT_DATA_EN | MMC_CMDAT_WR | MMC_CMDAT_DMA_EN; break; case MMC_LOCK_UNLOCK: cmdat |= MMC_CMDAT_DATA_EN | MMC_CMDAT_WR; break; case MMC_STOP_TRANSMISSION: cmdat |= MMC_CMDAT_STOP_TRAN; break; /* ac - no data transfer */ default: break; } switch (request->cmd) { case MMC_READ_MULTIPLE_BLOCK: case MMC_WRITE_MULTIPLE_BLOCK: nob = request->nob; break; default: nob = 1; } switch (request->rtype) { case RESPONSE_NONE: cmdat |= MMC_CMDAT_RES_NORESP; break; case RESPONSE_R1B: cmdat |= MMC_CMDAT_BUSY; /*FALLTHRU*/ case RESPONSE_R1: case RESPONSE_R4: case RESPONSE_R5: case RESPONSE_R6: cmdat |= MMC_CMDAT_RES_RESP; break; case RESPONSE_R3: cmdat |= MMC_CMDAT_RES_R3; break; case RESPONSE_R2_CID: case RESPONSE_R2_CSD: cmdat |= MMC_CMDAT_RES_R2; break; default: break; } /* Set command index */ if (request->cmd == MMC_CIM_RESET) { MMC_CMD = MMC_GO_IDLE_STATE & MMC_CMD_MASK; } else { MMC_CMD = request->cmd & MMC_CMD_MASK; } /* Set argument */ MMC_ARGL = request->arg & MMC_ARGL_MASK; MMC_ARGH = (request->arg >> 16) & MMC_ARGH_MASK; if (request->cmd == SEND_SCR) { MMC_BLKLEN = 8; MMC_NOB = 1; } else { MMC_BLKLEN = request->block_len & MMC_BLKLEN_MASK; MMC_NOB = nob & MMC_NOB_MASK; } MMC_RDTO = ~0 & MMC_RDTO_MASK; MMC_RESTO = ~0 & MMC_RESTO_MASK; MMC_CMDAT = cmdat; /* Send command */ DEBUG(3, ": Send cmd %d cmdat: %x arg: %d resp %d\n", request->cmd, cmdat, request->arg, request->rtype);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -