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 + -
显示快捷键?