tifm_sd.c

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

C
1,096
字号
/* *  tifm_sd.c - TI FlashMedia driver * *  Copyright (C) 2006 Alex Dubov <oakad@yahoo.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Special thanks to Brad Campbell for extensive testing of this driver. * */#include <linux/tifm.h>#include <linux/mmc/host.h>#include <linux/highmem.h>#include <linux/scatterlist.h>#include <asm/io.h>#define DRIVER_NAME "tifm_sd"#define DRIVER_VERSION "0.8"static int no_dma = 0;static int fixed_timeout = 0;module_param(no_dma, bool, 0644);module_param(fixed_timeout, bool, 0644);/* Constants here are mostly from OMAP5912 datasheet */#define TIFM_MMCSD_RESET      0x0002#define TIFM_MMCSD_CLKMASK    0x03ff#define TIFM_MMCSD_POWER      0x0800#define TIFM_MMCSD_4BBUS      0x8000#define TIFM_MMCSD_RXDE       0x8000   /* rx dma enable */#define TIFM_MMCSD_TXDE       0x0080   /* tx dma enable */#define TIFM_MMCSD_BUFINT     0x0c00   /* set bits: AE, AF */#define TIFM_MMCSD_DPE        0x0020   /* data timeout counted in kilocycles */#define TIFM_MMCSD_INAB       0x0080   /* abort / initialize command */#define TIFM_MMCSD_READ       0x8000#define TIFM_MMCSD_ERRMASK    0x01e0   /* set bits: CCRC, CTO, DCRC, DTO */#define TIFM_MMCSD_EOC        0x0001   /* end of command phase  */#define TIFM_MMCSD_CD         0x0002   /* card detect           */#define TIFM_MMCSD_CB         0x0004   /* card enter busy state */#define TIFM_MMCSD_BRS        0x0008   /* block received/sent   */#define TIFM_MMCSD_EOFB       0x0010   /* card exit busy state  */#define TIFM_MMCSD_DTO        0x0020   /* data time-out         */#define TIFM_MMCSD_DCRC       0x0040   /* data crc error        */#define TIFM_MMCSD_CTO        0x0080   /* command time-out      */#define TIFM_MMCSD_CCRC       0x0100   /* command crc error     */#define TIFM_MMCSD_AF         0x0400   /* fifo almost full      */#define TIFM_MMCSD_AE         0x0800   /* fifo almost empty     */#define TIFM_MMCSD_OCRB       0x1000   /* OCR busy              */#define TIFM_MMCSD_CIRQ       0x2000   /* card irq (cmd40/sdio) */#define TIFM_MMCSD_CERR       0x4000   /* card status error     */#define TIFM_MMCSD_ODTO       0x0040   /* open drain / extended timeout */#define TIFM_MMCSD_CARD_RO    0x0200   /* card is read-only     */#define TIFM_MMCSD_FIFO_SIZE  0x0020#define TIFM_MMCSD_RSP_R0     0x0000#define TIFM_MMCSD_RSP_R1     0x0100#define TIFM_MMCSD_RSP_R2     0x0200#define TIFM_MMCSD_RSP_R3     0x0300#define TIFM_MMCSD_RSP_R4     0x0400#define TIFM_MMCSD_RSP_R5     0x0500#define TIFM_MMCSD_RSP_R6     0x0600#define TIFM_MMCSD_RSP_BUSY   0x0800#define TIFM_MMCSD_CMD_BC     0x0000#define TIFM_MMCSD_CMD_BCR    0x1000#define TIFM_MMCSD_CMD_AC     0x2000#define TIFM_MMCSD_CMD_ADTC   0x3000#define TIFM_MMCSD_MAX_BLOCK_SIZE  0x0800ULenum {	CMD_READY    = 0x0001,	FIFO_READY   = 0x0002,	BRS_READY    = 0x0004,	SCMD_ACTIVE  = 0x0008,	SCMD_READY   = 0x0010,	CARD_BUSY    = 0x0020,	DATA_CARRY   = 0x0040};struct tifm_sd {	struct tifm_dev       *dev;	unsigned short        eject:1,			      open_drain:1,			      no_dma:1;	unsigned short        cmd_flags;	unsigned int          clk_freq;	unsigned int          clk_div;	unsigned long         timeout_jiffies;	struct tasklet_struct finish_tasklet;	struct timer_list     timer;	struct mmc_request    *req;	int                   sg_len;	int                   sg_pos;	unsigned int          block_pos;	struct scatterlist    bounce_buf;	unsigned char         bounce_buf_data[TIFM_MMCSD_MAX_BLOCK_SIZE];};/* for some reason, host won't respond correctly to readw/writew */static void tifm_sd_read_fifo(struct tifm_sd *host, struct page *pg,			      unsigned int off, unsigned int cnt){	struct tifm_dev *sock = host->dev;	unsigned char *buf;	unsigned int pos = 0, val;	buf = kmap_atomic(pg, KM_BIO_DST_IRQ) + off;	if (host->cmd_flags & DATA_CARRY) {		buf[pos++] = host->bounce_buf_data[0];		host->cmd_flags &= ~DATA_CARRY;	}	while (pos < cnt) {		val = readl(sock->addr + SOCK_MMCSD_DATA);		buf[pos++] = val & 0xff;		if (pos == cnt) {			host->bounce_buf_data[0] = (val >> 8) & 0xff;			host->cmd_flags |= DATA_CARRY;			break;		}		buf[pos++] = (val >> 8) & 0xff;	}	kunmap_atomic(buf - off, KM_BIO_DST_IRQ);}static void tifm_sd_write_fifo(struct tifm_sd *host, struct page *pg,			       unsigned int off, unsigned int cnt){	struct tifm_dev *sock = host->dev;	unsigned char *buf;	unsigned int pos = 0, val;	buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + off;	if (host->cmd_flags & DATA_CARRY) {		val = host->bounce_buf_data[0] | ((buf[pos++] << 8) & 0xff00);		writel(val, sock->addr + SOCK_MMCSD_DATA);		host->cmd_flags &= ~DATA_CARRY;	}	while (pos < cnt) {		val = buf[pos++];		if (pos == cnt) {			host->bounce_buf_data[0] = val & 0xff;			host->cmd_flags |= DATA_CARRY;			break;		}		val |= (buf[pos++] << 8) & 0xff00;		writel(val, sock->addr + SOCK_MMCSD_DATA);	}	kunmap_atomic(buf - off, KM_BIO_SRC_IRQ);}static void tifm_sd_transfer_data(struct tifm_sd *host){	struct mmc_data *r_data = host->req->cmd->data;	struct scatterlist *sg = r_data->sg;	unsigned int off, cnt, t_size = TIFM_MMCSD_FIFO_SIZE * 2;	unsigned int p_off, p_cnt;	struct page *pg;	if (host->sg_pos == host->sg_len)		return;	while (t_size) {		cnt = sg[host->sg_pos].length - host->block_pos;		if (!cnt) {			host->block_pos = 0;			host->sg_pos++;			if (host->sg_pos == host->sg_len) {				if ((r_data->flags & MMC_DATA_WRITE)				    && DATA_CARRY)					writel(host->bounce_buf_data[0],					       host->dev->addr					       + SOCK_MMCSD_DATA);				return;			}			cnt = sg[host->sg_pos].length;		}		off = sg[host->sg_pos].offset + host->block_pos;		pg = nth_page(sg_page(&sg[host->sg_pos]), off >> PAGE_SHIFT);		p_off = offset_in_page(off);		p_cnt = PAGE_SIZE - p_off;		p_cnt = min(p_cnt, cnt);		p_cnt = min(p_cnt, t_size);		if (r_data->flags & MMC_DATA_READ)			tifm_sd_read_fifo(host, pg, p_off, p_cnt);		else if (r_data->flags & MMC_DATA_WRITE)			tifm_sd_write_fifo(host, pg, p_off, p_cnt);		t_size -= p_cnt;		host->block_pos += p_cnt;	}}static void tifm_sd_copy_page(struct page *dst, unsigned int dst_off,			      struct page *src, unsigned int src_off,			      unsigned int count){	unsigned char *src_buf = kmap_atomic(src, KM_BIO_SRC_IRQ) + src_off;	unsigned char *dst_buf = kmap_atomic(dst, KM_BIO_DST_IRQ) + dst_off;	memcpy(dst_buf, src_buf, count);	kunmap_atomic(dst_buf - dst_off, KM_BIO_DST_IRQ);	kunmap_atomic(src_buf - src_off, KM_BIO_SRC_IRQ);}static void tifm_sd_bounce_block(struct tifm_sd *host, struct mmc_data *r_data){	struct scatterlist *sg = r_data->sg;	unsigned int t_size = r_data->blksz;	unsigned int off, cnt;	unsigned int p_off, p_cnt;	struct page *pg;	dev_dbg(&host->dev->dev, "bouncing block\n");	while (t_size) {		cnt = sg[host->sg_pos].length - host->block_pos;		if (!cnt) {			host->block_pos = 0;			host->sg_pos++;			if (host->sg_pos == host->sg_len)				return;			cnt = sg[host->sg_pos].length;		}		off = sg[host->sg_pos].offset + host->block_pos;		pg = nth_page(sg_page(&sg[host->sg_pos]), off >> PAGE_SHIFT);		p_off = offset_in_page(off);		p_cnt = PAGE_SIZE - p_off;		p_cnt = min(p_cnt, cnt);		p_cnt = min(p_cnt, t_size);		if (r_data->flags & MMC_DATA_WRITE)			tifm_sd_copy_page(sg_page(&host->bounce_buf),					  r_data->blksz - t_size,					  pg, p_off, p_cnt);		else if (r_data->flags & MMC_DATA_READ)			tifm_sd_copy_page(pg, p_off, sg_page(&host->bounce_buf),					  r_data->blksz - t_size, p_cnt);		t_size -= p_cnt;		host->block_pos += p_cnt;	}}static int tifm_sd_set_dma_data(struct tifm_sd *host, struct mmc_data *r_data){	struct tifm_dev *sock = host->dev;	unsigned int t_size = TIFM_DMA_TSIZE * r_data->blksz;	unsigned int dma_len, dma_blk_cnt, dma_off;	struct scatterlist *sg = NULL;	unsigned long flags;	if (host->sg_pos == host->sg_len)		return 1;	if (host->cmd_flags & DATA_CARRY) {		host->cmd_flags &= ~DATA_CARRY;		local_irq_save(flags);		tifm_sd_bounce_block(host, r_data);		local_irq_restore(flags);		if (host->sg_pos == host->sg_len)			return 1;	}	dma_len = sg_dma_len(&r_data->sg[host->sg_pos]) - host->block_pos;	if (!dma_len) {		host->block_pos = 0;		host->sg_pos++;		if (host->sg_pos == host->sg_len)			return 1;		dma_len = sg_dma_len(&r_data->sg[host->sg_pos]);	}	if (dma_len < t_size) {		dma_blk_cnt = dma_len / r_data->blksz;		dma_off = host->block_pos;		host->block_pos += dma_blk_cnt * r_data->blksz;	} else {		dma_blk_cnt = TIFM_DMA_TSIZE;		dma_off = host->block_pos;		host->block_pos += t_size;	}	if (dma_blk_cnt)		sg = &r_data->sg[host->sg_pos];	else if (dma_len) {		if (r_data->flags & MMC_DATA_WRITE) {			local_irq_save(flags);			tifm_sd_bounce_block(host, r_data);			local_irq_restore(flags);		} else			host->cmd_flags |= DATA_CARRY;		sg = &host->bounce_buf;		dma_off = 0;		dma_blk_cnt = 1;	} else		return 1;	dev_dbg(&sock->dev, "setting dma for %d blocks\n", dma_blk_cnt);	writel(sg_dma_address(sg) + dma_off, sock->addr + SOCK_DMA_ADDRESS);	if (r_data->flags & MMC_DATA_WRITE)		writel((dma_blk_cnt << 8) | TIFM_DMA_TX | TIFM_DMA_EN,		       sock->addr + SOCK_DMA_CONTROL);	else		writel((dma_blk_cnt << 8) | TIFM_DMA_EN,		       sock->addr + SOCK_DMA_CONTROL);	return 0;}static unsigned int tifm_sd_op_flags(struct mmc_command *cmd){	unsigned int rc = 0;	switch (mmc_resp_type(cmd)) {	case MMC_RSP_NONE:		rc |= TIFM_MMCSD_RSP_R0;		break;	case MMC_RSP_R1B:		rc |= TIFM_MMCSD_RSP_BUSY; // deliberate fall-through	case MMC_RSP_R1:		rc |= TIFM_MMCSD_RSP_R1;		break;	case MMC_RSP_R2:		rc |= TIFM_MMCSD_RSP_R2;		break;	case MMC_RSP_R3:		rc |= TIFM_MMCSD_RSP_R3;		break;	default:		BUG();	}	switch (mmc_cmd_type(cmd)) {	case MMC_CMD_BC:		rc |= TIFM_MMCSD_CMD_BC;		break;	case MMC_CMD_BCR:		rc |= TIFM_MMCSD_CMD_BCR;		break;	case MMC_CMD_AC:		rc |= TIFM_MMCSD_CMD_AC;		break;	case MMC_CMD_ADTC:		rc |= TIFM_MMCSD_CMD_ADTC;		break;	default:		BUG();	}	return rc;}static void tifm_sd_exec(struct tifm_sd *host, struct mmc_command *cmd){	struct tifm_dev *sock = host->dev;	unsigned int cmd_mask = tifm_sd_op_flags(cmd);	if (host->open_drain)		cmd_mask |= TIFM_MMCSD_ODTO;	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))		cmd_mask |= TIFM_MMCSD_READ;	dev_dbg(&sock->dev, "executing opcode 0x%x, arg: 0x%x, mask: 0x%x\n",		cmd->opcode, cmd->arg, cmd_mask);	writel((cmd->arg >> 16) & 0xffff, sock->addr + SOCK_MMCSD_ARG_HIGH);	writel(cmd->arg & 0xffff, sock->addr + SOCK_MMCSD_ARG_LOW);	writel(cmd->opcode | cmd_mask, sock->addr + SOCK_MMCSD_COMMAND);}static void tifm_sd_fetch_resp(struct mmc_command *cmd, struct tifm_dev *sock){	cmd->resp[0] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x1c) << 16)		       | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x18);	cmd->resp[1] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x14) << 16)		       | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x10);	cmd->resp[2] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x0c) << 16)		       | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x08);	cmd->resp[3] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x04) << 16)		       | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x00);}static void tifm_sd_check_status(struct tifm_sd *host){	struct tifm_dev *sock = host->dev;	struct mmc_command *cmd = host->req->cmd;	if (cmd->error)		goto finish_request;	if (!(host->cmd_flags & CMD_READY))		return;	if (cmd->data) {		if (cmd->data->error) {			if ((host->cmd_flags & SCMD_ACTIVE)			    && !(host->cmd_flags & SCMD_READY))				return;			goto finish_request;		}		if (!(host->cmd_flags & BRS_READY))			return;		if (!(host->no_dma || (host->cmd_flags & FIFO_READY)))			return;		if (cmd->data->flags & MMC_DATA_WRITE) {			if (host->req->stop) {				if (!(host->cmd_flags & SCMD_ACTIVE)) {					host->cmd_flags |= SCMD_ACTIVE;					writel(TIFM_MMCSD_EOFB					       | readl(sock->addr						       + SOCK_MMCSD_INT_ENABLE),					       sock->addr					       + SOCK_MMCSD_INT_ENABLE);					tifm_sd_exec(host, host->req->stop);					return;				} else {					if (!(host->cmd_flags & SCMD_READY)					    || (host->cmd_flags & CARD_BUSY))						return;					writel((~TIFM_MMCSD_EOFB)					       & readl(sock->addr						       + SOCK_MMCSD_INT_ENABLE),					       sock->addr					       + SOCK_MMCSD_INT_ENABLE);				}			} else {				if (host->cmd_flags & CARD_BUSY)					return;				writel((~TIFM_MMCSD_EOFB)				       & readl(sock->addr					       + SOCK_MMCSD_INT_ENABLE),				       sock->addr + SOCK_MMCSD_INT_ENABLE);			}		} else {			if (host->req->stop) {				if (!(host->cmd_flags & SCMD_ACTIVE)) {					host->cmd_flags |= SCMD_ACTIVE;					tifm_sd_exec(host, host->req->stop);					return;				} else {					if (!(host->cmd_flags & SCMD_READY))						return;				}			}		}	}finish_request:	tasklet_schedule(&host->finish_tasklet);}/* Called from interrupt handler */static void tifm_sd_data_event(struct tifm_dev *sock){	struct tifm_sd *host;	unsigned int fifo_status = 0;	struct mmc_data *r_data = NULL;	spin_lock(&sock->lock);	host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));	fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);	dev_dbg(&sock->dev, "data event: fifo_status %x, flags %x\n",		fifo_status, host->cmd_flags);	if (host->req) {		r_data = host->req->cmd->data;		if (r_data && (fifo_status & TIFM_FIFO_READY)) {			if (tifm_sd_set_dma_data(host, r_data)) {				host->cmd_flags |= FIFO_READY;				tifm_sd_check_status(host);			}		}	}	writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);	spin_unlock(&sock->lock);}/* Called from interrupt handler */static void tifm_sd_card_event(struct tifm_dev *sock){	struct tifm_sd *host;	unsigned int host_status = 0;	int cmd_error = 0;	struct mmc_command *cmd = NULL;	unsigned long flags;	spin_lock(&sock->lock);	host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));	host_status = readl(sock->addr + SOCK_MMCSD_STATUS);	dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",		host_status, host->cmd_flags);	if (host->req) {		cmd = host->req->cmd;		if (host_status & TIFM_MMCSD_ERRMASK) {			writel(host_status & TIFM_MMCSD_ERRMASK,			       sock->addr + SOCK_MMCSD_STATUS);			if (host_status & TIFM_MMCSD_CTO)				cmd_error = -ETIMEDOUT;			else if (host_status & TIFM_MMCSD_CCRC)				cmd_error = -EILSEQ;			if (cmd->data) {				if (host_status & TIFM_MMCSD_DTO)					cmd->data->error = -ETIMEDOUT;				else if (host_status & TIFM_MMCSD_DCRC)					cmd->data->error = -EILSEQ;			}			writel(TIFM_FIFO_INT_SETALL,			       sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);			writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);			if (host->req->stop) {				if (host->cmd_flags & SCMD_ACTIVE) {					host->req->stop->error = cmd_error;					host->cmd_flags |= SCMD_READY;				} else {					cmd->error = cmd_error;					host->cmd_flags |= SCMD_ACTIVE;					tifm_sd_exec(host, host->req->stop);					goto done;				}

⌨️ 快捷键说明

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