www.pudn.com > s3c2440_kernel2.4.18_module_mmc.rar > mmc_slot_s3c2440.c


/*
 * drivers/mmc/mmc_slot_s3c2410.c
 *
 * MMC slot interfaces specific to S3C2410 SD Controller
 *
 * Copyright (C) 2002-2003 MIZI Research, Inc.
 *
 * Author: Chan Gyun Jeong 
 * $Id: mmc_slot_s3c2440.c,v 1.2 2004/01/26 08:29:56 laputa Exp $
 *
 * Revision History:
 *
 * 2002-12-07 Chan Gyun Jeong 
 * - initial release
 * 
 * 2003-01-13 Chan Gyun Jeong 
 * - clean up, and sort of improvements
 *
 * 2003-01-17 Chan Gyun Jeong 
 * - support for interrupt-driven data I/O
 *
 * 2004-01-06 kwang hyun LA 
 * - pre-scaler caculate method changed for s3c2440
 * 
 *
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "mmc.h"

/* Board-specific definitions */
#ifdef CONFIG_S3C2440_SMDK
#define SD_GPIO_CD		GPIO_nCD_SD
#define SD_IRQ_CD		IRQ_nCD_SD
#define SD_CD_LOWACTIVE
#undef USE_POLLING
#define USE_INTERRUPT
#endif

static struct tq_struct card_detect_task;
static volatile int card_in;

#ifdef USE_INTERRUPT
static DECLARE_WAIT_QUEUE_HEAD(wq);
static volatile int reading;
static u_char *buffer;
static int bufcnt;
static int datalen;
static volatile int error;
#endif

/*
 * MMC Data R/W functions 
 */

static int read_data(struct mmc_slot *slot, u_char *buf, int len) 
{
#ifdef USE_POLLING
	int i = 0;
	int cnt;
	u32 stat;

	DEBUG2(4, "[%s] ", __FUNCTION__);

	while (i < len) {
		stat = SDIDSTA;

		if (!card_in) {
			SDIDSTA = stat;
			return -ENODEV;
		}

		if (stat & SDIDSTA_TOUT) {
			DEBUG2(2, "[%s] Timeout Error\n", __FUNCTION__);
			SDIDSTA = stat; /* clear */
			return -EIO;
		}

		stat = SDIFSTA;
		if (stat & SDIFSTA_RX) {
			cnt = (stat & SDIFSTA_CNT);
			while (cnt) {
				*((u32 *)(buf + i)) = SDIDAT;
#ifdef CONFIG_MMC_DEBUG
				DEBUG2(4, "%02x", buf[i + 0]);
				DEBUG2(4, "%02x", buf[i + 1]);
				DEBUG2(4, "%02x", buf[i + 2]);
				DEBUG2(4, "%02x", buf[i + 3]);
#endif
				i += sizeof(u32);
				cnt -= sizeof(u32);
			}
		}
	}

	DEBUG2(4, "\n");

	do {
		stat = SDIDSTA;
		if (!card_in) {
			SDIDSTA = stat;
			return -ENODEV;
		}
	} while (!(stat & SDIDSTA_TOUT) && !(stat & SDIDSTA_DFIN));

	SDIDSTA = stat; /* clear */
	return 0;

#elif defined(USE_INTERRUPT)
	interruptible_sleep_on(&wq);

	if (!card_in) {
		return -ENODEV;
	}

	if (error) {
		return -EIO;
	}

	return 0;
#endif /* USE_POLLING */
}

static int write_data(struct mmc_slot *slot, const u_char *buf, int len)
{
#ifdef USE_POLLING
	int i;
	int cnt;
	u32 stat;

#ifdef CONFIG_MMC_DEBUG
	DEBUG2(4, "[%s] ", __FUNCTION__);
	for(i = 0; i < len; i++)
		DEBUG2(4, "%02x", buf[i]);
	DEBUG2(4, "\n");
#endif

	i = 0;
	while (i < len) {
		stat = SDIFSTA;

		if (!card_in) {
			SDIDSTA = stat;
			return -ENODEV;
		}

		if (stat & SDIFSTA_TX) {
			cnt = SDI_MAX_TX_FIFO - (stat & SDIFSTA_CNT);
			while (cnt) {
				SDIDAT = *((u32 *)(buf + i));
				i += sizeof(u32);
				cnt -= sizeof(u32);
			}
		}
	}

	do {
		stat = SDIDSTA;
		if (!card_in) {
			SDIDSTA = stat;
			return -ENODEV;
		}
	} while (!(stat & SDIDSTA_TOUT) && !(stat & SDIDSTA_DFIN));

	SDIDSTA = stat; /* clear */
	return 0;
#elif defined(USE_INTERRUPT)
	interruptible_sleep_on(&wq);

	if (!card_in) {
		return -ENODEV;
	}

	if (error) {
		return -EIO;
	}

	return 0;	
#endif /* USE_POLLING */
}

#ifdef USE_INTERRUPT
static void sdi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	u32 stat;
	u32 cnt;

	if (reading) {
#ifdef CONFIG_MMC_DEBUG
		if (bufcnt == 0) {
			DEBUG2(4, "[%s] RX: ", __FUNCTION__);
		}
#endif

		stat = SDIFSTA;
		if (stat & SDIFSTA_RX_LAST) {
			DEBUG2(5, "\nRX_LAST\n");
			cnt = (stat & SDIFSTA_CNT) / sizeof(u32);
			while (cnt) {
				*((u32 *)(buffer + bufcnt)) = SDIDAT;
#ifdef CONFIG_MMC_DEBUG
				DEBUG2(4, "%02x", buffer[bufcnt + 0]);
				DEBUG2(4, "%02x", buffer[bufcnt + 1]);
				DEBUG2(4, "%02x", buffer[bufcnt + 2]);
				DEBUG2(4, "%02x", buffer[bufcnt + 3]);
#endif
				bufcnt += sizeof(u32);
				cnt--;
			}
		} else if (stat & SDIFSTA_RX_FULL) {
			DEBUG2(5, "\nRX_FULL\n");
			cnt = SDI_MAX_RX_FIFO / sizeof(u32);
			while (cnt && bufcnt < datalen) {
				*((u32 *)(buffer + bufcnt)) = SDIDAT;
#ifdef CONFIG_MMC_DEBUG
				DEBUG2(4, "%02x", buffer[bufcnt + 0]);
				DEBUG2(4, "%02x", buffer[bufcnt + 1]);
				DEBUG2(4, "%02x", buffer[bufcnt + 2]);
				DEBUG2(4, "%02x", buffer[bufcnt + 3]);
#endif
				bufcnt += sizeof(u32);
				cnt--;
			}
		}

		stat = SDIDSTA;
		if (stat & SDIDSTA_DFIN) {
			DEBUG2(4, "\n");
			DEBUG2(5, "DFIN\n");
			SDIDSTA = stat;
			wake_up_interruptible(&wq);
		} else if (stat & SDIDSTA_TOUT) {
			DEBUG2(4, "\n");
			DEBUG2(5, "DTOUT\n");
			SDIDSTA = stat;
			error = 1;
			wake_up_interruptible(&wq);
		}
	} else {
		stat = SDIFSTA;
		if (stat & SDIFSTA_TX_EMP) {
			DEBUG2(5, "TX_EMP\n");
			cnt = SDI_MAX_TX_FIFO / sizeof(u32);
			while (cnt && bufcnt < datalen) {
				SDIDAT = *((u32 *)(buffer + bufcnt));
				bufcnt += sizeof(u32);
				cnt--;
			}
		}

		stat = SDIDSTA;
		if (stat & SDIDSTA_DFIN) {
			DEBUG2(5, "DFIN\n");
			SDIDSTA = stat;
			wake_up_interruptible(&wq);
		} else if (stat & SDIDSTA_TOUT) {
			DEBUG2(5, "DTOUT\n");
			SDIDSTA = stat;
			error = 1;
			wake_up_interruptible(&wq);
		}
	}
}
#endif /* USE_INTERRRUPT */

/*
 * SD Slot Interface funtions
 */

static void power_up(struct mmc_slot *slot)
{
	/* Set block size to 512 bytes */
	SDIBSIZE = 0x200;

	/* Set timeout count */
	SDIDTIMER = 0x7ffffff; //  timeout value for 2440 [22..0]

	/* Disable SDI interrupt */
	SDIIMSK = 0x0;

	/* Type B, FIFO reset, SD clock enable */
	SDICON = SDICON_LE | SDICON_FRESET | SDICON_ENCLK;

	/* clear all */
	SDIDSTA = SDIDSTA_ALL;

	/* Wait for power-up */
	mdelay(125);
}

static void power_down(struct mmc_slot *slot)
{
	/* Disable SD Controller */
	SDICON = 0x0;
	SDIIMSK = 0x0;
	SDIDTIMER = 0x0;
	SDIBSIZE = 0x0;
}

static void set_clock(struct mmc_slot *slot, int rate)
{
	unsigned long val, pclk;

	if (rate == MMC_MMC_CLOCK_HIGH) {
		SDICON |= SDICON_CLKTYP;  // 1 MMC TYPE CLOCK
		rate = 20000000;
	}

	pclk = s3c2440_get_bus_clk(GET_PCLK); // laputa to receive s3c2440 PCLK from 2.4.19
	val = (pclk / (rate)) ; // protect mmc over clocking to set prescler
	SDIPRE = (SDIPRE_MSK & val); // prescaler set value anding
}

static int send_cmd(struct mmc_slot *slot,  struct mmc_cmd *cmd)
{
	int ret;
	u32 stat;

	if (cmd->res_flag & MMC_RES_FLAG_RDATA) {
#ifdef USE_INTERRUPT
		SDIIMSK = SDIIMSK_TOUT | SDIIMSK_DFIN | SDIIMSK_RX_LAST | 
			SDIIMSK_RX_FULL;
		reading = 1;
		buffer = cmd->data;
		bufcnt = 0;
		datalen = cmd->data_len;
		error = 0;
#endif

		stat = SDIDCON_RACMD_1 | SDIDCON_BLK | SDIDCON_RX | 1 << 0;
		SDIBSIZE = cmd->data_len;
		SDICON |= SDICON_FRESET;
		SDIDCON = stat;
	} else if (cmd->res_flag & MMC_RES_FLAG_WDATA) {
#ifdef USE_INTERRUPT
		SDIIMSK = SDIIMSK_TOUT | SDIIMSK_DFIN | SDIIMSK_TX_EMP; 
		reading = 0;
		buffer = cmd->data;
		bufcnt = 0;
		datalen = cmd->data_len;
		error = 0;
#endif

		stat = SDIDCON_TARSP_1 | SDIDCON_BLK | SDIDCON_TX | 1 << 0;
		SDIBSIZE = cmd->data_len;
		SDICON |= SDICON_FRESET;
		SDIDCON = stat;
	} else if (cmd->res_flag & MMC_RES_FLAG_DATALINE) {
#ifdef USE_INTERRUPT
		SDIIMSK = SDIIMSK_TOUT | SDIIMSK_DFIN | SDIIMSK_RX_LAST | 
			SDIIMSK_RX_FULL;
		reading = 1;
		buffer = cmd->res;
		bufcnt = 0;
		datalen = cmd->data_len;
		error = 0;
#endif

		stat = SDIDCON_RACMD_1 | SDIDCON_BLK | SDIDCON_RX | 1 << 0;
		SDIBSIZE = cmd->data_len;
		SDICON |= SDICON_FRESET;
		SDIDCON = stat;
	}

	stat = SDICCON_START | cmd->cmd;
	if (cmd->res_type != MMC_RES_TYPE_NONE) {
		stat |=  SDICCON_WRSP;
		if (cmd->res_type == MMC_RES_TYPE_R2) {
			stat |= SDICCON_LRSP;
		}
	}
	SDICARG = cmd->arg;
	SDICCON = stat;

	if (cmd->res_type != MMC_RES_TYPE_NONE) {
		if (cmd->res_flag & MMC_RES_FLAG_DATALINE) {
			ret = read_data(slot, cmd->res, cmd->data_len);
			goto cmdexit;
		} else {
			while(1) {
				stat = SDICSTA;
				if (!card_in) {
					SDICSTA = stat;
					return -ENODEV;
				}
				if (stat & SDICSTA_TOUT) {
					ret = -ETIMEDOUT;
					SDICSTA = stat; /* clear bits */
					DEBUG2(2, "[%s] Timeout Error\n",
					       __FUNCTION__);
					goto cmdexit;
				}
				if (stat & SDICSTA_RSP) break;
			}
			if (!(cmd->res_flag & MMC_RES_FLAG_NOCRC)) {
				if ((stat & SDICSTA_ALLFLAG) != 
				    (SDICSTA_SENT | SDICSTA_RSP)) {
#if 0
					ret = -EIO;
					SDICSTA = stat; /* clear bits */
					DEBUG2(2, "[%s] CRC Error\n",
					       __FUNCTION__);
					goto cmdexit;
#endif
				}
			}
			SDICSTA = stat; /* clear bits */

			if (cmd->res_flag & MMC_RES_FLAG_RDATA) {
				ret = read_data(slot, cmd->data, 
						cmd->data_len);
				goto cmdexit;
			}

			if (cmd->res_flag & MMC_RES_FLAG_WDATA) {
				ret = write_data(slot, cmd->data, 
						 cmd->data_len);
				goto cmdexit;
			}

			if (mmc_get_res_len(cmd->res_type) == MMC_RES_LEN_LONG) {
				stat = SDIRSP0;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res, &stat, sizeof(stat));
				stat = SDIRSP1;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res + (1 * sizeof(stat)), 
				       &stat, sizeof(stat));
				stat = SDIRSP2;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res + (2 * sizeof(stat)), 
				       &stat, sizeof(stat));
				stat = SDIRSP3;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res + (3 * sizeof(stat)), 
				       &stat, sizeof(stat));
			} else {
				stat = SDIRSP0;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res, &stat, sizeof(stat));
				stat = SDIRSP1;
				stat = cpu_to_be32(stat);
				memcpy(cmd->res + (1 * sizeof(stat)), 
				       &stat, sizeof(stat));
			}
		}
	} else {
		do {
			stat = SDICSTA;
			if (!card_in) {
				SDICSTA = stat;
				return -ENODEV;
			}
		} while (!(stat & SDICSTA_SENT));
		SDICSTA = stat; /* clear bits */
	}

	ret = 0;
 cmdexit:
	return ret;
}

#define WIDE_BUS 0
#define STANDARD_BUS 1
static struct mmc_slot slot = {
	id:			0,
	narrow_bus:     WIDE_BUS,
	power_up:		power_up,
	power_down:		power_down,
	set_clock:		set_clock,
	send_cmd:		send_cmd,
};

static void add_task_handler(void *data)
{
	struct mmc_slot *slot = (struct mmc_slot *)data;
	int wp;

#ifdef CONFIG_S3C2440_SMDK
	/* Not available on SMDK2410 */
	wp = 0;
#endif

	if (wp) {
		slot->readonly = 1;
	}
	else {
		slot->readonly = 0;
	}
	add_mmc_device(slot);
}

static void del_task_handler(void *data)
{
	struct mmc_slot *slot = (struct mmc_slot *)data;

	del_mmc_device(slot);
}

static void card_detect_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	int empty;

#ifdef SD_CD_LOWACTIVE
	empty = read_gpio_bit(SD_GPIO_CD);
#else
	empty = !read_gpio_bit(SD_GPIO_CD);
#endif
	DEBUG2(1, "[%s] SD CD = %d\n", __FUNCTION__, !empty);

	if (!card_in && !empty) {
		/* card inserted */
		DEBUG2(1, "[%s] card inserted\n", __FUNCTION__);
		card_in = 1;
		card_detect_task.data = (void *)dev_id;
		card_detect_task.routine = add_task_handler;
		schedule_task(&card_detect_task);
	} else if (card_in && empty) {
		/* card ejected */
		DEBUG2(1, "[%s] card ejected\n", __FUNCTION__);
		card_in = 0;
#ifdef USE_INTERRUPT
		wake_up_interruptible(&wq);
#endif
		card_detect_task.data = (void *)dev_id;
		card_detect_task.routine = del_task_handler;
		schedule_task(&card_detect_task);
	}
}

static int __init init_mmc_slot_s3c2410(void)
{
	int ret;

	/* Initialize status variables */
	card_in = 0;

	/* Initialize h/w settings */
	set_gpio_ctrl(GPIO_SDDAT3 | GPIO_MODE_SDDAT | GPIO_PULLUP_EN);
	set_gpio_ctrl(GPIO_SDDAT2 | GPIO_MODE_SDDAT | GPIO_PULLUP_EN);
	set_gpio_ctrl(GPIO_SDDAT1 | GPIO_MODE_SDDAT | GPIO_PULLUP_EN);
	set_gpio_ctrl(GPIO_SDDAT0 | GPIO_MODE_SDDAT | GPIO_PULLUP_EN);
	set_gpio_ctrl(GPIO_SDCMD | GPIO_MODE_SDCMD | GPIO_PULLUP_EN);
	set_gpio_ctrl(GPIO_SDCLK | GPIO_MODE_SDCLK | GPIO_PULLUP_EN);

#ifdef CONFIG_S3C2440_SMDK
	set_gpio_ctrl(SD_GPIO_CD);
#endif
	
	/* Register IRQ handlers */
	set_external_irq(SD_IRQ_CD, EXT_BOTH_EDGES, GPIO_PULLUP_DIS);
	ret = request_irq(SD_IRQ_CD, card_detect_interrupt, 
			  SA_INTERRUPT, "SD CD", (void *)&slot);
	if (ret) {
		printk("MMC Slot: request_irq(SD CD) failed\n");
		goto err1;
	}

#ifdef USE_INTERRUPT
	ret = request_irq(IRQ_SDI, sdi_interrupt, 
			  SA_INTERRUPT, "SDI", (void *)&slot);
	if (ret) {
		printk("MMC Slot: request_irq(SDI) failed\n");
		goto err2;
	}
#endif

	printk("MMC Slot initialized\n");

	card_detect_interrupt(SD_IRQ_CD, (void *)&slot, NULL);

	return 0;
#ifdef USE_INTERRUPT
 err2:
	free_irq(SD_IRQ_CD, (void *)&slot);
#endif
 err1:
	return ret;
}

static void __exit exit_mmc_slot_s3c2410(void)
{
#ifdef USE_INTERRUPT
	free_irq(IRQ_SDI, (void *)&slot);
#endif
	free_irq(SD_IRQ_CD, (void *)&slot);
	if (card_in) {
		card_in = 0;
		del_task_handler((void *)&slot);
	}
}

module_init(init_mmc_slot_s3c2410);
module_exit(exit_mmc_slot_s3c2410);

MODULE_AUTHOR("Chan Gyun Jeong ");
MODULE_LICENSE("Not GPL, Proprietary License");
MODULE_DESCRIPTION("MMC slot interfaces specific to S3C2410 SD Controller");