| /* |
| * |
| * Marvell Orion SD\MMC\SDIO driver |
| * |
| * Author: Maen Suleiman |
| * Copyright (C) 2008 Marvell Ltd. |
| * |
| * 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. |
| * |
| */ |
| /* |
| * mvsdmmc TODO list: |
| * |
| * --> Set timeout value according to timeout_ns and timeout_clks, |
| * meanwhile a maximum value is set for the host |
| * |
| * --> Report errors in mrq->data->stop->error and in mrq->data->error, |
| * meanwhile errors are reported in the command itself since always the |
| * completion of data and AutoCmd12 are done with the originated command. |
| * |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/version.h> |
| #include <linux/init.h> |
| #include <linux/ioport.h> |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| #include <linux/mbus.h> |
| #endif |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/scatterlist.h> |
| #include <linux/mmc/host.h> |
| #include <linux/proc_fs.h> |
| #include <linux/irq.h> |
| |
| |
| #include <asm/dma.h> |
| #include <asm/sizes.h> |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| #include <asm/plat-orion/mvsdmmc-orion.h> |
| #endif |
| |
| #include "ctrlEnv/mvCtrlEnvLib.h" |
| #include "mvsdmmc.h" |
| |
| #undef MVSDMMC_DEBUG |
| #undef MVSDMMC_DUMP_REGS_ON_CMD |
| #define MVSDMMC_DUMP_ALL_REGS_ON_ERROR |
| #define MVSDMMC_DBG_ERROR |
| |
| #undef MVSDMMC_DBG_FUNC_ENTRY |
| #ifdef MVSDMMC_DBG_FUNC_ENTRY |
| #define mvsdmmc_dbg_enter() printk(KERN_DEBUG "ENTER <=%s\n", __func__) |
| #define mvsdmmc_dbg_exit() printk(KERN_DEBUG "EXIT <=%s\n", __func__) |
| #else |
| #define mvsdmmc_dbg_enter() |
| #define mvsdmmc_dbg_exit() |
| #endif |
| |
| #undef MVSDMMC_WARN |
| #ifdef MVSDMMC_WARN |
| #define mvsdmmc_warning(host, fmt, arg...) \ |
| dev_printk(KERN_INFO, &host->pdev->dev, fmt, ##arg) |
| #else |
| #define mvsdmmc_warning(host, a...) |
| #endif |
| #ifdef MVSDMMC_DEBUG |
| #define mvsdmmc_debug(host, fmt, arg...) \ |
| dev_printk(KERN_DEBUG, &host->pdev->dev, fmt, ##arg) |
| #else |
| #define mvsdmmc_debug(host, a...) |
| #endif |
| |
| #if defined(MVSDMMC_DBG_ERROR) |
| #define mvsdmmc_debug_error(host, fmt, arg...) \ |
| dev_printk(KERN_ERR, &host->pdev->dev, fmt, ##arg) |
| #else |
| #define mvsdmmc_debug_error(host, a...) |
| #endif |
| |
| #define DRIVER_NAME "mvsdmmc" |
| |
| |
| struct mvsdmmc_host_stat { |
| |
| int total_requests; |
| int copied_data; |
| int total_data; |
| int unaligned_buf; |
| int cache_unaligned_buf; |
| int ints; |
| int error_ints; |
| int card_ints; |
| int unfinished_dma; |
| int buf_256; |
| int cmd_timeout; |
| int int_timeout; |
| int empty_int; |
| int first_int_status; |
| int first_error_status; |
| int detect_int; |
| int first_err_int; /* request number of first error interrupt */ |
| int first_unfinished_dma;/* request number of first unfinished dma */ |
| }; |
| |
| struct mvsdmmc_host { |
| struct mmc_host *mmc; /* associated mmc structure */ |
| struct platform_device *pdev; /* platform device */ |
| spinlock_t lock; /* spin lock of the host */ |
| struct resource *res; /* resource for IRQ and base */ |
| void __iomem *base; /* base address of the host |
| * registers |
| */ |
| int irq; /* host IRQ number */ |
| int irq_detect; /* host IRQ number for |
| * insertion/detection |
| */ |
| char *dma_buffer; /* virtual address for |
| temp buffer*/ |
| dma_addr_t dma_addr; /* Physical address for |
| remp buffer */ |
| unsigned int dma_len; /* sg fragments number |
| * of the request |
| */ |
| int size; /* Total size of transfer */ |
| unsigned char power_mode; /* power status */ |
| struct mmc_request *mrq; /* current mmc request |
| * structure |
| */ |
| struct mmc_command *cmd; /* current mmc command |
| * structure |
| */ |
| struct mmc_data *data; /* current mmc data structure */ |
| unsigned short intr_status; /* interrupt status on IRQ */ |
| unsigned short intr_en; /* enabled interrupts |
| * during command- status |
| */ |
| unsigned short intr_cmd; /* enabled interrupts |
| * during command |
| */ |
| unsigned int cmd_data; /* if cmd\data are proccesed */ |
| #define MVSDMMC_CMD 0x1 |
| #define MVSDMMC_DATA 0x2 |
| #define MVSDMMC_CMD12 0x4 |
| unsigned int pending_commands;/* commands on process*/ |
| int card_present; /* if card present*/ |
| struct timer_list timer; /* Timer for timeouts */ |
| unsigned int copy_buf; /* copy to host buffer*/ |
| unsigned int bad_size; /* unaligned size */ |
| struct mvsdmmc_host_stat stat; /* host statistics */ |
| }; |
| |
| |
| static int maxfreq = MVSDMMC_CLOCKRATE_MAX; |
| static int highspeed = 1; |
| static int detect = 1; |
| static int dump_on_error; |
| |
| static void mvsdmmc_request_done(struct mvsdmmc_host *host); |
| static void mvsdmmc_power_down(struct mmc_host *mmc); |
| static void mvsdmmc_power_up(struct mmc_host *mmc); |
| static void mvsdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); |
| |
| static inline void mvsdmmc_sg_to_dma(struct mvsdmmc_host *host, |
| struct mmc_data *data); |
| static inline void mvsdmmc_dma_to_sg(struct mvsdmmc_host *host, |
| struct mmc_data *data); |
| static irqreturn_t mvsdmmc_irq(int irq, void *dev); |
| static irqreturn_t mvsdmmc_irq_detect(int irq, void *dev); |
| |
| #define mvsdmmc_writew(host, offs, val) \ |
| writew((val), (host->base + offs)) |
| |
| |
| static inline unsigned short mvsdmmc_readw(struct mvsdmmc_host *host, |
| unsigned short offs) |
| { |
| unsigned short val = readw((host->base + offs)); |
| return (val); |
| } |
| |
| #define mvsdmmc_bitset(host, offs, bitmask) \ |
| writew((readw(host->base + offs) | (bitmask)), \ |
| host->base + offs) |
| |
| #define mvsdmmc_bitreset(host, offs, bitmask) \ |
| writew((readw(host->base + offs) & (~(bitmask))), \ |
| host->base + offs) |
| |
| int mvsdmmc_read_procmem(char *buf, char **start, off_t offset, |
| int count, int *eof, void *data) |
| { |
| |
| int len = 0; |
| struct mvsdmmc_host *host = (struct mvsdmmc_host *)data; |
| |
| len += sprintf(buf+len, "\ntotal requests=%d\n", |
| host->stat.total_requests); |
| len += sprintf(buf+len, "total data=%d\n", |
| host->stat.total_data); |
| len += sprintf(buf+len, "copied data=%d\n", host->stat.copied_data); |
| len += sprintf(buf+len, "buffers small than 256=%d\n", |
| host->stat.buf_256); |
| len += sprintf(buf+len, "buffers not word aligned=%d\n", |
| host->stat.unaligned_buf); |
| len += sprintf(buf+len, "buffers not cache line aligned=%d\n", |
| host->stat.cache_unaligned_buf); |
| len += sprintf(buf+len, "total interrupts=%d\n", host->stat.ints); |
| len += sprintf(buf+len, "total card ints=%d\n", host->stat.card_ints); |
| len += sprintf(buf+len, "total error ints=%d\n", host->stat.error_ints); |
| len += sprintf(buf+len, "Request num of first error int=%d\n", |
| host->stat.first_err_int); |
| len += sprintf(buf+len, "Status of int on first error int=0x%x\n", |
| (unsigned int)host->stat.first_int_status); |
| len += sprintf(buf+len, "Status of first error int=0x%x\n", |
| (unsigned int)host->stat.first_error_status); |
| len += sprintf(buf+len, "Empty interrupts=%d\n", host->stat.empty_int); |
| len += sprintf(buf+len, "Detect interrupts=%d\n", |
| host->stat.detect_int); |
| len += sprintf(buf+len, "Unifinished dma=%d\n", |
| host->stat.unfinished_dma); |
| len += sprintf(buf+len, "Request num of first unfinished dma=%d\n", |
| host->stat.first_unfinished_dma); |
| len += sprintf(buf+len, "interrupt timeouts=%d\n", |
| host->stat.int_timeout); |
| len += sprintf(buf+len, "command timeouts=%d\n", |
| host->stat.cmd_timeout); |
| |
| *eof = 1; |
| |
| memset(&host->stat , sizeof(struct mvsdmmc_host_stat), 0); |
| |
| return len; |
| |
| } |
| |
| static void mvsdmmc_dump_registers(struct mvsdmmc_host *host, |
| unsigned short cmdreg) |
| { |
| unsigned int reg; |
| |
| for (reg = SDIO_SYS_ADDR_LOW; reg <= SDIO_AUTO_RSP2; reg += 4) { |
| if (reg == SDIO_CMD) { |
| mvsdmmc_debug(host, "reg 0x%x = 0x%x \n", reg, |
| (unsigned int)cmdreg); |
| continue; |
| } |
| |
| mvsdmmc_debug(host, "reg 0x%x = 0x%x \n", reg, |
| (unsigned int)mvsdmmc_readw(host, reg)); |
| } |
| for (reg = 0x100; reg <= 0x130; reg += 4) { |
| mvsdmmc_debug(host, "reg 0x%x = 0x%x \n", reg, |
| (unsigned int)mvsdmmc_readw(host, reg)); |
| } |
| } |
| |
| |
| static inline u32 mvsdmmc_aligned_size(u32 size) |
| { |
| return ((size + 3) / 4) * 4; |
| } |
| |
| /* |
| * Tx alignment. This is a workaround for problems with |
| * data sizes which are not 4-bytes aligned. |
| * Currently this function only works for LSB_FIRST=0. |
| */ |
| static void mvsdmmc_align_tx_data(struct mvsdmmc_host *host, |
| struct mmc_data *data) |
| { |
| u32 size = data->blocks * data->blksz; |
| u32 aligned_size = mvsdmmc_aligned_size(size); |
| u8 *ptr = (u8 *)sg_virt_addr(data->sg); |
| u8 *end = ptr + aligned_size-4; |
| u32 remainder = size%4; |
| u8 tmp[4]; |
| |
| if (!remainder) |
| return; |
| |
| data->sg->length = aligned_size; |
| memcpy(tmp, end, 4); |
| memset(end, 0, 4); |
| memcpy(end+4-remainder, tmp, remainder); |
| } |
| |
| |
| /* |
| * Rx alignment. This is a workaround for problems with |
| * data sizes which are not 4-bytes aligned. |
| * Currently this function only works for LSB_FIRST=0 |
| */ |
| static void mvsdmmc_align_rx_data(struct mvsdmmc_host *host, |
| struct mmc_data *data) |
| { |
| u32 size = data->blocks * data->blksz; |
| u32 aligned_size = mvsdmmc_aligned_size(size); |
| u8 *ptr = (u8 *)sg_virt_addr(data->sg); |
| u8 *end = ptr + aligned_size-4; |
| u32 remainder = size%4; |
| u8 tmp[4]; |
| |
| if (!remainder) |
| return; |
| |
| data->sg->length = aligned_size; |
| memcpy(tmp, end, 4); |
| memset(end, 0, 4); |
| memcpy(end, &tmp[4-remainder], remainder); |
| } |
| |
| |
| |
| |
| |
| |
| static void mvsdmmc_stop_clock(struct mvsdmmc_host *host) |
| { |
| |
| mvsdmmc_dbg_enter(); |
| mvsdmmc_bitset(host, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); |
| mvsdmmc_dbg_exit(); |
| } |
| |
| static void __mvsdmmc_enable_irq(struct mvsdmmc_host *host, unsigned int mask) |
| { |
| host->intr_en |= mask; |
| mvsdmmc_writew(host, SDIO_NOR_INTR_EN, host->intr_en); |
| host->intr_cmd |= mask; /* intr_cmd is zeroed in mvsdmmc_start_cmd */ |
| if (mask == SDIO_NOR_CARD_INT) |
| mvsdmmc_bitset(host, SDIO_XFER_MODE, |
| SDIO_XFER_MODE_INT_CHK_EN); |
| } |
| static void mvsdmmc_enable_irq(struct mvsdmmc_host *host, unsigned int mask) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&host->lock, flags); |
| __mvsdmmc_enable_irq(host, mask); |
| spin_unlock_irqrestore(&host->lock, flags); |
| } |
| |
| static void __mvsdmmc_disable_irq(struct mvsdmmc_host *host, unsigned int mask) |
| { |
| host->intr_en &= ~mask; |
| mvsdmmc_writew(host, SDIO_NOR_INTR_EN, host->intr_en); |
| if (mask == SDIO_NOR_CARD_INT) |
| mvsdmmc_bitreset(host, SDIO_XFER_MODE, |
| SDIO_XFER_MODE_INT_CHK_EN); |
| } |
| |
| static void mvsdmmc_disable_irq(struct mvsdmmc_host *host, unsigned int mask) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&host->lock, flags); |
| __mvsdmmc_disable_irq(host, mask); |
| spin_unlock_irqrestore(&host->lock, flags); |
| } |
| |
| static void mvsdmmc_setup_data(struct mvsdmmc_host *host, struct mmc_data *data) |
| { |
| char *virt_addr = NULL; |
| dma_addr_t phys_addr = 0; |
| u32 reg; |
| |
| mvsdmmc_dbg_enter(); |
| |
| host->stat.total_data++; |
| |
| /* |
| * Calculate size. |
| */ |
| host->size = data->blocks * data->blksz; |
| |
| host->bad_size = 0; |
| |
| host->copy_buf = 0; |
| |
| if (data->flags & MMC_DATA_READ) { |
| |
| if (host->size & 0x3) |
| mvsdmmc_align_rx_data(host, data); |
| |
| host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, |
| data->sg_len, |
| DMA_FROM_DEVICE); |
| |
| BUG_ON(host->dma_len == 0); |
| phys_addr = sg_dma_address(data->sg); |
| |
| if (host->dma_len > 1) { |
| host->copy_buf = 1; |
| } |
| } |
| |
| if (data->flags & MMC_DATA_WRITE) { |
| |
| if (host->size & 0x3) |
| mvsdmmc_align_tx_data(host, data); |
| |
| host->dma_len = dma_map_sg(mmc_dev(host->mmc), |
| data->sg, data->sg_len, |
| DMA_TO_DEVICE); |
| BUG_ON(host->dma_len == 0); |
| phys_addr = sg_dma_address(data->sg); |
| |
| if (host->dma_len > 1) |
| host->copy_buf = 1; |
| } |
| |
| if (phys_addr & 0x3) |
| host->copy_buf = 1; |
| |
| reg = mvsdmmc_readw(host, SDIO_HOST_CTRL); |
| |
| if (host->bad_size) { |
| reg &= ~SDIO_HOST_CTRL_BIG_ENDIAN; |
| host->copy_buf = 1; |
| } else { |
| reg |= SDIO_HOST_CTRL_BIG_ENDIAN; |
| } |
| mvsdmmc_writew(host, SDIO_HOST_CTRL, reg); |
| |
| if (host->copy_buf) { |
| |
| virt_addr = host->dma_buffer; |
| phys_addr = host->dma_addr; |
| } |
| |
| BUG_ON(host->size > MVSDMMC_DMA_SIZE); |
| |
| if ((data->flags & MMC_DATA_WRITE) && (host->copy_buf)) { |
| /* |
| * Transfer data from the SG list to |
| * the DMA buffer. |
| */ |
| BUG_ON(virt_addr == NULL); |
| mvsdmmc_sg_to_dma(host, data); |
| dma_sync_single_for_device(mmc_dev(host->mmc), |
| host->dma_addr, |
| mvsdmmc_aligned_size(host->size), |
| DMA_TO_DEVICE); |
| |
| } else if ((data->flags & MMC_DATA_READ) && (host->copy_buf)) { |
| |
| dma_sync_single_for_device(mmc_dev(host->mmc), |
| host->dma_addr, |
| mvsdmmc_aligned_size(host->size), |
| DMA_FROM_DEVICE); |
| } |
| |
| mvsdmmc_writew(host, SDIO_BLK_COUNT, data->blocks); |
| mvsdmmc_writew(host, SDIO_SYS_ADDR_LOW, |
| (0xffff & ((unsigned int)phys_addr))); |
| mvsdmmc_writew(host, SDIO_SYS_ADDR_HI, |
| (0xffff & (((unsigned int)phys_addr) >> 16))); |
| |
| mvsdmmc_writew(host, SDIO_BLK_SIZE, data->blksz); |
| |
| mvsdmmc_dbg_exit(); |
| |
| } |
| |
| static void mvsdmmc_start_cmd(struct mvsdmmc_host *host, |
| struct mmc_command *cmd) |
| { |
| |
| struct mmc_data *data = host->mrq->data; |
| struct mmc_request *mrq = cmd->mrq; |
| unsigned short cmdreg = 0, xfer = 0; |
| unsigned short intr_enable = 0; |
| |
| mvsdmmc_dbg_enter(); |
| BUG_ON(host->cmd_data); |
| BUG_ON(host->cmd == NULL); |
| |
| /* disable interrupts */ |
| mvsdmmc_writew(host, SDIO_ERR_INTR_EN, 0); |
| /* clear error status */ |
| mvsdmmc_writew(host, SDIO_ERR_INTR_STATUS, |
| mvsdmmc_readw(host, SDIO_ERR_INTR_STATUS)); |
| |
| BUG_ON(host->intr_en & ~SDIO_NOR_CARD_INT); |
| |
| /* reset host->intr_cmd*/ |
| host->intr_cmd = 0; |
| |
| if (cmd->flags != MMC_RSP_NONE) { |
| intr_enable |= SDIO_NOR_UNEXP_RSP; |
| cmdreg |= SDIO_UNEXPECTED_RESP; |
| } |
| if (cmd->flags & MMC_RSP_OPCODE) |
| cmdreg |= SDIO_CMD_INDX_CHECK; |
| |
| |
| if (cmd->flags == MMC_RSP_NONE) |
| cmdreg |= SDIO_CMD_RSP_NONE; |
| else if (cmd->flags & MMC_RSP_BUSY) |
| cmdreg |= SDIO_CMD_RSP_48BUSY; |
| else if (cmd->flags & MMC_RSP_136) |
| cmdreg |= SDIO_CMD_RSP_136; |
| else if (cmd->flags & MMC_RSP_PRESENT) |
| cmdreg |= SDIO_CMD_RSP_48; |
| |
| if (cmd->flags & MMC_RSP_CRC) |
| cmdreg |= (SDIO_CMD_CHECK_CMDCRC); |
| |
| if (data) { |
| host->data = mrq->data; |
| mvsdmmc_setup_data(host, mrq->data); |
| |
| /* if multiple blocks and need AutoCMD12*/ |
| if (data->stop) { |
| struct mmc_command *stop = data->stop; |
| mvsdmmc_writew(host, SDIO_AUTOCMD12_ARG_LOW, |
| stop->arg & 0xffff); |
| mvsdmmc_writew(host, SDIO_AUTOCMD12_ARG_HI, |
| stop->arg >> 16); |
| mvsdmmc_writew(host, SDIO_AUTOCMD12_INDEX, |
| (stop->opcode << 8) | 3); |
| /* enable autocmd12 interrupt*/ |
| intr_enable |= SDIO_NOR_AUTOCMD12_DONE; |
| |
| xfer |= SDIO_XFER_MODE_AUTO_CMD12; |
| host->cmd_data = (MVSDMMC_CMD | MVSDMMC_CMD12); |
| } else { |
| /* enable data interrupt*/ |
| intr_enable |= SDIO_NOR_DMA_INI; |
| host->cmd_data = (MVSDMMC_CMD|MVSDMMC_DATA); |
| } |
| |
| /* default values */ |
| cmdreg |= SDIO_CMD_CHECK_DATACRC16; |
| cmdreg |= SDIO_CMD_DATA_PRESENT; |
| xfer |= SDIO_XFER_MODE_HW_WR_DATA_EN; |
| |
| if (data->flags & MMC_DATA_READ) |
| xfer |= SDIO_XFER_MODE_TO_HOST; |
| else if (data->flags & MMC_DATA_WRITE) |
| xfer &= ~SDIO_XFER_MODE_TO_HOST; |
| } else { |
| intr_enable |= SDIO_NOR_CMD_DONE; |
| cmdreg &= ~SDIO_CMD_DATA_PRESENT; |
| host->cmd_data = MVSDMMC_CMD; |
| } |
| |
| if (host->intr_en & SDIO_NOR_CARD_INT) |
| xfer |= SDIO_XFER_MODE_INT_CHK_EN; |
| |
| cmdreg |= ((cmd->opcode & 0xff) << 0x8); |
| |
| mvsdmmc_writew(host, SDIO_ARG_LOW, cmd->arg & 0xffff); |
| mvsdmmc_writew(host, SDIO_ARG_HI, cmd->arg >> 16); |
| mvsdmmc_writew(host, SDIO_XFER_MODE, xfer); |
| |
| /* start timer */ |
| mod_timer(&host->timer, jiffies + 5 * HZ); |
| |
| __mvsdmmc_enable_irq(host, intr_enable); |
| |
| host->pending_commands++; |
| |
| /* enable error interrupts*/ |
| mvsdmmc_writew(host, SDIO_ERR_INTR_EN, 0xffff); |
| #if defined(MVSDMMC_DUMP_REGS_ON_CMD) |
| mvsdmmc_debug_error(host, "================================>\n"); |
| mvsdmmc_debug_error(host, "CMD%d Start \n", host->cmd->opcode); |
| mvsdmmc_dump_registers(host, cmdreg); |
| #endif |
| mvsdmmc_writew(host, SDIO_CMD, cmdreg); |
| |
| BUG_ON(host->intr_en == 0); |
| BUG_ON(host->intr_en == SDIO_NOR_UNEXP_RSP); |
| |
| } |
| |
| |
| static void mvsdmmc_finish_data(struct mvsdmmc_host *host) { |
| char *virt_addr; |
| struct mmc_data *data = host->data; |
| u32 blocks_left; |
| unsigned short response[3], resp_indx = 0; |
| |
| |
| BUG_ON(data == NULL); |
| BUG_ON(!(host->intr_status & SDIO_NOR_AUTOCMD12_DONE) && |
| !(host->intr_status & SDIO_NOR_DMA_INI)); |
| |
| if (host->intr_status & SDIO_NOR_AUTOCMD12_DONE) { |
| host->cmd_data &= ~MVSDMMC_CMD12; |
| host->intr_status &= ~(SDIO_NOR_AUTOCMD12_DONE); |
| } else if (host->intr_status & SDIO_NOR_DMA_INI) { |
| host->cmd_data &= ~MVSDMMC_DATA; |
| host->intr_status &= ~(SDIO_NOR_DMA_INI); |
| } |
| |
| if (!host->copy_buf) |
| BUG_ON(host->dma_len == 0); |
| |
| if ((data->flags & MMC_DATA_READ) && (host->copy_buf)) { |
| |
| virt_addr = host->dma_buffer; |
| BUG_ON(virt_addr == NULL); |
| dma_sync_single_for_cpu(mmc_dev(host->mmc), |
| host->dma_addr, |
| host->size, |
| DMA_FROM_DEVICE); |
| |
| /* |
| * Transfer data from DMA buffer to |
| * SG list. |
| */ |
| mvsdmmc_dma_to_sg(host, data); |
| |
| mvsdmmc_align_rx_data(host, data); |
| |
| } else if ((data->flags & MMC_DATA_WRITE) && (host->copy_buf)) { |
| |
| dma_sync_single_for_cpu(mmc_dev(host->mmc), |
| host->dma_addr, |
| host->size, |
| DMA_TO_DEVICE); |
| |
| } |
| |
| if ((!host->copy_buf) && (data->flags & MMC_DATA_READ)) |
| dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, |
| DMA_FROM_DEVICE); |
| |
| if ((!host->copy_buf) && (data->flags & MMC_DATA_WRITE)) |
| dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, |
| DMA_TO_DEVICE); |
| |
| /* check how much data was transfered */ |
| blocks_left = mvsdmmc_readw(host, SDIO_CURR_BLK_LEFT); |
| if (blocks_left) { |
| u32 bytes_left = mvsdmmc_readw(host, SDIO_CURR_BYTE_LEFT); |
| data->bytes_xfered = host->size - |
| (((blocks_left - 1) * data->blksz) + bytes_left); |
| } else { |
| data->bytes_xfered = host->size; |
| } |
| |
| /* Handle Auto cmd 12 response */ |
| if (data->stop) { |
| |
| for (resp_indx = 0 ; resp_indx < 3; resp_indx++) |
| response[resp_indx] = |
| mvsdmmc_readw(host, SDIO_AUTO_RSP(resp_indx)); |
| |
| memset(data->stop->resp, 0, 4 * sizeof(data->stop->resp[0])); |
| |
| data->stop->resp[0] = ((response[2] & 0x3f) << (8 - 8)) | |
| ((response[1] & 0xffff) << (14 - 8)) | |
| ((response[0] & 0x3ff) << (30 - 8)); |
| data->stop->resp[1] = ((response[0] & 0xfc00) >> 10); |
| |
| } |
| |
| |
| if (data->bytes_xfered != host->size) { |
| host->stat.unfinished_dma++; |
| |
| if (host->stat.unfinished_dma == 1) |
| host->stat.first_unfinished_dma = |
| host->stat.total_requests; |
| |
| mvsdmmc_warning(host, "data transfere not complete\n"); |
| mvsdmmc_warning(host, "data transfered =%d" |
| "original size= %d\n", |
| data->bytes_xfered, host->size); |
| } |
| |
| host->data = NULL; |
| } |
| |
| static void mvsdmmc_finish_cmd(struct mvsdmmc_host *host) { |
| unsigned short response[8], resp_indx = 0; |
| struct mmc_command *cmd = host->cmd; |
| |
| host->intr_status &= ~(SDIO_NOR_CMD_DONE); |
| host->cmd_data &= ~MVSDMMC_CMD; |
| |
| for (resp_indx = 0 ; resp_indx < 8; resp_indx++) |
| response[resp_indx] = mvsdmmc_readw(host, SDIO_RSP(resp_indx)); |
| |
| memset(cmd->resp, 0, 4 * sizeof(cmd->resp[0])); |
| |
| if (cmd->flags & MMC_RSP_136) { |
| cmd->resp[3] = ((response[7] & 0x3fff) << 8) | |
| ((response[6] & 0x3ff) << 22); |
| cmd->resp[2] = ((response[6] & 0xfc00) >> 10) | |
| ((response[5] & 0xffff) << 6) | |
| ((response[4] & 0x3ff) << 22); |
| cmd->resp[1] = ((response[4] & 0xfc00) >> 10) | |
| ((response[3] & 0xffff) << 6) | |
| ((response[2] & 0x3ff) << 22); |
| cmd->resp[0] = ((response[2] & 0xfc00) >> 10) | |
| ((response[1] & 0xffff) << 6) | |
| ((response[0] & 0x3ff) << 22); |
| } else if (cmd->flags & MMC_RSP_PRESENT) { |
| cmd->resp[0] = ((response[2] & 0x3f) << (8 - 8)) | |
| ((response[1] & 0xffff) << (14 - 8)) | |
| ((response[0] & 0x3ff) << (30 - 8)); |
| cmd->resp[1] = ((response[0] & 0xfc00) >> 10); |
| } |
| } |
| |
| static unsigned int mvsdmmc_check_error(struct mvsdmmc_host *host) { |
| unsigned short error_status; |
| unsigned int error = 0; |
| |
| error_status = mvsdmmc_readw(host, SDIO_NOR_INTR_STATUS); |
| /* make sure if there is an error*/ |
| if (error_status & SDIO_NOR_UNEXP_RSP) { |
| error = -EPROTO; |
| mvsdmmc_warning(host, "SDIO_NOR_INTR_STATUS(0x%x)=0x%x" |
| "\n", SDIO_NOR_INTR_STATUS, error_status); |
| } else if (error_status & SDIO_NOR_ERROR) { |
| /* clear error status*/ |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, SDIO_NOR_ERROR); |
| |
| error_status = mvsdmmc_readw(host, SDIO_ERR_INTR_STATUS); |
| |
| mvsdmmc_warning(host, "SDIO_ERR_INTR_STATUS(0x%x)=0x%x" |
| "\n", SDIO_ERR_INTR_STATUS, |
| error_status); |
| |
| if (error_status & (SDIO_ERR_CMD_TIMEOUT | |
| SDIO_ERR_DATA_TIMEOUT)) { |
| error = -ETIMEDOUT; |
| } else { |
| error = EILSEQ; |
| } |
| } |
| |
| if (host->cmd) |
| host->cmd->error = error; |
| |
| return error; |
| } |
| |
| static void mvsdmmc_request_done(struct mvsdmmc_host *host) { |
| struct mmc_request *mrq = host->mrq; |
| struct mmc_command *cmd = host->cmd; |
| |
| if ((cmd->error) && (host->data)) { |
| char *virt_addr; |
| int i; |
| |
| host->intr_status = host->intr_cmd; |
| __mvsdmmc_disable_irq(host, host->intr_cmd); |
| if (!host->copy_buf) |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| virt_addr = (char *)sg_virt(host->data->sg); |
| #else |
| virt_addr = (char *)sg_virt_addr(host->data->sg); |
| #endif |
| else |
| virt_addr = (char *)host->dma_buffer; |
| |
| mvsdmmc_warning(host, "error: data size =%d, block size= %d " |
| "block numbers=%d\n", |
| host->size, host->data->blksz, |
| host->data->blocks); |
| |
| if (dump_on_error) { |
| for (i = 0 ; i < host->size ; i++) { |
| if (i % 32 == 0) |
| mvsdmmc_warning(host, "\n 0x%04X:", |
| (unsigned int)i); |
| mvsdmmc_warning(host, "%02X ", *virt_addr++); |
| } |
| } |
| mvsdmmc_warning(host, "\n"); |
| mvsdmmc_finish_data(host); |
| } |
| |
| if ((cmd->error)) { |
| |
| /* disable pending interrupts*/ |
| __mvsdmmc_disable_irq(host, host->intr_cmd); |
| /* clear pending interrupts*/ |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, host->intr_cmd); |
| |
| host->cmd_data = 0; |
| host->intr_status = 0; |
| } |
| |
| host->mrq = NULL; |
| host->cmd = NULL; |
| host->pending_commands--; |
| |
| mmc_request_done(host->mmc, mrq); |
| } |
| |
| static irqreturn_t mvsdmmc_irq_detect(int irq, void *dev) |
| { |
| struct mmc_host *mmc = (struct mmc_host *)dev; |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| |
| host->stat.detect_int++; |
| mmc_detect_change(mmc, msecs_to_jiffies(100)); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t mvsdmmc_irq(int irq, void *dev) |
| { |
| struct mmc_host *mmc = (struct mmc_host *)dev; |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| unsigned long flags; |
| int handled = IRQ_NONE; |
| int cardint = 0; |
| int cmd_data_int = 0; |
| unsigned int error; |
| |
| mvsdmmc_dbg_enter(); |
| spin_lock_irqsave(&host->lock, flags); |
| |
| host->stat.ints++; |
| |
| if (host->pending_commands) |
| del_timer(&host->timer); |
| |
| /* first make sure if there is an error*/ |
| error = mvsdmmc_check_error(host); |
| |
| |
| #if defined(MVSDMMC_DUMP_REGS_ON_CMD) |
| if (host->cmd) { |
| mvsdmmc_debug_error(host, "==============================>\n"); |
| mvsdmmc_debug_error(host, "CMD%d Response \n", |
| host->cmd->opcode); |
| mvsdmmc_dump_registers(host, |
| mvsdmmc_readw(host, SDIO_CMD)); |
| } |
| #endif |
| |
| if (error) { |
| |
| host->stat.error_ints++; |
| |
| if (dump_on_error) |
| mvsdmmc_dump_registers(host, mvsdmmc_readw(host, |
| SDIO_CMD)); |
| |
| if (host->stat.error_ints == 1) { |
| host->stat.first_err_int = host->stat.total_requests; |
| host->stat.first_int_status = |
| mvsdmmc_readw(host, SDIO_NOR_INTR_STATUS); |
| host->stat.first_error_status = |
| mvsdmmc_readw(host, SDIO_ERR_INTR_STATUS); |
| } |
| |
| /* clear error interrupts*/ |
| mvsdmmc_writew(host, SDIO_ERR_INTR_STATUS, |
| mvsdmmc_readw(host, SDIO_ERR_INTR_STATUS)); |
| |
| mvsdmmc_writew(host, SDIO_ERR_INTR_EN, 0); |
| |
| mvsdmmc_warning(host, "command,data error \n"); |
| handled = IRQ_HANDLED; |
| goto done; |
| } |
| |
| /* read interrupts status */ |
| host->intr_status = mvsdmmc_readw(host, SDIO_NOR_INTR_STATUS); |
| /* handle only enabled interrupts*/ |
| host->intr_status &= mvsdmmc_readw(host, SDIO_NOR_INTR_EN); |
| |
| /* disable pending interrupts*/ |
| __mvsdmmc_disable_irq(host, host->intr_status); |
| /* clear pending interrupts*/ |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, host->intr_status); |
| |
| if (host->intr_status & SDIO_NOR_CARD_INT) { |
| host->intr_status &= ~SDIO_NOR_CARD_INT; |
| host->stat.card_ints++; |
| cardint = 1; |
| handled = IRQ_HANDLED; |
| } |
| |
| if (host->intr_status) { |
| cmd_data_int = 1; |
| if ((host->intr_status & SDIO_NOR_AUTOCMD12_DONE) || |
| (host->intr_status & SDIO_NOR_DMA_INI)) { |
| |
| mvsdmmc_finish_data(host); |
| /* if data only interrupt was enable, |
| * then handle command now as well |
| */ |
| if (!(host->intr_cmd & SDIO_NOR_CMD_DONE)) |
| mvsdmmc_finish_cmd(host); |
| } |
| |
| if (host->intr_status & SDIO_NOR_CMD_DONE) |
| mvsdmmc_finish_cmd(host); |
| |
| handled = IRQ_HANDLED; |
| } |
| |
| if (cardint) |
| mmc_signal_sdio_irq(host->mmc); |
| |
| if (handled != IRQ_HANDLED) { |
| /* clear interrupts*/ |
| __mvsdmmc_disable_irq(host, 0xffff); |
| dev_printk(KERN_WARNING, &host->pdev->dev , "interrupt not handled!!! int status = 0x%x\n", mvsdmmc_readw(host, SDIO_NOR_INTR_STATUS)); |
| |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, |
| mvsdmmc_readw(host, SDIO_NOR_INTR_STATUS)); |
| |
| if ((host->cmd_data&MVSDMMC_DATA) && (host->data)) { |
| host->data->error = ETIMEDOUT; |
| host->intr_status = SDIO_NOR_DMA_INI; /* dummy */ |
| mvsdmmc_finish_data(host); |
| } |
| if ((host->cmd_data&MVSDMMC_CMD) && (host->cmd)) { |
| host->cmd->error = ETIMEDOUT; |
| mvsdmmc_finish_cmd(host); |
| } |
| |
| handled = IRQ_HANDLED; |
| host->stat.empty_int++; |
| } |
| done: |
| if ((cmd_data_int) || |
| ((host->pending_commands) && (host->cmd->error))) { |
| /* Since we may and may not have SDIO_NOR_UNEXP_RSP interrupt |
| * make sure SDIO_NOR_UNEXP_RSP cleared and disabled before next |
| * command |
| */ |
| host->intr_status &= ~SDIO_NOR_UNEXP_RSP; |
| __mvsdmmc_disable_irq(host, SDIO_NOR_UNEXP_RSP); |
| mvsdmmc_bitset(host, SDIO_NOR_INTR_STATUS, SDIO_NOR_UNEXP_RSP); |
| mvsdmmc_request_done(host); |
| } |
| spin_unlock_irqrestore(&host->lock, flags); |
| |
| mvsdmmc_dbg_exit(); |
| return handled; |
| |
| } |
| |
| static void mvsdmmc_timeout_timer(unsigned long data) |
| { |
| struct mvsdmmc_host *host; |
| irqreturn_t retval; |
| |
| host = (struct mvsdmmc_host *)data; |
| |
| /* disable interrupts */ |
| mvsdmmc_writew(host, SDIO_ERR_INTR_EN, 0); |
| |
| host->stat.int_timeout++; |
| mvsdmmc_debug_error(host, "timeout for cmd%d\n",host->cmd->opcode); |
| |
| mvsdmmc_bitset(host, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); |
| mvsdmmc_bitset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_HI_SPEED_EN); |
| mvsdmmc_bitreset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_HI_SPEED_EN); |
| /* reset */ |
| mvsdmmc_writew(host, SDIO_SW_RESET, 0x100); |
| /* enable the clock*/ |
| mvsdmmc_bitreset(host, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); |
| |
| if (dump_on_error) |
| mvsdmmc_dump_registers(host, mvsdmmc_readw(host, SDIO_CMD)); |
| |
| if (host->pending_commands) { |
| mvsdmmc_debug_error(host, "timeout:trying to call interrupt routine\n"); |
| /* first try to handle any pending interrupts*/ |
| retval = mvsdmmc_irq(0, (void *)host->mmc); |
| } |
| } |
| |
| /* 32bit byte swap. For example 0x11223344 -> 0x44332211 */ |
| #define MV_BYTE_SWAP_32BIT(X) ((((X)&0xff)<<24) | \ |
| (((X)&0xff00)<<8) | \ |
| (((X)&0xff0000)>>8) | \ |
| (((X)&0xff000000)>>24)) |
| |
| static inline void swap_buf(void *buf, int size) { |
| |
| int i, aligned_size = (size & 0x3)?((size & ~0x3) + 4):size; |
| u32 temp, *buf32 = (u32*)buf; |
| |
| for (i = 0; i < aligned_size; i+=4) { |
| temp = *buf32; |
| *buf32 = MV_BYTE_SWAP_32BIT((temp)); |
| buf32++; |
| } |
| } |
| |
| static inline void mvsdmmc_sg_to_dma(struct mvsdmmc_host *host, |
| struct mmc_data *data) |
| { |
| unsigned int len, i; |
| struct scatterlist *sg; |
| char *dmabuf = host->dma_buffer; |
| char *sgbuf; |
| |
| sg = data->sg; |
| len = data->sg_len; |
| |
| for (i = 0; i < len; i++) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| sgbuf = (char *)sg_virt((&sg[i])); |
| #else |
| sgbuf = (char *)sg_virt_addr((&sg[i])); |
| #endif |
| |
| memcpy(dmabuf, sgbuf, sg[i].length); |
| dmabuf += sg[i].length; |
| } |
| if (host->bad_size) |
| swap_buf(host->dma_buffer, host->size); |
| } |
| |
| static inline void mvsdmmc_dma_to_sg(struct mvsdmmc_host *host, |
| struct mmc_data *data) |
| { |
| unsigned int len, i; |
| struct scatterlist *sg; |
| char *dmabuf = host->dma_buffer; |
| char *sgbuf; |
| |
| sg = data->sg; |
| len = data->sg_len; |
| |
| if (host->bad_size) |
| swap_buf(host->dma_buffer, host->size); |
| |
| for (i = 0; i < len; i++) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| sgbuf = (char *)sg_virt((&sg[i])); |
| #else |
| sgbuf = (char *)sg_virt_addr((&sg[i])); |
| #endif |
| memcpy(sgbuf, dmabuf, sg[i].length); |
| dmabuf += sg[i].length; |
| } |
| } |
| |
| |
| static int mvsdmmc_dma_init(struct mmc_host *mmc) |
| { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| |
| mvsdmmc_dbg_enter(); |
| |
| host->dma_buffer = NULL; |
| host->dma_addr = (dma_addr_t)NULL; |
| |
| /* |
| * We need to allocate a special buffer in |
| * order for SDIO host to be able to DMA to it. |
| */ |
| |
| host->dma_buffer = kmalloc(MVSDMMC_DMA_SIZE, |
| GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN); |
| |
| if (!host->dma_buffer) |
| goto err; |
| |
| /* |
| * Translate the address to a physical address. |
| */ |
| |
| host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer, |
| MVSDMMC_DMA_SIZE, DMA_BIDIRECTIONAL); |
| |
| /* |
| * SDIO host DMA must be aligned on a 4 byte basis. |
| */ |
| if ((host->dma_addr & 0x3) != 0) { |
| |
| mvsdmmc_warning(host, "mvsdmmc: dma alignment error\n"); |
| goto kfree; |
| } |
| |
| |
| |
| mvsdmmc_dbg_exit(); |
| return 0; |
| kfree: |
| /* |
| * If we've gotten here then there is some kind of alignment bug |
| */ |
| |
| dma_unmap_single(mmc_dev(host->mmc), host->dma_addr, |
| MVSDMMC_DMA_SIZE, DMA_BIDIRECTIONAL); |
| host->dma_addr = (dma_addr_t)NULL; |
| |
| kfree(host->dma_buffer); |
| host->dma_buffer = NULL; |
| |
| return -ENOMEM; |
| |
| err: |
| printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. " |
| "Falling back on FIFO.\n", (unsigned int)host->dma_buffer); |
| |
| return -ENOMEM; |
| |
| } |
| |
| |
| static void mvsdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) |
| { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&host->lock, flags); |
| WARN_ON(host->mrq != NULL); |
| |
| host->stat.total_requests++; |
| |
| BUG_ON(host->pending_commands == 1); |
| |
| host->mrq = mrq; |
| host->cmd = mrq->cmd; |
| host->data = mrq->data; |
| |
| mvsdmmc_start_cmd(host, mrq->cmd); |
| |
| spin_unlock_irqrestore(&host->lock, flags); |
| |
| mvsdmmc_dbg_exit(); |
| |
| } |
| |
| static void mvsdmmc_set_clock(struct mmc_host *mmc, unsigned int clock) |
| { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| unsigned int m; |
| mvsdmmc_dbg_enter(); |
| |
| BUG_ON(clock == 0); |
| |
| if(MV_6183_DEV_ID == mvCtrlModelGet()) |
| m = MVSDMMC_BASE_FAST_CLOCK_ORION/(2*clock) - 1; |
| else |
| m = MVSDMMC_BASE_FAST_CLOCK_KW/(2*clock) - 1; |
| |
| mvsdmmc_debug(host, "mvsdmmc_set_clock: dividor = 0x%x clock=%d\n", |
| m, clock); |
| |
| mvsdmmc_writew(host, SDIO_CLK_DIV, m & 0x7ff); |
| |
| msleep(10); |
| |
| mvsdmmc_dbg_exit(); |
| } |
| |
| static void mvsdmmc_power_up(struct mmc_host *mmc) |
| { |
| unsigned int reg; |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| |
| |
| mvsdmmc_dbg_enter(); |
| |
| reg = mvsdmmc_readw(host, SDIO_HOST_CTRL); |
| /* set sd-mem only*/ |
| reg &= ~SDIO_HOST_CTRL_CARD_TYPE_MASK; |
| reg |= SDIO_HOST_CTRL_CARD_TYPE_MASK; |
| |
| /* set big endian */ |
| reg |= SDIO_HOST_CTRL_BIG_ENDIAN; |
| reg &= ~SDIO_HOST_CTRL_LSB_FIRST; |
| |
| /* set maximum timeout */ |
| reg &= ~SDIO_HOST_CTRL_TMOUT_MASK; |
| reg |= SDIO_HOST_CTRL_TMOUT_MAX; |
| reg |= SDIO_HOST_CTRL_TMOUT_EN; |
| mvsdmmc_writew(host, SDIO_HOST_CTRL, reg); |
| |
| mvsdmmc_writew(host, SDIO_NOR_STATUS_EN, 0xffff); |
| mvsdmmc_writew(host, SDIO_ERR_STATUS_EN, 0xffff); |
| mvsdmmc_writew(host, SDIO_NOR_INTR_EN, 0); |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, 0xffff); |
| |
| mvsdmmc_bitreset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_HI_SPEED_EN); |
| |
| /* reset */ |
| mvsdmmc_writew(host, SDIO_SW_RESET, 0x100); |
| msleep(50); |
| |
| |
| /* enable the clock*/ |
| mvsdmmc_bitreset(host, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); |
| mvsdmmc_dbg_exit(); |
| } |
| static void mvsdmmc_power_down(struct mmc_host *mmc) |
| { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| |
| mvsdmmc_dbg_enter(); |
| /* stop the clock*/ |
| mvsdmmc_bitset(host, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); |
| mvsdmmc_writew(host, SDIO_NOR_STATUS_EN, 0); |
| mvsdmmc_writew(host, SDIO_ERR_STATUS_EN, 0); |
| mvsdmmc_writew(host, SDIO_NOR_INTR_EN, 0); |
| mvsdmmc_writew(host, SDIO_NOR_INTR_STATUS, 0xffff); |
| |
| mvsdmmc_dbg_exit(); |
| } |
| |
| |
| |
| static void mvsdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
| { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| |
| mvsdmmc_dbg_enter(); |
| |
| mvsdmmc_debug(host, "setting clock to %d\n", ios->clock); |
| if (ios->clock) |
| mvsdmmc_set_clock(mmc, ios->clock); |
| else |
| mvsdmmc_stop_clock(host); |
| |
| if ((ios->power_mode == MMC_POWER_UP) && |
| (host->power_mode != MMC_POWER_UP)) { |
| |
| mvsdmmc_debug(host, "power up\n"); |
| mvsdmmc_power_up(mmc); |
| host->power_mode = MMC_POWER_UP; |
| } |
| |
| if ((ios->power_mode == MMC_POWER_OFF) && |
| (host->power_mode != MMC_POWER_OFF)) { |
| |
| mvsdmmc_debug(host, " power off\n"); |
| mvsdmmc_power_down(mmc); |
| host->power_mode = MMC_POWER_OFF; |
| } |
| |
| if ((ios->power_mode == MMC_POWER_ON) && |
| (host->power_mode != MMC_POWER_ON)) { |
| |
| mvsdmmc_debug(host, " power on\n"); |
| host->power_mode = MMC_POWER_ON; |
| } |
| |
| if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) { |
| mvsdmmc_debug(host, " set bus mode to opendrain\n"); |
| mvsdmmc_bitreset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_PUSH_PULL_EN); |
| } else if (ios->bus_mode == MMC_BUSMODE_PUSHPULL) { |
| |
| mvsdmmc_debug(host, " set bus mode to pushpull\n"); |
| mvsdmmc_bitset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_PUSH_PULL_EN); |
| } |
| |
| if (ios->bus_width == MMC_BUS_WIDTH_1) { |
| mvsdmmc_debug(host, " set width x1\n"); |
| mvsdmmc_bitreset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_DATA_WIDTH_4_BITS); |
| } else if (ios->bus_width == MMC_BUS_WIDTH_4) { |
| mvsdmmc_debug(host, " set width x4\n"); |
| mvsdmmc_bitset(host, SDIO_HOST_CTRL, |
| SDIO_HOST_CTRL_DATA_WIDTH_4_BITS); |
| } |
| |
| mvsdmmc_dbg_exit(); |
| } |
| |
| static void mvsdmmc_enable_sdio_irq(struct mmc_host *host, int enable) |
| { |
| struct mvsdmmc_host *mvsdmmc_host = mmc_priv(host); |
| if (enable) |
| mvsdmmc_enable_irq(mvsdmmc_host, SDIO_NOR_CARD_INT); |
| else |
| mvsdmmc_disable_irq(mvsdmmc_host, SDIO_NOR_CARD_INT); |
| } |
| |
| static const struct mmc_host_ops mvsdmmc_ops = { |
| .request = mvsdmmc_request, |
| .get_ro = NULL, |
| .set_ios = mvsdmmc_set_ios, |
| .enable_sdio_irq = mvsdmmc_enable_sdio_irq, |
| }; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| static void mv_conf_mbus_windows(struct mvsdmmc_host *host, |
| struct mbus_dram_target_info *dram) { |
| int i; |
| |
| for (i = 0; i < 4; i++) { |
| writel(0, host->base + WINDOW_CTRL(i)); |
| writel(0, host->base + WINDOW_BASE(i)); |
| } |
| |
| for (i = 0; i < dram->num_cs; i++) { |
| struct mbus_dram_window *cs = dram->cs + i; |
| writel(((cs->size - 1) & 0xffff0000) | |
| (cs->mbus_attr << 8) | |
| (dram->mbus_dram_target_id << 4) | 1, |
| host->base + WINDOW_CTRL(i)); |
| writel(cs->base, host->base + WINDOW_BASE(i)); |
| } |
| } |
| #endif |
| |
| static int mvsdmmc_probe(struct platform_device *pdev) |
| { |
| struct mmc_host *mmc = NULL; |
| struct mvsdmmc_host *host = NULL; |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| const struct orion_mvsdmmc_data *mv_platform_data; |
| #endif |
| struct resource *r; |
| int ret = 0, irq = NO_IRQ; |
| int irq_detect = NO_IRQ; |
| |
| mvsdmmc_dbg_enter(); |
| |
| BUG_ON(pdev == NULL); |
| |
| r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!r) |
| return -ENXIO; |
| |
| r = request_mem_region(r->start, SZ_1K, DRIVER_NAME); |
| if (!r) |
| return -EBUSY; |
| |
| irq = platform_get_irq(pdev, 0); |
| |
| if (irq == NO_IRQ) { |
| ret = -ENXIO; |
| goto out; |
| } |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| mv_platform_data = pdev->dev.platform_data; |
| #endif |
| |
| printk( DRIVER_NAME ": irq =%d start %x\n", irq, r->start); |
| |
| if (detect) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| irq_detect = mv_platform_data->detect_irq; |
| #else |
| irq_detect = platform_get_irq(pdev, 1); |
| #endif |
| |
| if (irq_detect == NO_IRQ) { |
| detect = 0; |
| printk( DRIVER_NAME ": no IRQ detect\n"); |
| } |
| else |
| printk( DRIVER_NAME ": irq_detect=%d\n", irq_detect); |
| } |
| |
| |
| mmc = mmc_alloc_host(sizeof(struct mvsdmmc_host), &pdev->dev); |
| if (!mmc) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| mmc->ops = &mvsdmmc_ops; |
| mmc->f_min = MVSDMMC_CLOCKRATE_MIN; |
| mmc->f_max = (unsigned int)maxfreq; |
| |
| mmc->max_hw_segs = 1; |
| mmc->max_phys_segs = 1; |
| |
| mmc->max_req_size = MVSDMMC_DMA_SIZE; |
| mmc->max_seg_size = mmc->max_req_size; |
| mmc->max_blk_count = MVSDMMC_DMA_SIZE; |
| mmc->max_blk_size = 2048; |
| |
| host = mmc_priv(mmc); |
| BUG_ON(host == NULL); |
| |
| host->pdev = pdev; |
| host->mmc = mmc; |
| host->irq = irq; |
| |
| if (detect) |
| host->irq_detect = irq_detect; |
| |
| host->res = r; |
| mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; |
| |
| mmc->caps |= MMC_CAP_4_BIT_DATA; |
| |
| if (highspeed) { |
| mmc->caps |= MMC_CAP_SD_HIGHSPEED; |
| mmc->caps |= MMC_CAP_MMC_HIGHSPEED; |
| } |
| |
| mmc->caps |= MMC_CAP_SDIO_IRQ; |
| |
| spin_lock_init(&host->lock); |
| host->power_mode = MMC_POWER_OFF; |
| |
| host->base = ioremap(r->start, SZ_4K); |
| |
| if (!host->base) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| /* |
| * (Re-)program MBUS remapping windows if we are asked to. |
| */ |
| if (mv_platform_data->dram != NULL) |
| mv_conf_mbus_windows(host, mv_platform_data->dram); |
| #endif |
| |
| if (request_irq(host->irq, mvsdmmc_irq, |
| 0 , DRIVER_NAME, mmc)) { |
| mvsdmmc_debug(host, "cannot assign irq %d\n", host->irq); |
| ret = -EINVAL; |
| host->irq = NO_IRQ; |
| goto out; |
| } |
| |
| if (detect) { |
| if (request_irq(host->irq_detect, mvsdmmc_irq_detect, |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| IRQT_RISING | IRQT_FALLING, |
| #else |
| 0, |
| #endif |
| DRIVER_NAME, mmc)) { |
| |
| mvsdmmc_debug(host, "cannot assign irq %d\n", |
| host->irq_detect); |
| ret = -EINVAL; |
| host->irq_detect = NO_IRQ; |
| goto out; |
| } |
| } else { |
| mmc->caps |= MMC_CAP_NEEDS_POLL; |
| host->irq_detect = NO_IRQ; |
| } |
| |
| platform_set_drvdata(pdev, mmc); |
| setup_timer(&host->timer, mvsdmmc_timeout_timer, (unsigned long)host); |
| |
| if (mvsdmmc_dma_init(mmc) != 0) |
| goto out; |
| |
| if (mmc_add_host(mmc) != 0) |
| goto out; |
| |
| create_proc_read_entry("mvsdmmc", 0 , NULL , |
| mvsdmmc_read_procmem, host); |
| |
| mvsdmmc_dbg_exit(); |
| return 0; |
| out: |
| if (host) { |
| mvsdmmc_disable_irq(host, 0xffff); |
| |
| if (host->base) { |
| mvsdmmc_stop_clock(host); |
| iounmap(host->base); |
| } |
| |
| |
| if (host->irq != NO_IRQ) |
| free_irq(host->irq, host); |
| |
| if (detect) { |
| if (host->irq_detect != NO_IRQ) |
| free_irq(host->irq_detect, host); |
| } |
| |
| if (host->res) |
| release_resource(host->res); |
| } |
| |
| if (mmc) |
| mmc_free_host(mmc); |
| |
| return ret; |
| } |
| |
| static int mvsdmmc_remove(struct platform_device *pdev) |
| { |
| struct mmc_host *mmc = platform_get_drvdata(pdev); |
| platform_set_drvdata(pdev, NULL); |
| |
| if (mmc) { |
| struct mvsdmmc_host *host = mmc_priv(mmc); |
| mvsdmmc_disable_irq(host, 0xffff); |
| |
| cancel_delayed_work(&mmc->detect); |
| |
| mmc_remove_host(mmc); |
| |
| if (host) { |
| mvsdmmc_stop_clock(host); |
| |
| |
| if (host->irq != NO_IRQ) |
| free_irq(host->irq, mmc); |
| |
| if (detect) { |
| if (host->irq_detect != NO_IRQ) |
| free_irq(host->irq_detect, mmc); |
| } |
| |
| if (host->base) |
| iounmap(host->base); |
| |
| if (host->res) |
| release_resource(host->res); |
| |
| del_timer_sync(&host->timer); |
| } |
| mmc_free_host(mmc); |
| } |
| remove_proc_entry("mvsdmmc", NULL); |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int mvsdmmc_suspend(struct platform_device *dev, pm_message_t state, |
| u32 level) |
| { |
| struct mmc_host *mmc = platform_get_drvdata(dev); |
| int ret = 0; |
| |
| if (mmc && level == SUSPEND_DISABLE) |
| ret = mmc_suspend_host(mmc, state); |
| |
| return ret; |
| } |
| |
| static int mvsdmmc_resume(struct platform_device *dev, u32 level) |
| { |
| struct mmc_host *mmc = platform_dev_get_drvdata(dev); |
| int ret = 0; |
| |
| if (mmc && level == RESUME_ENABLE) |
| ret = mmc_resume_host(mmc); |
| |
| return ret; |
| } |
| #else |
| #define mvsdmmc_suspend NULL |
| #define mvsdmmc_resume NULL |
| #endif |
| |
| |
| static struct platform_driver mvsdmmc_driver = { |
| .probe = mvsdmmc_probe, |
| .remove = mvsdmmc_remove, |
| .suspend = mvsdmmc_suspend, |
| .resume = mvsdmmc_resume, |
| .driver = { |
| .name = DRIVER_NAME, |
| }, |
| }; |
| |
| static int __init mvsdmmc_init(void) |
| { |
| int ret = 0; |
| mvsdmmc_dbg_enter(); |
| |
| if (MV_FALSE == mvCtrlPwrClckGet(SDIO_UNIT_ID, 0)) { |
| printk("\nWarning SDIO unit is Powered Off\n"); |
| return; |
| } |
| |
| ret = platform_driver_register(&mvsdmmc_driver); |
| |
| mvsdmmc_dbg_exit(); |
| return ret; |
| } |
| |
| static void __exit mvsdmmc_exit(void) |
| { |
| mvsdmmc_dbg_enter(); |
| platform_driver_unregister(&mvsdmmc_driver); |
| mvsdmmc_dbg_exit(); |
| } |
| |
| module_init(mvsdmmc_init); |
| module_exit(mvsdmmc_exit); |
| |
| /* maximum frequency used in the driver (default 50MHz) */ |
| module_param(maxfreq, int, 0); |
| |
| /* do we support high speed, default yes*/ |
| module_param(highspeed, int, 0); |
| |
| module_param(dump_on_error, int, 0); |
| |
| /* support detection removal\insersion (default = 1) */ |
| module_param(detect, int, 0); |
| |
| |
| MODULE_AUTHOR("Maen Suleiman <maen@marvell.com>"); |
| MODULE_DESCRIPTION("Marvell SD,SDIO,MMC Host Controller driver"); |
| MODULE_LICENSE("GPL"); |