sdhci.c
来自「linux 内核源代码」· C语言 代码 · 共 1,668 行 · 第 1/3 页
C
1,668 行
/* * linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver * * Copyright (C) 2005-2007 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. * * Thanks to the following companies for their support: * * - JMicron (hardware and technical support) */#include <linux/delay.h>#include <linux/highmem.h>#include <linux/pci.h>#include <linux/dma-mapping.h>#include <linux/scatterlist.h>#include <linux/mmc/host.h>#include "sdhci.h"#define DRIVER_NAME "sdhci"#define DBG(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)static unsigned int debug_quirks = 0;/* * Different quirks to handle when the hardware deviates from a strict * interpretation of the SDHCI specification. *//* Controller doesn't honor resets unless we touch the clock register */#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)/* Controller has bad caps bits, but really supports DMA */#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)/* Controller doesn't like clearing the power reg before a change */#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)/* Controller has flaky internal state so reset it on each ios change */#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4)/* Controller has an unusable DMA engine */#define SDHCI_QUIRK_BROKEN_DMA (1<<5)/* Controller can only DMA from 32-bit aligned addresses */#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<6)/* Controller can only DMA chunk sizes that are a multiple of 32 bits */#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<7)/* Controller needs to be reset after each request to stay stable */#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<8)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, }, { .vendor = PCI_VENDOR_ID_ENE, .device = PCI_DEVICE_ID_ENE_CB712_SD, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_BROKEN_DMA, }, { .vendor = PCI_VENDOR_ID_ENE, .device = PCI_DEVICE_ID_ENE_CB712_SD_2, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_BROKEN_DMA, }, { .vendor = PCI_VENDOR_ID_ENE, .device = PCI_DEVICE_ID_ENE_CB714_SD, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, }, { .vendor = PCI_VENDOR_ID_ENE, .device = PCI_DEVICE_ID_ENE_CB714_SD_2, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, }, { .vendor = PCI_VENDOR_ID_JMICRON, .device = PCI_DEVICE_ID_JMICRON_JMB38X_SD, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .driver_data = SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE | SDHCI_QUIRK_RESET_AFTER_REQUEST, }, { /* 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_WAKE_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.\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_sg_to_buffer(struct sdhci_host* host){ return sg_virt(host->cur_sg);}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_sg_to_buffer(host) + host->offset; while (blksize) { if (chunk_remain == 0) { data = readl(host->ioaddr + SDHCI_BUFFER); chunk_remain = min(blksize, 4); } size = min(host->remain, chunk_remain); chunk_remain -= size; blksize -= size; host->offset += size; host->remain -= size; while (size) { *buffer = data & 0xFF; buffer++; data >>= 8; size--; } if (host->remain == 0) { if (sdhci_next_sg(host) == 0) { BUG_ON(blksize != 0); return; } buffer = sdhci_sg_to_buffer(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_sg_to_buffer(host) + host->offset; while (blksize) { size = min(host->remain, chunk_remain); chunk_remain -= size; blksize -= size; host->offset += size; host->remain -= 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) { if (sdhci_next_sg(host) == 0) { BUG_ON(blksize != 0); return; } buffer = sdhci_sg_to_buffer(host); } }}static void sdhci_transfer_pio(struct sdhci_host *host){ u32 mask; BUG_ON(!host->data); if (host->num_sg == 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->num_sg == 0) break; } 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; /* Sanity checks */ BUG_ON(data->blksz * data->blocks > 524288); BUG_ON(data->blksz > host->mmc->max_blk_size); BUG_ON(data->blocks > 65535); host->data = data; host->data_early = 0; /* 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) host->flags |= SDHCI_REQ_USE_DMA; if (unlikely((host->flags & SDHCI_REQ_USE_DMA) && (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) && ((data->blksz * data->blocks) & 0x3))) { DBG("Reverting to PIO because of transfer size (%d)\n", data->blksz * data->blocks); host->flags &= ~SDHCI_REQ_USE_DMA; } /* * The assumption here being that alignment is the same after * translation to device address space. */ if (unlikely((host->flags & SDHCI_REQ_USE_DMA) && (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && (data->sg->offset & 0x3))) { DBG("Reverting to PIO because of bad alignment\n"); host->flags &= ~SDHCI_REQ_USE_DMA; } if (host->flags & SDHCI_REQ_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->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; if (data == NULL) return; WARN_ON(!host->data); 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_REQ_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_REQ_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) blocks = (data->error == 0) ? 0 : 1; else blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT); data->bytes_xfered = data->blksz * (data->blocks - blocks); if (!data->error && blocks) { printk(KERN_ERR "%s: Controller signalled completion even " "though there were blocks left.\n", mmc_hostname(host->mmc)); data->error = -EIO; } if (data->stop) { /* * The controller needs a reset of internal state machines * upon error conditions. */ if (data->error) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); }
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?