diff options
Diffstat (limited to 'drivers/mmc/host/ak88-mmc/ak88_mci.c')
-rw-r--r-- | drivers/mmc/host/ak88-mmc/ak88_mci.c | 1195 |
1 files changed, 1195 insertions, 0 deletions
diff --git a/drivers/mmc/host/ak88-mmc/ak88_mci.c b/drivers/mmc/host/ak88-mmc/ak88_mci.c new file mode 100644 index 00000000000..456417b0dc5 --- /dev/null +++ b/drivers/mmc/host/ak88-mmc/ak88_mci.c @@ -0,0 +1,1195 @@ +/* + * linux/drivers/mmc/host/ak88_mci.c - AK88 MMC/SD/SDIO driver + * + * Copyright (C) 2010 Anyka, Ltd, 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 version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/highmem.h> +#include <linux/log2.h> +#include <linux/mmc/host.h> +#include <linux/clk.h> +#include <linux/scatterlist.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/dma-mapping.h> + +#include <asm/cacheflush.h> +#include <asm/div64.h> +#include <asm/io.h> +#include <asm/sizes.h> + +#include <mach/devices_ak880x.h> +#include <mach/gpio.h> +#include "ak88_mci.h" + +#define DRIVER_NAME "ak88_mci" + +//#define AKMCI_INNERFIFO_PIO /* only 4bytes inner fifo */ +#define AKMCI_L2FIFO_PIO +//#define AKMCI_L2FIFO_DMA + +#define DBG(host,fmt,args...) \ + pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) + +#define PK1(fmt...) //printk(fmt) +#define PK(fmt...) //printk(fmt) + +#define SRDPIN_USE_MUTEX + +#ifdef SRDPIN_USE_MUTEX +extern struct mutex nand_lock; +#else +extern struct semaphore nand_lock; +#endif + +#if defined AKMCI_L2FIFO_PIO || defined AKMCI_L2FIFO_DMA +static unsigned int fmax = (20*1000*1000); +#elif defined AKMCI_INNERFIFO_PIO +static unsigned int fmax = (4*1000*1000); +#else +#error "Please select one FIFO translation mode!" +#endif + +static void ak88_mci_dump_regs(void *base) +{ + int i; + + for (i = 0; i <= 0x40; i+=4) { + PK("%02x - %08x\n", i, ioread32(base+i)); + } +} + +static void dump_data(struct mmc_data *data) +{ + struct scatterlist *sg; + u8 *sg_dat, *sg_end; + unsigned int blks, blkdat; + + printk("%s\n", __func__); + + sg = data->sg; + sg_dat = sg_virt(sg); + sg_end = sg_dat + sg->length; + + for (blks = 0; blks < data->blocks; blks++) { + for (blkdat = 0; blkdat < data->blksz; blkdat++) { + printk("%02X ", *sg_dat); + if ((blkdat % 16) == 15) + printk("\n"); + sg_dat++; + if (sg_dat >= sg_end) { + sg = sg_next(sg); + if (sg == NULL) + break; + sg_dat = sg_virt(sg); + sg_end = sg_dat + sg->length; + } + } + printk("\n"); + } +} + +#ifdef AKMCI_L2FIFO_PIO +static void +mci_xfer(struct ak88_mci_host *host) +{ + int i, sg_remain; + u32 *src, *dst; + + PK("%s\n", __func__); + + if (host->data->flags & MMC_DATA_WRITE) { + src = sg_virt(host->sg_ptr) + host->sg_off; + dst = host->l2fifo; + } else { + src = host->l2fifo; + dst = sg_virt(host->sg_ptr) + host->sg_off; + } + + /* + limit: blksz(512), host_remain, sg + + xfer_len = min(host->size, host->data->blksz); + if (xfer_len <= 0) + return 0; + + sg_remain = ; + while (sg_remain <= 0) { + next_sg(); + sg_remain = ; + } + + if (sg_remain >= xfer_len) + memcpy(dst, src, xfer_len); + + do { + sg_remain = host->sg_ptr->length - host->sg_off; + x_len = xfer_len; + if (sg_remain < x_len) + x_len = sg_remain; + memcpy(dst, src, x_len); + xfer_len -= x_len; + sg_remain -= x_len; + }while (xfer_len > 0 || sg == NULL); + + while (host->sg_ptr && offset < xfer_len) { + sg_remain = host->sg_ptr->length - host->sg_off; + x_len = min(sg_remain, xfer_len); + + memcpy(dst, src, x_len); + if (read) + memcpy(sg_virt(host->sg_ptr) + host->sg_off, buffer + offset, x_len); + else + memcpy(buffer + offset, sg_virt(host->sg_ptr)+host->sg_off, x_len); + + offset += x_len; + } + + xfer_len -= x_len; + if (xfer_len <= 0) { + sg_remain -= x_len; + break; // done + } + + host->sg_ptr = sg_next(); + host->sg_off = 0; + if (host->sg_ptr == NULL) + break; + + if (read) { + dst = sg_virt(host->sg_ptr); + src += x_len; + } else { + src = sg_virt(host->sg_ptr); + dst += x_len; + } + } + host->sg_off = sg->length + */ + + sg_remain = host->sg_ptr->length - host->sg_off; + for (i = 0; i < host->data->blksz; i+=4) { + *dst = *src; + src++; + dst++; + + host->data_xfered += 4; + host->size -= 4; + + sg_remain -= 4; + if (sg_remain <= 0) { + host->sg_ptr = sg_next(host->sg_ptr); + if (host->sg_ptr == NULL) + return; + host->sg_off = 0; + if (host->data->flags & MMC_DATA_WRITE) + src = sg_virt(host->sg_ptr) + host->sg_off; + else + dst = sg_virt(host->sg_ptr) + host->sg_off; + sg_remain = host->sg_ptr->length - host->sg_off; + } + } + PK("\n"); + + host->sg_off = host->sg_ptr->length - sg_remain; +} +#endif + +static void ak88_mci_stop_data(struct ak88_mci_host *host) +{ + u32 masks; + + PK1("%s\n", __func__); + + writel(0, host->base + AK88MCIDMACTRL); + writel(0, host->base + AK88MCIDATACTRL); + masks = readl(host->base + AK88MCIMASK); + masks &= ~(MCI_DATAIRQMASKS|MCI_FIFOFULLMASK|MCI_FIFOEMPTYMASK); + writel(masks, host->base + AK88MCIMASK); + PK("DISABLE DATA IRQ\n"); + +#ifdef MCI_USE_L2FIFO_DMA + if (host->data->flags & MMC_DATA_WRITE) { + dma_sync_sg_for_cpu(mmc_dev(host->mmc), host->data->sg, host->data->sg_len, DMA_TO_DEVICE); + dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->data->sg_len, DMA_TO_DEVICE); + } else { + dma_sync_sg_for_cpu(mmc_dev(host->mmc), host->data->sg, host->data->sg_len, DMA_FROM_DEVICE); + dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->data->sg_len, DMA_FROM_DEVICE); + } +#endif + + host->data = NULL; +} + + +static void +ak88_mci_request_end(struct ak88_mci_host *host, struct mmc_request *mrq) +{ + PK1("%s\n", __func__); + + writel(0, host->base + AK88MCICOMMAND); + + BUG_ON(host->data); + + host->mrq = NULL; + host->cmd = NULL; + + if (mrq->data) + mrq->data->bytes_xfered = host->data_xfered; + + /* release shared data pins */ +#ifdef SRDPIN_USE_MUTEX + mutex_unlock(&nand_lock); +#else + up(&nand_lock); +#endif + + /* + * Need to drop the host lock here; mmc_request_done may call + * back into the driver... + */ + spin_unlock(&host->lock); + mmc_request_done(host->mmc, mrq); + spin_lock(&host->lock); +} + +static void ak88_mci_start_data(struct ak88_mci_host *host, struct mmc_data *data) +{ + unsigned int datactrl, timeout, irqmask; + unsigned long long clks; + void __iomem *base; + u32 regval; + + PK1("%s: blksz %04x blks %04x flags %08x\n", + __func__, data->blksz, data->blocks, data->flags); + + host->data = data; + host->size = data->blksz * data->blocks; + host->data_xfered = 0; + + ak88_mci_init_sg(host, data); + + if (data->timeout_clks) { + timeout = data->timeout_clks; + } else { + clks = (unsigned long long)data->timeout_ns * host->bus_clkrate; + do_div(clks, 1000000000UL); + timeout = (unsigned int)clks; + } + PK("timeout: %uns / %uclks\n", data->timeout_ns, data->timeout_clks); + + base = host->base; + writel(timeout, base + AK88MCIDATATIMER); + writel(host->size, base + AK88MCIDATALENGTH); + +#ifdef AKMCI_L2FIFO_PIO + + /* get l2 fifo */ + regval = readl(host->l2base + L2FIFO_ASSIGN1); + regval = (regval & (~(7<<12))) | (MCI_L2FIFO_NUM << 12); + writel(regval, host->l2base + L2FIFO_ASSIGN1); + + regval = readl(host->l2base + L2FIFO_CONF1); + regval |= (1 << (16 + MCI_L2FIFO_NUM)) | (1 << (24 + MCI_L2FIFO_NUM)); + writel(regval, host->l2base + L2FIFO_CONF1); + + PK1("L2ASSIGN: 0x%08x, L2CONF: 0x%08x\n", + readl(host->l2base + L2FIFO_ASSIGN1), + readl(host->l2base + L2FIFO_CONF1)); + + /* set l2 fifo info */ + writel (MCI_DMA_BUFEN | MCI_DMA_SIZE(MCI_L2FIFO_SIZE/4), + base + AK88MCIDMACTRL); + +#elif defined AKMCI_L2FIFO_DMA + + /* get l2 fifo */ + regval = readl(host->l2base + L2FIFO_ASSIGN1); + regval = (regval & (~(3<<12))) | (MCI_L2FIFO_NUM << 12); + writel(regval, host->l2base + L2FIFO_ASSIGN1); + + regval = readl(host->l2base + L2FIFO_CONF1); + regval |= (1 << (0 + MCI_L2FIFO_NUM)) + | (1 << (16 + MCI_L2FIFO_NUM)) + | (1 << (24 + MCI_L2FIFO_NUM)); + if (data->flags & MMC_DATA_WRITE) + regval |= (1 << (8 + MCI_L2FIFO_NUM)); + else + regval &= ~(1 << (8 + MCI_L2FIFO_NUM)); + writel(regval, host->l2base + L2FIFO_CONF1); + + /* set dma addr */ + if (data->flags & MMC_DATA_WRITE) + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, DMA_TO_DEVICE); + else + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, DMA_FROM_DEVICE); + writel(sg_dma_address(data->sg), host->l2base + MCI_L2FIFO_NUM); + + /* set dma size */ + if (host->size > L2DMA_MAX_SIZE) + dma_size = L2DMA_MAX_SIZE; + dma_times = dma_size/64; + writel(dma_times, host->l2base + 0x40 + MCI_L2FIFO_NUM); + + if (host->size > L2DMA_MAX_SIZE) { + /* need to handle dma int */ + regval = readl(host->l2base + L2FIFO_INTEN); + regval |= (1 << (9 + MCI_L2FIFO_NUM)); + writel(regval, host->l2base + L2FIFO_INTEN); + + request_irq(AK88_L2MEM_IRQ(9+MCI_L2FIFO_NUM), ak88_mcil2_irq, + IRQF_DISABLED, DRIVER_NAME "(dma)", host); + } + + /* when to start dma? */ + regval = readl(host->l2base + L2FIFO_DMACONF); + regval |= (1 | (1 << (24 + MCI_L2FIFO_NUM))); + writel(regval, host->l2base + L2FIFO_DMACONF); + + if (dma_size % 64) { + /* fraction DMA */ + (8 * MCI_L2FIFO_NUM) + } + + /* set l2 fifo info */ + writel (MCI_DMA_BUFEN | MCI_DMA_EN | MCI_DMA_SIZE(MCI_L2FIFO_SIZE/4), + base + AK88MCIDMACTRL); +#endif + + datactrl = MCI_DPSM_ENABLE; + + switch (host->bus_width) { + case MMC_BUS_WIDTH_8: + datactrl |= MCI_DPSM_BUSMODE(2); + break; + case MMC_BUS_WIDTH_4: + datactrl |= MCI_DPSM_BUSMODE(1); + break; + case MMC_BUS_WIDTH_1: + default: + datactrl |= MCI_DPSM_BUSMODE(0); + break; + } + + if (data->flags & MMC_DATA_STREAM) { + DBG(host, "%s", "STREAM Data\n"); + datactrl |= MCI_DPSM_STREAM; + } else { + DBG(host, "BLOCK Data: %u x %u\n", data->blksz, data->blocks); + datactrl |= MCI_DPSM_BLOCKSIZE(data->blksz); + } + + if (data->flags & MMC_DATA_READ) { + datactrl |= MCI_DPSM_DIRECTION; + } + + writel(readl(base + AK88MCIMASK) | MCI_DATAIRQMASKS, base + AK88MCIMASK); + writel(datactrl, base + AK88MCIDATACTRL); + + PK("ENABLE DATA IRQ, datactrl: 0x%08x, timeout: 0x%08x, len: %u\n", + datactrl, readl(base+AK88MCIDATATIMER), host->size); + +#ifdef AKMCI_L2FIFO_PIO + if (data->flags & MMC_DATA_WRITE) + mci_xfer(host); +#endif + +#ifdef AKMCI_INNERFIFO_PIO + irqmask = readl(base + AK88MCIMASK); + if (data->flags & MMC_DATA_READ) { + if (host->size > MCI_FIFOSIZE) + irqmask |= MCI_FIFOFULLMASK; + else + ; /* wait for DATAEND int */ + } else { + irqmask |= MCI_FIFOEMPTYMASK; + } + + writel(irqmask, base + AK88MCIMASK); +#endif +} + +static void +ak88_mci_start_command(struct ak88_mci_host *host, struct mmc_command *cmd) +{ + unsigned int c; + void __iomem *base = host->base; + + PK1("%s: op %02x arg %08x flags %08x\n", + __func__, cmd->opcode, cmd->arg, cmd->flags); + + if (readl(base + AK88MCICOMMAND) & MCI_CPSM_ENABLE) { + writel(0, base + AK88MCICOMMAND); + udelay(1); + } + + c = MCI_CPSM_CMD(cmd->opcode) | MCI_CPSM_ENABLE; + if (cmd->flags & MMC_RSP_PRESENT) { + c |= MCI_CPSM_RESPONSE; + if (cmd->flags & MMC_RSP_136) + c |= MCI_CPSM_LONGRSP; + } + + if (cmd->data) + c |= MCI_CPSM_WITHDATA; + + host->cmd = cmd; + + writel(cmd->arg, base + AK88MCIARGUMENT); + writel(readl(base + AK88MCIMASK) | MCI_CMDIRQMASKS, base + AK88MCIMASK); + PK("ENABLE CMD IRQ\n"); + PK("irqmask: 0x%08x\n", readl(base+AK88MCIMASK)); + writel(c, base + AK88MCICOMMAND); +#if 0 + PK("%s: cmd:0x%08x; irq:0x%08x\n", + __func__, c, readl(base+AK88MCIMASK)); + ak88_mci_dump_regs(host->base); +#endif +} + + +#ifdef AKMCI_INNERFIFO_PIO +static void +ak88_mci_pio_irq(struct ak88_mci_host *host, unsigned int status) +{ + u32 *p; + + if (host->sg_ptr == NULL) { + printk("%s ERROR\n", __func__); + return; + } + + p = sg_virt(host->sg_ptr) + host->sg_off; + + if ((status & MCI_FIFOFULL) && (status & MCI_RXACTIVE)) { + *p = readl(host->base + AK88MCIFIFO); + PK("read: 0x%08x\n", *p); + } else if ((status & MCI_FIFOEMPTY) && (status & MCI_TXACTIVE)) { + writel(*p, host->base + AK88MCIFIFO); + PK("write: 0x%08x\n", *p); + } else { + return; + } + + host->data_xfered += 4; + host->size -= 4; + + host->sg_off += 4; + if (host->sg_off >= host->sg_ptr->length) { + ak88_mci_next_sg(host); + } +} +#endif + +static void +ak88_mci_data_irq(struct ak88_mci_host *host, struct mmc_data *data, + unsigned int status) +{ + if (status & MCI_DATABLOCKEND) { + PK("BLOCKEND\n"); +#ifdef AKMCI_L2FIFO_PIO + if (host->size > 0) + mci_xfer(host); +#endif + } + if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBIT_ERR)) { + PK1("DATA ERROR: 0x%08x\n", status); + + if (status & MCI_DATACRCFAIL || status & MCI_STARTBIT_ERR) + data->error = -EILSEQ; + else if (status & MCI_DATATIMEOUT) + data->error = -ETIMEDOUT; + status |= MCI_DATAEND; + /* + * We hit an error condition. Ensure that any data + * partially written to a page is properly coherent. + */ + if (host->sg_len && data->flags & MMC_DATA_READ) + flush_dcache_page(sg_page(host->sg_ptr)); + } + if (status & MCI_DATAEND) { + ak88_mci_stop_data(host); + + //dump_data(data); + + if (!data->stop) { + ak88_mci_request_end(host, data->mrq); + } else { + ak88_mci_start_command(host, data->stop); + } + } +} + +static void +ak88_mci_cmd_irq(struct ak88_mci_host *host, struct mmc_command *cmd, + unsigned int status) +{ + void __iomem *base = host->base; + + PK("+%s\n", __func__); + host->cmd = NULL; + + cmd->resp[0] = readl(base + AK88MCIRESPONSE0); + cmd->resp[1] = readl(base + AK88MCIRESPONSE1); + cmd->resp[2] = readl(base + AK88MCIRESPONSE2); + cmd->resp[3] = readl(base + AK88MCIRESPONSE3); + + if (status & MCI_RESPTIMEOUT) { + cmd->error = -ETIMEDOUT; + } else if (status & MCI_RESPCRCFAIL && cmd->flags & MMC_RSP_CRC) { + cmd->error = -EILSEQ; + } + + writel(readl(base + AK88MCIMASK) & ~MCI_CMDIRQMASKS, base + AK88MCIMASK); + PK("DISABLE CMD IRQ\n"); + + if (!cmd->data || cmd->error) { + if (host->data) + ak88_mci_stop_data(host); + ak88_mci_request_end(host, cmd->mrq); + } else if (!(cmd->data->flags & MMC_DATA_READ)) { + ak88_mci_start_data(host, cmd->data); + } + PK("-%s\n", __func__); +} + +/* + * Handle completion of command and data transfers. + */ +static irqreturn_t ak88_mci_irq(int irq, void *dev_id) +{ + struct ak88_mci_host *host = dev_id; + u32 status; + int ret = 0; + + PK("+%s ", __func__); + + spin_lock(&host->lock); + + do { + struct mmc_command *cmd; + struct mmc_data *data; + + status = readl(host->base + AK88MCISTATUS); + + PK("irq0 %08x\n", status); +/* + PK("irq: a: 0x%08x, b: 0x%08x\n", status, + readl(host->base + AK88MCISTATUS)); +*/ + +#ifdef AKMCI_INNERFIFO_PIO + if (host->data) + ak88_mci_pio_irq(host, status); +#endif + + cmd = host->cmd; + if (status & (MCI_RESPCRCFAIL|MCI_RESPTIMEOUT|MCI_CMDSENT|MCI_RESPEND) + && cmd) + ak88_mci_cmd_irq(host, cmd, status); + + data = host->data; + if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_DATAEND|MCI_DATABLOCKEND|MCI_STARTBIT_ERR) + && data) + ak88_mci_data_irq(host, data, status); + +#ifdef SDIO + if (status & MCI_SDIOINT) { + mmc_signal_sdio_irq(host->mmc); + } +#endif + + ret = 1; + } while (0); + + spin_unlock(&host->lock); + + PK("-%s, irqmask: 0x%08x\n", __func__, readl(host->base + AK88MCIMASK)); + + return IRQ_RETVAL(ret); +} + +static void ak88_mci_detect_change(unsigned long data) +{ + struct ak88_mci_host *host = (struct ak88_mci_host *)data; + + PK("%s\n", __func__); + + mmc_detect_change(host->mmc, 0); + + if (host->irq_cd_type == IRQ_TYPE_LEVEL_LOW) { + host->irq_cd_type = IRQ_TYPE_LEVEL_HIGH; + } else { + host->irq_cd_type = IRQ_TYPE_LEVEL_LOW; + } + set_irq_type(host->irq_cd, host->irq_cd_type); + enable_irq(host->irq_cd); +} + +static irqreturn_t ak88_mci_card_detect_irq(int irq, void *dev) +{ + struct ak88_mci_host *host = dev; + + PK("%s\n", __func__); + + disable_irq_nosync(irq); + mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(400)); + + return IRQ_HANDLED; +} + +static void ak88_mci_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct ak88_mci_host *host = mmc_priv(mmc); + unsigned long flags; + + WARN_ON(host->mrq != NULL); + + PK1("%s: CMD%i\n", __func__, mrq->cmd->opcode); + + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", + mmc_hostname(mmc), mrq->data->blksz); + mrq->cmd->error = -EINVAL; + mmc_request_done(mmc, mrq); + return; + } + + spin_lock_irqsave(&host->lock, flags); + + host->mrq = mrq; + + /* grab shared pins */ + PK("set shared pins\n"); +#if defined CONFIG_BOARD_AK8801EPC + + /* soc pin mux bug */ +#ifdef SRDPIN_USE_MUTEX + mutex_lock_interruptible(&nand_lock); +#else + down_interruptible(&nand_lock); +#endif +/* ak880x_gpio_pullup(AK88_GPIO_39, AK88_GPIO_PUPD_DISABLE); + ak880x_gpio_pullup(AK88_GPIO_40, AK88_GPIO_PUPD_DISABLE);*/ + AK88_GPIO_MDAT2(AK88_SHARE_FUNC); + AK88_MCI_ENABLE(); + +#elif defined CONFIG_BOARD_AK8802EBOOK + +#ifdef SRDPIN_USE_MUTEX + mutex_lock_interruptible(&nand_lock); +#else + down_interruptible(&nand_lock); +#endif + AK88_GPIO_NFC_DATA4_7(AK88_SHARE_FUNC); + AK88_GPIO_NFC_DATA1_3(AK88_SHARE_FUNC); + AK88_GPIO_NFC_DATA0(AK88_SHARE_FUNC); + AK88_SD_ENABLE(); + +#endif + + if (mrq->data && (mrq->data->flags & MMC_DATA_READ)) + ak88_mci_start_data(host, mrq->data); + + ak88_mci_start_command(host, mrq->cmd); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void ak88_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct ak88_mci_host *host = mmc_priv(mmc); + unsigned int regval; + int div; + + printk("%s ", __func__); + + /* ak88 (and SD spec) don't support MMC_BUSMODE_OPENDRAIN */ + host->bus_mode = ios->bus_mode; + host->bus_width = ios->bus_width; + printk("%ubits(%u); ", (unsigned)1 >> (host->bus_width), host->bus_width); + + /* we can't control external power supply unit */ + switch (ios->power_mode) { + case MMC_POWER_UP: + PK("MMC_POWER_UP; "); +#if 0 + regval = readl(host->base + AK88MCICLOCK); + regval |= MCI_ENABLE; + writel(regval, host->base + AK88MCICLOCK); +#endif + break; + case MMC_POWER_ON: + PK("MMC_POWER_ON; "); + break; + case MMC_POWER_OFF: + PK("MMC_POWER_OFF; "); +#if 0 + regval = readl(host->base + AK88MCICLOCK); + regval &= ~MCI_ENABLE; + writel(regval, host->base + AK88MCICLOCK); +#endif + break; + } + + if (ios->clock != host->bus_clkrate) { + regval = readl(host->base + AK88MCICLOCK); + if (ios->clock == 0) { + regval &= ~MCI_CLK_ENABLE; + host->bus_clkrate = 0; + } else { + regval |= MCI_CLK_ENABLE; + regval &= ~0xffff; /* clear clk div */ + div = (host->asic_clkrate + ios->clock - 1) / ios->clock - 2; + printk("clk_div: %d\n", div); + if (div < 256) { + regval |= MMC_CLK_DIVL(div); + } else { + regval |= MMC_CLK_DIVL(255) | MMC_CLK_DIVH(div-255); + } + host->bus_clkrate = host->asic_clkrate / (div + 2); + } + writel(regval, host->base + AK88MCICLOCK); + } + + /* no matter high-speed mode or not, ak88 mci use the same timing */ + + printk("ios->clock(%dkhz), host->bus_clkrate(%lukhz), host->asic_clkrate(%lumhz), MMC_CLK_CTRL(0x%08x)\n", + ios->clock/1000, host->bus_clkrate/1000, host->asic_clkrate/1000000, readl(host->base + AK88MCICLOCK)); +} + +static int ak88_mci_get_ro(struct mmc_host *mmc) +{ + struct ak88_mci_host *host = mmc_priv(mmc); + + if (host->gpio_wp == -ENOSYS) + return -ENOSYS; + + printk("%s: %i\n", __func__, ak880x_gpio_getpin(host->gpio_wp)); + return (ak880x_gpio_getpin(host->gpio_wp) != 0); +} + +#if 0 +static int ak88_mci_get_cd(struct mmc_host *mmc) +{ + struct ak88_mci_host *host = mmc_priv(mmc); + unsigned int status; + + if (host->gpio_cd == -ENOSYS) + status = host->plat->status(mmc_dev(host->mmc)); + else + status = gpio_get_value(host->gpio_cd); + + return !status; +} +#endif + +static const struct mmc_host_ops ak88_mci_ops = { + .request = ak88_mci_request, + .set_ios = ak88_mci_set_ios, + .get_ro = ak88_mci_get_ro, +#if 0 + .get_cd = ak88_mci_get_cd, +#endif +#ifdef SDIO + .enable_sdio_irq = ak88_mci_enable_sdio_irq, +#endif +}; + +#if 0 +static void ak88_mci_check_status(unsigned long data) +{ + struct ak88_mci_host *host = (struct ak88_mci_host *)data; + unsigned int status = ak88_mci_get_cd(host->mmc); + + if (status ^ host->oldstat) + mmc_detect_change(host->mmc, 0); + + host->oldstat = status; + mod_timer(&host->timer, jiffies + HZ); +} +#endif + +static int __devinit ak88_mci_probe(struct platform_device *pdev) +{ + struct ak88_mci_platform_data *plat = pdev->dev.platform_data; + struct ak88_mci_host *host; + struct mmc_host *mmc; + struct resource *res; + int irq; + int ret; + + /* must have platform data */ + if (!plat) { + ret = -EINVAL; + goto out; + } + + PK("%s\n", __func__); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + +#if 0 /* can do in akmci_request() */ + /* set shared data pins */ + PK("set shared pins\n"); +#if defined CONFIG_BOARD_AK8801EPC + ak880x_gpio_pullup(AK88_GPIO_39, AK88_GPIO_PUPD_DISABLE); + ak880x_gpio_pullup(AK88_GPIO_40, AK88_GPIO_PUPD_DISABLE); + AK88_GPIO_MDAT2(AK88_SHARE_FUNC); + AK88_MCI_ENABLE(); +#elif defined CONFIG_BOARD_AK8802EBOOK + AK88_GPIO_NFC_DATA4_7(AK88_SHARE_FUNC); + AK88_GPIO_NFC_DATA1_3(AK88_SHARE_FUNC); + AK88_GPIO_NFC_DATA0(AK88_SHARE_FUNC); + AK88_SD_ENABLE(); +#endif +#endif + + PK("res: %x, %u", res->start, resource_size(res)); + res = request_mem_region(res->start, resource_size(res), DRIVER_NAME); + if (!res) { + ret = -EBUSY; + goto out; + } + PK("res: %x, %u", res->start, resource_size(res)); + + mmc = mmc_alloc_host(sizeof(struct ak88_mci_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto out; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + + host->gpio_wp = -ENOSYS; + host->gpio_cd = -ENOSYS; + + host->clk = clk_get(&pdev->dev, "mci_clk"); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + host->clk = NULL; + goto host_free; + } + + ret = clk_enable(host->clk); + if (ret) + goto clk_free; + + host->plat = plat; + host->asic_clkrate = clk_get_rate(host->clk); + PK("asic_clkrate: %uhz", host->asic_clkrate); + + host->base = ioremap(res->start, resource_size(res)); + if (!host->base) { + ret = -ENOMEM; + goto clk_disable; + } + + mmc->ops = &ak88_mci_ops; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = MMC_CAP_4_BIT_DATA; +#if 0 + mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; +#endif +#ifdef SDIO + mmc->caps |= MMC_CAP_SDIO_IRQ; +#endif +// mmc->caps |= MMC_CAP_NEEDS_POLL; + mmc->f_min = host->asic_clkrate / (255+1 + 255+1); + mmc->f_max = host->asic_clkrate / (0+1 + 0+1); + mmc->f_max = mmc->f_max < fmax ? mmc->f_max : fmax; + + /* + * We can do SGIO + */ + mmc->max_hw_segs = 16; + mmc->max_phys_segs = NR_SG; + + /* + * Since we only have a 16-bit data length register, we must + * ensure that we don't exceed 2^16-1 bytes in a single request. + */ + mmc->max_req_size = 65535; + + /* + * Set the maximum segment size. Since we aren't doing DMA + * (yet) we are only limited by the data length register. + */ + mmc->max_seg_size = mmc->max_req_size; + +#if 0 + /* + * Block size can be up to 2048 bytes, but must be a power of two. + */ + mmc->max_blk_size = 2048; +#else + /* as l2 fifo limit to 512 bytes */ + mmc->max_blk_size = 512; +#endif + + /* + * No limit on the number of blocks transferred. + */ + mmc->max_blk_count = mmc->max_req_size; + + spin_lock_init(&host->lock); + + writel(0, host->base + AK88MCICLOCK); + udelay(1000); + + writel(MCI_ENABLE|MCI_FAIL_TRIGGER, host->base + AK88MCICLOCK); + + writel(0, host->base + AK88MCIMASK); + PK("%s: MCICLOCK: 0x%08x\n", __func__, readl(host->base + AK88MCICLOCK)); + + PK("request irq %i\n", irq); + ret = request_irq(irq, ak88_mci_irq, IRQF_DISABLED, DRIVER_NAME " (cmd)", host); + if (ret) + goto unmap; + host->irq_mci = irq; + +#if 0 + if (gpio_is_valid(plat->gpio_cd)) { + ret = gpio_request(plat->gpio_cd, DRIVER_NAME " (cd)"); + if (ret == 0) + ret = gpio_direction_input(plat->gpio_cd); + if (ret == 0) + host->gpio_cd = plat->gpio_cd; + else if (ret != -ENOSYS) + goto err_gpio_cd; + } + if (gpio_is_valid(plat->gpio_wp)) { + ret = gpio_request(plat->gpio_wp, DRIVER_NAME " (wp)"); + if (ret == 0) + ret = gpio_direction_input(plat->gpio_wp); + if (ret == 0) + host->gpio_wp = plat->gpio_wp; + else if (ret != -ENOSYS) + goto err_gpio_wp; + } + + if (gpio_is_valid(plat->gpio_cd)) { + irq = ak880x_gpio_to_irq(host->gpio_cd); + PK("request card detect irq: %u - %u\n", AK88_DGPIO_12, irq); + ret = request_irq(irq, ak88_mci_card_detect_irq, + IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING, + DRIVER_NAME " cd", host); + if (ret) + goto irq_free; + host->irq_cd = irq; + } +#else + host->gpio_cd = plat->gpio_cd; + host->gpio_wp = plat->gpio_wp; + ak880x_gpio_cfgpin(host->gpio_cd, AK88_GPIO_IN_0); + ak880x_gpio_pullup(host->gpio_wp, AK88_GPIO_PUPD_DISABLE); + ak880x_gpio_cfgpin(host->gpio_wp, AK88_GPIO_IN_0); + + setup_timer(&host->detect_timer, ak88_mci_detect_change, + (unsigned long)host); + irq = ak880x_gpio_to_irq(host->gpio_cd); + PK("request card detect irq: %u - %u\n", AK88_DGPIO_12, irq); + if (ak880x_gpio_getpin(host->gpio_cd)) + set_irq_type (irq, IRQ_TYPE_LEVEL_LOW); + else + set_irq_type (irq, IRQ_TYPE_LEVEL_HIGH); + ret = request_irq(irq, ak88_mci_card_detect_irq, + IRQF_DISABLED, + DRIVER_NAME " cd", host); + if (ret) + goto irq_free; + host->irq_cd = irq; + host->irq_cd_type = IRQ_TYPE_LEVEL_LOW; +#endif + + platform_set_drvdata(pdev, mmc); + + mmc_add_host(mmc); + + PK(KERN_INFO "%s: AK88MCI at 0x%016llx irq %d\n", + mmc_hostname(mmc), (unsigned long long)res->start, + host->irq_mci); + + /* ak88_mci_dump_regs(host->base); */ + PK("srdpin conf1: 0x%08x, srdpin conf2: 0x%08x\n", + readl(AK88_SHAREPIN_CON1), readl(AK88_SHAREPIN_CON2)); + +#ifdef AKMCI_L2FIFO_PIO + res = request_mem_region(L2BASE, SZ_256, DRIVER_NAME); + if (!res) { + ret = -EBUSY; + goto irq_free; + } + host->l2base = ioremap(res->start, resource_size(res)); + if (!host->l2base) { + ret = -ENOMEM; + goto irq_free; + } + + res = request_mem_region(L2ADDR(MCI_L2FIFO_NUM), MCI_L2FIFO_SIZE, DRIVER_NAME); + if (!res) { + ret = -EBUSY; + goto irq_free; + } + host->l2fifo = ioremap(res->start, resource_size(res)); + if (!host->l2fifo) { + ret = -ENOMEM; + goto irq_free; + } +#endif + + return 0; + + irq_free: + PK("ERR irq_free\n"); + free_irq(host->irq_mci, host); + unmap: + PK("ERR unmap\n"); +#if 0 + if (host->gpio_wp != -ENOSYS) + gpio_free(host->gpio_wp); +#endif + err_gpio_wp: + PK("ERR gpio_wp\n"); +#if 0 + if (host->gpio_cd != -ENOSYS) + gpio_free(host->gpio_cd); +#endif + err_gpio_cd: + PK("ERR gpio_cd\n"); + iounmap(host->base); + clk_disable: + PK("ERR clk_disable\n"); + clk_disable(host->clk); + clk_free: + PK("ERR clk_free\n"); + clk_put(host->clk); + host_free: + PK("ERR host_free\n"); + mmc_free_host(mmc); + out: + PK("ERR out\n"); + return ret; +} + +static int __devexit ak88_mci_remove(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + + if (mmc) { + struct ak88_mci_host *host = mmc_priv(mmc); + +#if 0 + del_timer_sync(&host->timer); +#endif + + mmc_remove_host(mmc); + + writel(0, host->base + AK88MCIMASK); + + writel(0, host->base + AK88MCICOMMAND); + writel(0, host->base + AK88MCIDATACTRL); + +#if 0 + if (gpio_is_valid(host->gpio_cd)) + free_irq(host->irq_cd, host); + free_irq(host->irq_mci, host); + + if (host->gpio_wp != -ENOSYS) + gpio_free(host->gpio_wp); + if (host->gpio_cd != -ENOSYS) + gpio_free(host->gpio_cd); +#else + free_irq(host->irq_cd, host); + free_irq(host->irq_mci, host); +#endif + + iounmap(host->base); + clk_disable(host->clk); + clk_put(host->clk); + + mmc_free_host(mmc); + } + + return 0; +} + +#ifdef CONFIG_PM +static int ak88_mci_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; + + if (mmc) { + struct ak88_mci_host *host = mmc_priv(mmc); + + ret = mmc_suspend_host(mmc, state); + if (ret == 0) + writel(0, host->base + AK88MCIMASK0); + } + + return ret; +} + +static int ak88_mci_resume(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; + + if (mmc) { + struct ak88_mci_host *host = mmc_priv(mmc); + + writel(MCI_IRQENABLE, host->base + AK88MCIMASK0); + + ret = mmc_resume_host(mmc); + } + + return ret; +} +#else +#define ak88_mci_suspend NULL +#define ak88_mci_resume NULL +#endif + +static struct platform_driver ak88_mci_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = ak88_mci_probe, + .remove = __devexit_p(ak88_mci_remove), + .suspend = ak88_mci_suspend, + .resume = ak88_mci_resume, +}; + +static int __init ak88_mci_init(void) +{ + PK("%s\n", __func__); + return platform_driver_register(&ak88_mci_driver); +} + +static void __exit ak88_mci_exit(void) +{ + platform_driver_unregister(&ak88_mci_driver); +} + +module_init(ak88_mci_init); +module_exit(ak88_mci_exit); + +MODULE_DESCRIPTION("Anyka AK88 MMC/SD/SDIO Interface driver"); +MODULE_LICENSE("GPL"); |