📄 omap_hsmmc.c
字号:
/* * drivers/mmc/omap_hsmmc.c * * Driver for OMAP2430/3430 MMC controller. * * Copyright (C) 2006-2007 Texas Instruments, Inc * Author: Texas Instruments * * 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. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */#include <linux/module.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/dma-mapping.h>#include <linux/platform_device.h>#include <linux/timer.h>#include <linux/i2c.h>#include <linux/mmc/mmc.h>#include <linux/mmc/sd.h>#include <linux/mmc/sdio.h>#include <linux/mmc/host.h>#include <linux/mmc/core.h>#include <linux/mmc/card.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/dma.h>#include <asm/mach-types.h>#include <asm/arch/twl4030.h>#include <asm/hardware.h>#include <asm/arch/board.h>#include <asm/arch/cpu.h>#include <asm/arch/clock.h>#include <asm/semaphore.h>#include "omap_hsmmc.h"#include <asm/scatterlist.h>#include <linux/notifier.h>#include <linux/pm.h>/* TAG for Aggressive Power changes in MMC *///#define AGGR_PM_CAP 1#undef AGGR_PM_CAP#define mmc_clk_enable_aggressive(host) /* NULL */#define mmc_clk_disable_aggressive(host) /* NULL */extern int enable_mmc_power(int slot);extern int disable_mmc_power(int slot);extern int mask_carddetect_int(int slot);extern int unmask_carddetect_int(int slot);extern int setup_mmc_carddetect_irq(int irq);extern int switch_power_mode(int power_mode);extern ssize_t mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr, char *buf);extern ssize_t set_mmc_carddetect(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);DEVICE_ATTR(mmc_cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);DEVICE_ATTR(mmc_card_detect, S_IWUSR, NULL, set_mmc_carddetect);#define MMC_CARD_NONE 4#define MAX_CRC_RETRY 1struct mmc_omap_host *saved_host1, *saved_host2;struct mmc_omap_host { int suspended; struct mmc_host *mmc; struct mmc_request *mrq; struct mmc_command *cmd; struct mmc_data *data; struct timer_list detect_timer; struct resource *mem_res; void __iomem *base; void *mapbase; struct clk *fclk, *iclk, *dbclk; /* Required for a 3430 ES1.0 Sil errata fix */ struct clk *gptfck; unsigned int id; int irq; int card_detect_irq; unsigned char bus_mode; struct semaphore sem; unsigned char datadir; u32 *buffer; u32 bytesleft; int use_dma, dma_ch; unsigned int dma_len; unsigned int sg_dma_len; unsigned int dma_dir; int chain_id; struct omap_dma_channel_params params; u32 chains_requested;/* Number of chains to be requested */ u32 extra_chain_reqd;/* if there is a need of last chaining*/ u32 no_of_chain_reqd;/*No of times callback called*/ u32 current_cb_cnt; int brs_received; int dma_done; int dma_is_read; spinlock_t dma_lock; unsigned int sg_len; int sg_idx; u32 buffer_bytes_left; u32 total_bytes_left; struct work_struct mmc_carddetect_work; int initstream; /*Added for CRC retry*/ bool card_detected; u32 rca, mod_addr, org_addr; int is_high_capacity; int flag_err, cmd_12,cmd_13, crc_retry;};static spinlock_t mmc_gpt_lock;static int gptfclk_counter;static int mmc_clk_counter [NO_OF_MMC_HOSTS];#define OMAP_MMC_STAT_BRR 1 << 5#define OMAP_MMC_STAT_BWR 1 << 4#define NO_OF_DMA_CHAINS_USED 2//This will work with 2 chains currentlystatic void mmc_chain_dma(struct mmc_omap_host *host, struct mmc_data *data);static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data);static int mmc_clk_enable (struct mmc_omap_host *host){ int hostid = host->id - 1; /* 3430-ES1.0 Sil errata fix */ if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { spin_lock(&mmc_gpt_lock); if (!gptfclk_counter) { if (clk_enable(host->gptfck) != 0) { dev_dbg(mmc_dev(host->mmc), "Unable to enable gptfck clock \n"); goto clk_en_err; } } gptfclk_counter ++; spin_unlock(&mmc_gpt_lock); } if (!mmc_clk_counter[hostid]) { if (clk_enable(host->iclk) != 0) goto clk_en_err1; if (clk_enable(host->fclk) != 0) goto clk_en_err2; } mmc_clk_counter[hostid] ++; return 0;clk_en_err2: /* On fclk failure */ clk_disable(host->iclk);clk_en_err1: /* On iclk failure */ if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { spin_lock(&mmc_gpt_lock); gptfclk_counter --; if (!gptfclk_counter) clk_disable(host->gptfck); spin_unlock(&mmc_gpt_lock); }clk_en_err: dev_dbg(mmc_dev(host->mmc), "Unable to enable MMC clocks \n"); spin_unlock(&mmc_gpt_lock); return -1;}static int mmc_clk_disable (struct mmc_omap_host *host){ int hostid = host->id - 1; mmc_clk_counter[hostid] --; if (!mmc_clk_counter[hostid]) { clk_disable(host->fclk); clk_disable(host->iclk); } /* 3430-ES1.0 Sil errata fix */ if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) { spin_lock(&mmc_gpt_lock); gptfclk_counter --; if (!gptfclk_counter) clk_disable(host->gptfck); spin_unlock(&mmc_gpt_lock); } return 0;}/* * Stop clock to the card */static void omap_mmc_stop_clock(struct mmc_omap_host *host){ /* Stop clock to the card */ OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN); if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0) dev_dbg(mmc_dev(host->mmc), "MMC clock not stoped," "clock freq can not be altered\n");}/* * Send init stream sequence to the card before sending IDLE command */static void send_init_stream(struct mmc_omap_host *host){ int reg = 0, status; typeof(jiffies) timeout; disable_irq(host->irq); OMAP_HSMMC_WRITE(host->base, ISE, INT_CLEAR); OMAP_HSMMC_WRITE(host->base, IE, INT_CLEAR); OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM); OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD); timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); while ((reg != CC) && time_before(jiffies, timeout)) { reg = OMAP_HSMMC_READ(host->base, STAT) & CC; } OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM); status = OMAP_HSMMC_READ(host->base, STAT); OMAP_HSMMC_WRITE(host->base, STAT, status); enable_irq(host->irq);}/* * Configure the resptype, cmdtype and send the given command to the card */static voidmmc_omap_start_command18(struct mmc_omap_host *host, struct mmc_command *cmd){ int cmdreg = 0, resptype = 0, cmdtype = 0; mmc_clk_enable_aggressive(host); /* Clear status bits and enable interrupts */ OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR); resptype = 2; cmdreg = (MMC_READ_MULTIPLE_BLOCK << 24) | (resptype << 16) | (cmdtype << 22); cmdreg |= DP_SELECT | DDIR | MSBS | BCE | DMA_EN; OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); OMAP_HSMMC_WRITE(host->base, ARG, host->mod_addr); OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);}/* * Configure the resptype, cmdtype and send the given command to the card */static voidmmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd){ int cmdreg = 0, resptype = 0, cmdtype = 0; dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n", mmc_hostname(host->mmc), cmd->opcode, cmd->arg); host->cmd = cmd; mmc_clk_enable_aggressive(host); /* Clear status bits and enable interrupts */ OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR); switch (RSP_TYPE(mmc_resp_type(cmd))) { case RSP_TYPE(MMC_RSP_R1): case RSP_TYPE(MMC_RSP_R3): /* resp 1, resp 1b */ resptype = 2; break; case RSP_TYPE(MMC_RSP_R2): resptype = 1; break; default: break; } cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22); if (cmd->opcode == MMC_SEND_CSD) host->rca = cmd->arg; if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK) host->org_addr = cmd->arg; if (cmd->opcode == MMC_READ_SINGLE_BLOCK || cmd->opcode == MMC_READ_MULTIPLE_BLOCK || cmd->opcode == SD_APP_SEND_SCR || (cmd->opcode == SD_SWITCH && cmd->arg == 0xfffff1) || (cmd->opcode == SD_SWITCH && cmd->arg == 0x80fffff1) || (cmd->opcode == MMC_SEND_EXT_CSD && cmd->arg == 0)) { if (host->use_dma) cmdreg |= DP_SELECT | DDIR | MSBS | BCE | DMA_EN; else cmdreg |= DP_SELECT | DDIR | MSBS | BCE; } else if (cmd->opcode == MMC_WRITE_BLOCK || cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) { if (host->use_dma) cmdreg |= DP_SELECT | MSBS | BCE | DMA_EN; else cmdreg |= DP_SELECT | MSBS | BCE; cmdreg &= ~(DDIR); } if (cmd->opcode == MMC_GO_IDLE_STATE || cmd->opcode == MMC_SEND_OP_COND || cmd->opcode == MMC_ALL_SEND_CID) OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) | OD); if (cmd->opcode == MMC_GO_IDLE_STATE) { if (host->initstream == 0) { send_init_stream(host); host->initstream = 1; } } OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg); OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);}/* * Notify the xfer done on MMC/SD cards to the core */static voidmmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data){ if(host->use_dma) { /* Un-map the memory required for DMA */ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len, host->dma_dir); } /* Reset the variables as transfer is complete */ host->data = NULL; host->sg_len = 0; host->sg_dma_len = 0; host->datadir = OMAP_MMC_DATADIR_NONE; if (data->error == MMC_ERR_NONE) data->bytes_xfered += data->blocks * (data->blksz); else data->bytes_xfered = 0; mmc_clk_disable_aggressive(host); if (!data->stop) { host->mrq = NULL; mmc_request_done(host->mmc, data->mrq); return; } /* Send the stop command. Remember FCLK is not stopped before this call */ mmc_omap_start_command(host, data->stop);}static voidmmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data){ unsigned long flags; int done; if (!host->use_dma) { mmc_omap_xfer_done(host, data); return; } done = 0; spin_lock_irqsave(&host->dma_lock, flags); if (host->dma_done) done = 1; else host->brs_received = 1; spin_unlock_irqrestore(&host->dma_lock, flags); if (done) mmc_omap_xfer_done(host, data);}static voidmmc_omap_dma_done(struct mmc_omap_host *host, struct mmc_data *data){ unsigned long flags; int done; done = 0; spin_lock_irqsave(&host->dma_lock, flags); if (host->brs_received) done = 1; else host->dma_done = 1; spin_unlock_irqrestore(&host->dma_lock, flags); if (done) mmc_omap_xfer_done(host, data);}/* * Notify the core about command completion */static voidmmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd){ host->cmd = NULL; if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { /* response type 2 */ cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10); cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32); cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54); cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76); } else { /* response types 1, 1b, 3, 4, 5, 6 */ cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10); } } if(cmd->opcode == SD_APP_OP_COND) { if(cmd->resp[0] & 0x40000000) { host->is_high_capacity = 1; } else { host->is_high_capacity = 0; } } if(cmd->opcode == MMC_SEND_OP_COND) { if(cmd->resp[0] & 0x40000000) { host->is_high_capacity = 1; } else { host->is_high_capacity = 0; } } if (host->data == NULL || cmd->error != MMC_ERR_NONE) { dev_dbg(mmc_dev(host->mmc), "%s: End request, err %x\n", mmc_hostname(host->mmc), cmd->error); host->mrq = NULL; mmc_clk_disable_aggressive(host); mmc_request_done(host->mmc, cmd->mrq); }}/* * Dma cleaning in case of command errors */static void mmc_dma_cleanup(struct mmc_omap_host *host){ int dma_ch; host->data->error |= MMC_ERR_TIMEOUT; if(host->mmc->mode == MMC_MODE_SDIO) { if (host->use_dma && host->dma_ch != -1) { dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len, host->dma_dir); dma_ch = host->dma_ch; host->dma_ch = -1; omap_free_dma(dma_ch); } } else { if (host->use_dma) { omap_stop_dma_chain_transfers(host->chain_id); dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->sg_len, host->dma_dir); omap_free_dma_chain(host->chain_id); mmc_omap_dma_done(host, host->data); host->chain_id = -1; } } host->data = NULL; host->datadir = OMAP_MMC_DATADIR_NONE;}/* PIO only */static voidmmc_omap_sg_to_buf(struct mmc_omap_host *host){ struct scatterlist *sg; sg = host->data->sg + host->sg_idx; host->buffer_bytes_left = sg->length; host->buffer = page_address(sg->page) + sg->offset; if (host->buffer_bytes_left > host->total_bytes_left) host->buffer_bytes_left = host->total_bytes_left;}/* PIO only */static voidmmc_omap_xfer_data(struct mmc_omap_host *host, int write){ int n; if (host->buffer_bytes_left == 0) { host->sg_idx++; BUG_ON(host->sg_idx == host->sg_len); mmc_omap_sg_to_buf(host); } n = 64; if (n > host->buffer_bytes_left) n = host->buffer_bytes_left; host->buffer_bytes_left -= n; host->total_bytes_left -= n; host->data->bytes_xfered += n;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -