wbsd.c

来自「linux 内核源代码」· C语言 代码 · 共 2,048 行 · 第 1/3 页

C
2,048
字号
	 * Send a stop command if needed.	 */	if (data->stop)		wbsd_send_command(host, data->stop);	/*	 * Wait for the controller to leave data	 * transfer state.	 */	do {		status = wbsd_read_index(host, WBSD_IDX_STATUS);	} while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));	/*	 * DMA transfer?	 */	if (host->dma >= 0) {		/*		 * Disable DMA on the host.		 */		wbsd_write_index(host, WBSD_IDX_DMA, 0);		/*		 * Turn of ISA DMA controller.		 */		dmaflags = claim_dma_lock();		disable_dma(host->dma);		clear_dma_ff(host->dma);		count = get_dma_residue(host->dma);		release_dma_lock(dmaflags);		data->bytes_xfered = host->mrq->data->blocks *			host->mrq->data->blksz - count;		data->bytes_xfered -= data->bytes_xfered % data->blksz;		/*		 * Any leftover data?		 */		if (count) {			printk(KERN_ERR "%s: Incomplete DMA transfer. "				"%d bytes left.\n",				mmc_hostname(host->mmc), count);			if (!data->error)				data->error = -EIO;		} else {			/*			 * Transfer data from DMA buffer to			 * SG list.			 */			if (data->flags & MMC_DATA_READ)				wbsd_dma_to_sg(host, data);		}		if (data->error) {			if (data->bytes_xfered)				data->bytes_xfered -= data->blksz;		}	}	wbsd_request_end(host, host->mrq);}/*****************************************************************************\ *                                                                           * * MMC layer callbacks                                                       * *                                                                           *\*****************************************************************************/static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq){	struct wbsd_host *host = mmc_priv(mmc);	struct mmc_command *cmd;	/*	 * Disable tasklets to avoid a deadlock.	 */	spin_lock_bh(&host->lock);	BUG_ON(host->mrq != NULL);	cmd = mrq->cmd;	host->mrq = mrq;	/*	 * Check that there is actually a card in the slot.	 */	if (!(host->flags & WBSD_FCARD_PRESENT)) {		cmd->error = -ENOMEDIUM;		goto done;	}	if (cmd->data) {		/*		 * The hardware is so delightfully stupid that it has a list		 * of "data" commands. If a command isn't on this list, it'll		 * just go back to the idle state and won't send any data		 * interrupts.		 */		switch (cmd->opcode) {		case 11:		case 17:		case 18:		case 20:		case 24:		case 25:		case 26:		case 27:		case 30:		case 42:		case 56:			break;		/* ACMDs. We don't keep track of state, so we just treat them		 * like any other command. */		case 51:			break;		default:#ifdef CONFIG_MMC_DEBUG			printk(KERN_WARNING "%s: Data command %d is not "				"supported by this controller.\n",				mmc_hostname(host->mmc), cmd->opcode);#endif			cmd->error = -EINVAL;			goto done;		};	}	/*	 * Does the request include data?	 */	if (cmd->data) {		wbsd_prepare_data(host, cmd->data);		if (cmd->data->error)			goto done;	}	wbsd_send_command(host, cmd);	/*	 * If this is a data transfer the request	 * will be finished after the data has	 * transfered.	 */	if (cmd->data && !cmd->error) {		/*		 * Dirty fix for hardware bug.		 */		if (host->dma == -1)			tasklet_schedule(&host->fifo_tasklet);		spin_unlock_bh(&host->lock);		return;	}done:	wbsd_request_end(host, mrq);	spin_unlock_bh(&host->lock);}static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios){	struct wbsd_host *host = mmc_priv(mmc);	u8 clk, setup, pwr;	spin_lock_bh(&host->lock);	/*	 * Reset the chip on each power off.	 * Should clear out any weird states.	 */	if (ios->power_mode == MMC_POWER_OFF)		wbsd_init_device(host);	if (ios->clock >= 24000000)		clk = WBSD_CLK_24M;	else if (ios->clock >= 16000000)		clk = WBSD_CLK_16M;	else if (ios->clock >= 12000000)		clk = WBSD_CLK_12M;	else		clk = WBSD_CLK_375K;	/*	 * Only write to the clock register when	 * there is an actual change.	 */	if (clk != host->clk) {		wbsd_write_index(host, WBSD_IDX_CLK, clk);		host->clk = clk;	}	/*	 * Power up card.	 */	if (ios->power_mode != MMC_POWER_OFF) {		pwr = inb(host->base + WBSD_CSR);		pwr &= ~WBSD_POWER_N;		outb(pwr, host->base + WBSD_CSR);	}	/*	 * MMC cards need to have pin 1 high during init.	 * It wreaks havoc with the card detection though so	 * that needs to be disabled.	 */	setup = wbsd_read_index(host, WBSD_IDX_SETUP);	if (ios->chip_select == MMC_CS_HIGH) {		BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);		setup |= WBSD_DAT3_H;		host->flags |= WBSD_FIGNORE_DETECT;	} else {		if (setup & WBSD_DAT3_H) {			setup &= ~WBSD_DAT3_H;			/*			 * We cannot resume card detection immediatly			 * because of capacitance and delays in the chip.			 */			mod_timer(&host->ignore_timer, jiffies + HZ / 100);		}	}	wbsd_write_index(host, WBSD_IDX_SETUP, setup);	/*	 * Store bus width for later. Will be used when	 * setting up the data transfer.	 */	host->bus_width = ios->bus_width;	spin_unlock_bh(&host->lock);}static int wbsd_get_ro(struct mmc_host *mmc){	struct wbsd_host *host = mmc_priv(mmc);	u8 csr;	spin_lock_bh(&host->lock);	csr = inb(host->base + WBSD_CSR);	csr |= WBSD_MSLED;	outb(csr, host->base + WBSD_CSR);	mdelay(1);	csr = inb(host->base + WBSD_CSR);	csr &= ~WBSD_MSLED;	outb(csr, host->base + WBSD_CSR);	spin_unlock_bh(&host->lock);	return csr & WBSD_WRPT;}static const struct mmc_host_ops wbsd_ops = {	.request	= wbsd_request,	.set_ios	= wbsd_set_ios,	.get_ro		= wbsd_get_ro,};/*****************************************************************************\ *                                                                           * * Interrupt handling                                                        * *                                                                           *\*****************************************************************************//* * Helper function to reset detection ignore */static void wbsd_reset_ignore(unsigned long data){	struct wbsd_host *host = (struct wbsd_host *)data;	BUG_ON(host == NULL);	DBG("Resetting card detection ignore\n");	spin_lock_bh(&host->lock);	host->flags &= ~WBSD_FIGNORE_DETECT;	/*	 * Card status might have changed during the	 * blackout.	 */	tasklet_schedule(&host->card_tasklet);	spin_unlock_bh(&host->lock);}/* * Tasklets */static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host){	WARN_ON(!host->mrq);	if (!host->mrq)		return NULL;	WARN_ON(!host->mrq->cmd);	if (!host->mrq->cmd)		return NULL;	WARN_ON(!host->mrq->cmd->data);	if (!host->mrq->cmd->data)		return NULL;	return host->mrq->cmd->data;}static void wbsd_tasklet_card(unsigned long param){	struct wbsd_host *host = (struct wbsd_host *)param;	u8 csr;	int delay = -1;	spin_lock(&host->lock);	if (host->flags & WBSD_FIGNORE_DETECT) {		spin_unlock(&host->lock);		return;	}	csr = inb(host->base + WBSD_CSR);	WARN_ON(csr == 0xff);	if (csr & WBSD_CARDPRESENT) {		if (!(host->flags & WBSD_FCARD_PRESENT)) {			DBG("Card inserted\n");			host->flags |= WBSD_FCARD_PRESENT;			delay = 500;		}	} else if (host->flags & WBSD_FCARD_PRESENT) {		DBG("Card removed\n");		host->flags &= ~WBSD_FCARD_PRESENT;		if (host->mrq) {			printk(KERN_ERR "%s: Card removed during transfer!\n",				mmc_hostname(host->mmc));			wbsd_reset(host);			host->mrq->cmd->error = -ENOMEDIUM;			tasklet_schedule(&host->finish_tasklet);		}		delay = 0;	}	/*	 * Unlock first since we might get a call back.	 */	spin_unlock(&host->lock);	if (delay != -1)		mmc_detect_change(host->mmc, msecs_to_jiffies(delay));}static void wbsd_tasklet_fifo(unsigned long param){	struct wbsd_host *host = (struct wbsd_host *)param;	struct mmc_data *data;	spin_lock(&host->lock);	if (!host->mrq)		goto end;	data = wbsd_get_data(host);	if (!data)		goto end;	if (data->flags & MMC_DATA_WRITE)		wbsd_fill_fifo(host);	else		wbsd_empty_fifo(host);	/*	 * Done?	 */	if (host->num_sg == 0) {		wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);		tasklet_schedule(&host->finish_tasklet);	}end:	spin_unlock(&host->lock);}static void wbsd_tasklet_crc(unsigned long param){	struct wbsd_host *host = (struct wbsd_host *)param;	struct mmc_data *data;	spin_lock(&host->lock);	if (!host->mrq)		goto end;	data = wbsd_get_data(host);	if (!data)		goto end;	DBGF("CRC error\n");	data->error = -EILSEQ;	tasklet_schedule(&host->finish_tasklet);end:	spin_unlock(&host->lock);}static void wbsd_tasklet_timeout(unsigned long param){	struct wbsd_host *host = (struct wbsd_host *)param;	struct mmc_data *data;	spin_lock(&host->lock);	if (!host->mrq)		goto end;	data = wbsd_get_data(host);	if (!data)		goto end;	DBGF("Timeout\n");	data->error = -ETIMEDOUT;	tasklet_schedule(&host->finish_tasklet);end:	spin_unlock(&host->lock);}static void wbsd_tasklet_finish(unsigned long param){	struct wbsd_host *host = (struct wbsd_host *)param;	struct mmc_data *data;	spin_lock(&host->lock);	WARN_ON(!host->mrq);	if (!host->mrq)		goto end;	data = wbsd_get_data(host);	if (!data)		goto end;	wbsd_finish_data(host, data);end:	spin_unlock(&host->lock);}/* * Interrupt handling */static irqreturn_t wbsd_irq(int irq, void *dev_id){	struct wbsd_host *host = dev_id;	int isr;	isr = inb(host->base + WBSD_ISR);	/*	 * Was it actually our hardware that caused the interrupt?	 */	if (isr == 0xff || isr == 0x00)		return IRQ_NONE;	host->isr |= isr;	/*	 * Schedule tasklets as needed.	 */	if (isr & WBSD_INT_CARD)		tasklet_schedule(&host->card_tasklet);	if (isr & WBSD_INT_FIFO_THRE)		tasklet_schedule(&host->fifo_tasklet);	if (isr & WBSD_INT_CRC)		tasklet_hi_schedule(&host->crc_tasklet);	if (isr & WBSD_INT_TIMEOUT)		tasklet_hi_schedule(&host->timeout_tasklet);	if (isr & WBSD_INT_TC)		tasklet_schedule(&host->finish_tasklet);	return IRQ_HANDLED;}/*****************************************************************************\ *                                                                           * * Device initialisation and shutdown                                        * *                                                                           *\*****************************************************************************//* * Allocate/free MMC structure. */static int __devinit wbsd_alloc_mmc(struct device *dev){	struct mmc_host *mmc;	struct wbsd_host *host;	/*	 * Allocate MMC structure.	 */	mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);	if (!mmc)		return -ENOMEM;	host = mmc_priv(mmc);	host->mmc = mmc;	host->dma = -1;	/*	 * Set host parameters.	 */	mmc->ops = &wbsd_ops;	mmc->f_min = 375000;	mmc->f_max = 24000000;	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;	mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE;	spin_lock_init(&host->lock);	/*	 * Set up timers	 */	init_timer(&host->ignore_timer);	host->ignore_timer.data = (unsigned long)host;	host->ignore_timer.function = wbsd_reset_ignore;	/*	 * Maximum number of segments. Worst case is one sector per segment	 * so this will be 64kB/512.	 */	mmc->max_hw_segs = 128;	mmc->max_phys_segs = 128;	/*	 * Maximum request size. Also limited by 64KiB buffer.	 */	mmc->max_req_size = 65536;	/*	 * Maximum segment size. Could be one segment with the maximum number	 * of bytes.	 */	mmc->max_seg_size = mmc->max_req_size;	/*	 * Maximum block size. We have 12 bits (= 4095) but have to subtract	 * space for CRC. So the maximum is 4095 - 4*2 = 4087.	 */	mmc->max_blk_size = 4087;	/*	 * Maximum block count. There is no real limit so the maximum	 * request size will be the only restriction.	 */	mmc->max_blk_count = mmc->max_req_size;	dev_set_drvdata(dev, mmc);	return 0;}static void wbsd_free_mmc(struct device *dev){	struct mmc_host *mmc;	struct wbsd_host *host;	mmc = dev_get_drvdata(dev);	if (!mmc)		return;	host = mmc_priv(mmc);	BUG_ON(host == NULL);	del_timer_sync(&host->ignore_timer);	mmc_free_host(mmc);	dev_set_drvdata(dev, NULL);}/* * Scan for known chip id:s */static int __devinit wbsd_scan(struct wbsd_host *host){	int i, j, k;	int id;	/*	 * Iterate through all ports, all codes to	 * find hardware that is in our known list.	 */	for (i = 0; i < ARRAY_SIZE(config_ports); i++) {		if (!request_region(config_ports[i], 2, DRIVER_NAME))			continue;		for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {			id = 0xFFFF;			host->config = config_ports[i];			host->unlock_code = unlock_codes[j];			wbsd_unlock_config(host);			outb(WBSD_CONF_ID_HI, config_ports[i]);			id = inb(config_ports[i] + 1) << 8;			outb(WBSD_CONF_ID_LO, config_ports[i]);			id |= inb(config_ports[i] + 1);			wbsd_lock_config(host);			for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {				if (id == valid_ids[k]) {					host->chip_id = id;					return 0;				}			}			if (id != 0xFFFF) {				DBG("Unknown hardware (id %x) found at %x\n",					id, config_ports[i]);			}		}		release_region(config_ports[i], 2);	}	host->config = 0;	host->unlock_code = 0;	return -ENODEV;}/* * Allocate/free io port ranges */static int __devinit wbsd_request_region(struct wbsd_host *host, int base){	if (base & 0x7)		return -EINVAL;	if (!request_region(base, 8, DRIVER_NAME))		return -EIO;	host->base = base;	return 0;}static void wbsd_release_regions(struct wbsd_host *host){	if (host->base)		release_region(host->base, 8);	host->base = 0;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?