📄 sdhci.c
字号:
/* * linux/drivers/mmc/sdhci.c - Secure Digital Host Controller Interface driver * * Copyright (C) 2005-2006 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 as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. */#include <linux/delay.h>#include <linux/highmem.h>#include <linux/pci.h>#include <linux/dma-mapping.h>#include <linux/mmc/host.h>#include <linux/mmc/protocol.h>#include <asm/scatterlist.h>#include "sdhci.h"#define DRIVER_NAME "sdhci"#define DRIVER_VERSION "0.12"#define BUGMAIL "<sdhci-devel@list.drzeus.cx>"#define DBG(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)static unsigned int debug_nodma = 0;static unsigned int debug_forcedma = 0;static unsigned int debug_quirks = 0;#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)#define SDHCI_QUIRK_FORCE_DMA (1<<1)/* Controller doesn't like some resets when there is no card inserted. */#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)static const struct pci_device_id pci_ids[] __devinitdata = { { .vendor = PCI_VENDOR_ID_RICOH, .device = PCI_DEVICE_ID_RICOH_R5C822, .subvendor = PCI_VENDOR_ID_IBM, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_CLOCK_BEFORE_RESET | SDHCI_QUIRK_FORCE_DMA, }, { .vendor = PCI_VENDOR_ID_RICOH, .device = PCI_DEVICE_ID_RICOH_R5C822, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_FORCE_DMA | SDHCI_QUIRK_NO_CARD_NO_RESET, }, { .vendor = PCI_VENDOR_ID_TI, .device = PCI_DEVICE_ID_TI_XX21_XX11_SD, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_FORCE_DMA, }, { /* Generic SD host controller */ PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00) }, { /* end: all zeroes */ },};MODULE_DEVICE_TABLE(pci, pci_ids);static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *);static void sdhci_finish_data(struct sdhci_host *);static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);static void sdhci_finish_command(struct sdhci_host *);static void sdhci_dumpregs(struct sdhci_host *host){ printk(KERN_DEBUG DRIVER_NAME ": ============== REGISTER DUMP ==============\n"); printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", readl(host->ioaddr + SDHCI_DMA_ADDRESS), readw(host->ioaddr + SDHCI_HOST_VERSION)); printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", readw(host->ioaddr + SDHCI_BLOCK_SIZE), readw(host->ioaddr + SDHCI_BLOCK_COUNT)); printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", readl(host->ioaddr + SDHCI_ARGUMENT), readw(host->ioaddr + SDHCI_TRANSFER_MODE)); printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", readl(host->ioaddr + SDHCI_PRESENT_STATE), readb(host->ioaddr + SDHCI_HOST_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", readb(host->ioaddr + SDHCI_POWER_CONTROL), readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", readb(host->ioaddr + SDHCI_WALK_UP_CONTROL), readw(host->ioaddr + SDHCI_CLOCK_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", readb(host->ioaddr + SDHCI_TIMEOUT_CONTROL), readl(host->ioaddr + SDHCI_INT_STATUS)); printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", readl(host->ioaddr + SDHCI_INT_ENABLE), readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)); printk(KERN_DEBUG DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", readw(host->ioaddr + SDHCI_ACMD12_ERR), readw(host->ioaddr + SDHCI_SLOT_INT_STATUS)); printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x | Max curr: 0x%08x\n", readl(host->ioaddr + SDHCI_CAPABILITIES), readl(host->ioaddr + SDHCI_MAX_CURRENT)); printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n");}/*****************************************************************************\ * * * Low level functions * * *\*****************************************************************************/static void sdhci_reset(struct sdhci_host *host, u8 mask){ unsigned long timeout; if (host->chip->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) { if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) return; } writeb(mask, host->ioaddr + SDHCI_SOFTWARE_RESET); if (mask & SDHCI_RESET_ALL) host->clock = 0; /* Wait max 100 ms */ timeout = 100; /* hw clears the bit when it's done */ while (readb(host->ioaddr + SDHCI_SOFTWARE_RESET) & mask) { if (timeout == 0) { printk(KERN_ERR "%s: Reset 0x%x never completed. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc), (int)mask); sdhci_dumpregs(host); return; } timeout--; mdelay(1); }}static void sdhci_init(struct sdhci_host *host){ u32 intmask; sdhci_reset(host, SDHCI_RESET_ALL); intmask = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT | SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE; writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE);}static void sdhci_activate_led(struct sdhci_host *host){ u8 ctrl; ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); ctrl |= SDHCI_CTRL_LED; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);}static void sdhci_deactivate_led(struct sdhci_host *host){ u8 ctrl; ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); ctrl &= ~SDHCI_CTRL_LED; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);}/*****************************************************************************\ * * * Core functions * * *\*****************************************************************************/static inline char* sdhci_kmap_sg(struct sdhci_host* host){ host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ); return host->mapped_sg + host->cur_sg->offset;}static inline void sdhci_kunmap_sg(struct sdhci_host* host){ kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ);}static inline int sdhci_next_sg(struct sdhci_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 void sdhci_read_block_pio(struct sdhci_host *host){ int blksize, chunk_remain; u32 data; char *buffer; int size; DBG("PIO reading\n"); blksize = host->data->blksz; chunk_remain = 0; data = 0; buffer = sdhci_kmap_sg(host) + host->offset; while (blksize) { if (chunk_remain == 0) { data = readl(host->ioaddr + SDHCI_BUFFER); chunk_remain = min(blksize, 4); } size = min(host->size, host->remain); size = min(size, chunk_remain); chunk_remain -= size; blksize -= size; host->offset += size; host->remain -= size; host->size -= size; while (size) { *buffer = data & 0xFF; buffer++; data >>= 8; size--; } if (host->remain == 0) { sdhci_kunmap_sg(host); if (sdhci_next_sg(host) == 0) { BUG_ON(blksize != 0); return; } buffer = sdhci_kmap_sg(host); } } sdhci_kunmap_sg(host);}static void sdhci_write_block_pio(struct sdhci_host *host){ int blksize, chunk_remain; u32 data; char *buffer; int bytes, size; DBG("PIO writing\n"); blksize = host->data->blksz; chunk_remain = 4; data = 0; bytes = 0; buffer = sdhci_kmap_sg(host) + host->offset; while (blksize) { size = min(host->size, host->remain); size = min(size, chunk_remain); chunk_remain -= size; blksize -= size; host->offset += size; host->remain -= size; host->size -= size; while (size) { data >>= 8; data |= (u32)*buffer << 24; buffer++; size--; } if (chunk_remain == 0) { writel(data, host->ioaddr + SDHCI_BUFFER); chunk_remain = min(blksize, 4); } if (host->remain == 0) { sdhci_kunmap_sg(host); if (sdhci_next_sg(host) == 0) { BUG_ON(blksize != 0); return; } buffer = sdhci_kmap_sg(host); } } sdhci_kunmap_sg(host);}static void sdhci_transfer_pio(struct sdhci_host *host){ u32 mask; BUG_ON(!host->data); if (host->size == 0) return; if (host->data->flags & MMC_DATA_READ) mask = SDHCI_DATA_AVAILABLE; else mask = SDHCI_SPACE_AVAILABLE; while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) { if (host->data->flags & MMC_DATA_READ) sdhci_read_block_pio(host); else sdhci_write_block_pio(host); if (host->size == 0) break; BUG_ON(host->num_sg == 0); } DBG("PIO transfer complete.\n");}static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data){ u8 count; unsigned target_timeout, current_timeout; WARN_ON(host->data); if (data == NULL) return; DBG("blksz %04x blks %04x flags %08x\n", data->blksz, data->blocks, data->flags); DBG("tsac %d ms nsac %d clk\n", data->timeout_ns / 1000000, data->timeout_clks); /* Sanity checks */ BUG_ON(data->blksz * data->blocks > 524288); BUG_ON(data->blksz > host->max_block); BUG_ON(data->blocks > 65535); /* timeout in us */ target_timeout = data->timeout_ns / 1000 + data->timeout_clks / host->clock; /* * Figure out needed cycles. * We do this in steps in order to fit inside a 32 bit int. * The first step is the minimum timeout, which will have a * minimum resolution of 6 bits: * (1) 2^13*1000 > 2^22, * (2) host->timeout_clk < 2^16 * => * (1) / (2) > 2^6 */ count = 0; current_timeout = (1 << 13) * 1000 / host->timeout_clk; while (current_timeout < target_timeout) { count++; current_timeout <<= 1; if (count >= 0xF) break; } if (count >= 0xF) { printk(KERN_WARNING "%s: Too large timeout requested!\n", mmc_hostname(host->mmc)); count = 0xE; } writeb(count, host->ioaddr + SDHCI_TIMEOUT_CONTROL); if (host->flags & SDHCI_USE_DMA) { int count; count = pci_map_sg(host->chip->pdev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE); BUG_ON(count != 1); writel(sg_dma_address(data->sg), host->ioaddr + SDHCI_DMA_ADDRESS); } else { host->size = data->blksz * data->blocks; host->cur_sg = data->sg; host->num_sg = data->sg_len; host->offset = 0; host->remain = host->cur_sg->length; } /* We do not handle DMA boundaries, so set it to max (512 KiB) */ writew(SDHCI_MAKE_BLKSZ(7, data->blksz), host->ioaddr + SDHCI_BLOCK_SIZE); writew(data->blocks, host->ioaddr + SDHCI_BLOCK_COUNT);}static void sdhci_set_transfer_mode(struct sdhci_host *host, struct mmc_data *data){ u16 mode; WARN_ON(host->data); if (data == NULL) return; mode = SDHCI_TRNS_BLK_CNT_EN; if (data->blocks > 1) mode |= SDHCI_TRNS_MULTI; if (data->flags & MMC_DATA_READ) mode |= SDHCI_TRNS_READ; if (host->flags & SDHCI_USE_DMA) mode |= SDHCI_TRNS_DMA; writew(mode, host->ioaddr + SDHCI_TRANSFER_MODE);}static void sdhci_finish_data(struct sdhci_host *host){ struct mmc_data *data; u16 blocks; BUG_ON(!host->data); data = host->data; host->data = NULL; if (host->flags & SDHCI_USE_DMA) { pci_unmap_sg(host->chip->pdev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE); } /* * Controller doesn't count down when in single block mode. */ if ((data->blocks == 1) && (data->error == MMC_ERR_NONE)) blocks = 0; else blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT); data->bytes_xfered = data->blksz * (data->blocks - blocks); if ((data->error == MMC_ERR_NONE) && blocks) { printk(KERN_ERR "%s: Controller signalled completion even " "though there were blocks left. Please report this " "to " BUGMAIL ".\n", mmc_hostname(host->mmc)); data->error = MMC_ERR_FAILED; } else if (host->size != 0) { printk(KERN_ERR "%s: %d bytes were left untransferred. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc), host->size); data->error = MMC_ERR_FAILED; } DBG("Ending data transfer (%d bytes)\n", data->bytes_xfered); if (data->stop) { /* * The controller needs a reset of internal state machines * upon error conditions. */ if (data->error != MMC_ERR_NONE) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } sdhci_send_command(host, data->stop); } else tasklet_schedule(&host->finish_tasklet);}static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd){ int flags; u32 mask; unsigned long timeout; WARN_ON(host->cmd); DBG("Sending cmd (%x)\n", cmd->opcode);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -