⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sdhci.c

📁 《linux驱动程序设计从入门到精通》一书中所有的程序代码含驱动和相应的应用程序
💻 C
📖 第 1 页 / 共 3 页
字号:
/* *  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 + -