mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-09 11:04:04 +02:00
1189 lines
34 KiB
Diff
1189 lines
34 KiB
Diff
|
From 448ffab41f86596340e9f34dc8141bf2ca857e4e Mon Sep 17 00:00:00 2001
|
||
|
From: mokopatches <mokopatches@openmoko.org>
|
||
|
Date: Wed, 16 Jul 2008 14:44:51 +0100
|
||
|
Subject: [PATCH] glamo-mmc.patch
|
||
|
|
||
|
---
|
||
|
drivers/mfd/glamo/Kconfig | 9 +
|
||
|
drivers/mfd/glamo/Makefile | 1 +
|
||
|
drivers/mfd/glamo/glamo-core.c | 17 +-
|
||
|
drivers/mfd/glamo/glamo-mci.c | 837 ++++++++++++++++++++++++++++++++++++++++
|
||
|
drivers/mfd/glamo/glamo-mci.h | 76 ++++
|
||
|
drivers/mfd/glamo/glamo-regs.h | 169 ++++++++-
|
||
|
6 files changed, 1099 insertions(+), 10 deletions(-)
|
||
|
create mode 100644 drivers/mfd/glamo/glamo-mci.c
|
||
|
create mode 100644 drivers/mfd/glamo/glamo-mci.h
|
||
|
|
||
|
diff --git a/drivers/mfd/glamo/Kconfig b/drivers/mfd/glamo/Kconfig
|
||
|
index b99f2b2..ec2ae3d 100644
|
||
|
--- a/drivers/mfd/glamo/Kconfig
|
||
|
+++ b/drivers/mfd/glamo/Kconfig
|
||
|
@@ -33,3 +33,12 @@ config MFD_GLAMO_SPI_FB
|
||
|
control channel. This SPI interface is frequently used to
|
||
|
interconnect the LCM control interface.
|
||
|
|
||
|
+config MFD_GLAMO_MCI
|
||
|
+ tristate "Glamo S3C SD/MMC Card Interface support"
|
||
|
+ depends on MFD_GLAMO && MMC
|
||
|
+ help
|
||
|
+ This selects a driver for the MCI interface found in
|
||
|
+ the S-Media GLAMO chip, as used in OpenMoko
|
||
|
+ neo1973 GTA-02.
|
||
|
+
|
||
|
+ If unsure, say N.
|
||
|
\ No newline at end of file
|
||
|
diff --git a/drivers/mfd/glamo/Makefile b/drivers/mfd/glamo/Makefile
|
||
|
index fb53982..dc64d50 100644
|
||
|
--- a/drivers/mfd/glamo/Makefile
|
||
|
+++ b/drivers/mfd/glamo/Makefile
|
||
|
@@ -8,4 +8,5 @@ obj-$(CONFIG_MFD_GLAMO_SPI_GPIO) += glamo-spi-gpio.o
|
||
|
|
||
|
obj-$(CONFIG_MFD_GLAMO_FB) += glamo-fb.o
|
||
|
obj-$(CONFIG_MFD_GLAMO_SPI_FB) += glamo-lcm-spi.o
|
||
|
+obj-$(CONFIG_MFD_GLAMO_MCI) += glamo-mci.o
|
||
|
|
||
|
diff --git a/drivers/mfd/glamo/glamo-core.c b/drivers/mfd/glamo/glamo-core.c
|
||
|
index acf545f..8497de2 100644
|
||
|
--- a/drivers/mfd/glamo/glamo-core.c
|
||
|
+++ b/drivers/mfd/glamo/glamo-core.c
|
||
|
@@ -440,7 +440,22 @@ int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine)
|
||
|
{
|
||
|
spin_lock(&glamo->lock);
|
||
|
switch (engine) {
|
||
|
- /* FIXME: Implementation */
|
||
|
+ case GLAMO_ENGINE_MMC:
|
||
|
+ __reg_set_bit_mask(glamo, GLAMO_REG_CLOCK_MMC, 0,
|
||
|
+ GLAMO_CLOCK_MMC_EN_M9CLK |
|
||
|
+ GLAMO_CLOCK_MMC_EN_TCLK |
|
||
|
+ GLAMO_CLOCK_MMC_DG_M9CLK |
|
||
|
+ GLAMO_CLOCK_MMC_DG_TCLK);
|
||
|
+ __reg_set_bit_mask(glamo, GLAMO_REG_HOSTBUS(2), 0,
|
||
|
+ GLAMO_HOSTBUS2_MMIO_EN_MMC);
|
||
|
+ /* disable the TCLK divider clk input */
|
||
|
+ __reg_set_bit_mask(glamo, GLAMO_REG_CLOCK_GEN5_1, 0,
|
||
|
+ GLAMO_CLOCK_GEN51_EN_DIV_TCLK);
|
||
|
+ /* good idea to hold the thing in reset when we power it off? */
|
||
|
+/* writew(readw(glamo->base + GLAMO_REG_CLOCK_MMC) |
|
||
|
+ GLAMO_CLOCK_MMC_RESET, glamo->base + GLAMO_REG_CLOCK_MMC);
|
||
|
+*/
|
||
|
+ break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
diff --git a/drivers/mfd/glamo/glamo-mci.c b/drivers/mfd/glamo/glamo-mci.c
|
||
|
new file mode 100644
|
||
|
index 0000000..f559e5e
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/mfd/glamo/glamo-mci.c
|
||
|
@@ -0,0 +1,837 @@
|
||
|
+/*
|
||
|
+ * linux/drivers/mmc/host/glamo-mmc.c - Glamo MMC driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2007 OpenMoko, Inc, Andy Green <andy@openmoko.com>
|
||
|
+ * Based on S3C MMC driver that was:
|
||
|
+ * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de>
|
||
|
+ *
|
||
|
+ * 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/dma-mapping.h>
|
||
|
+#include <linux/clk.h>
|
||
|
+#include <linux/mmc/mmc.h>
|
||
|
+#include <linux/mmc/host.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/irq.h>
|
||
|
+#include <linux/pcf50633.h>
|
||
|
+#include <linux/delay.h>
|
||
|
+#include <linux/interrupt.h>
|
||
|
+
|
||
|
+#include <asm/dma.h>
|
||
|
+#include <asm/dma-mapping.h>
|
||
|
+#include <asm/io.h>
|
||
|
+
|
||
|
+#include "glamo-mci.h"
|
||
|
+#include "glamo-core.h"
|
||
|
+#include "glamo-regs.h"
|
||
|
+
|
||
|
+/* from glamo-core.c */
|
||
|
+extern struct glamo_mci_pdata glamo_mci_def_pdata;
|
||
|
+
|
||
|
+
|
||
|
+#define DRIVER_NAME "glamo-mci"
|
||
|
+#define RESSIZE(ressource) (((ressource)->end - (ressource)->start) + 1)
|
||
|
+
|
||
|
+static void glamo_mci_send_request(struct mmc_host *mmc);
|
||
|
+
|
||
|
+unsigned char CRC7(u8 * pu8, int cnt)
|
||
|
+{
|
||
|
+ u8 crc = 0;
|
||
|
+
|
||
|
+ while (cnt--) {
|
||
|
+ int n;
|
||
|
+ u8 d = *pu8++;
|
||
|
+ for (n = 0; n < 8; n++) {
|
||
|
+ crc <<= 1;
|
||
|
+ if ((d & 0x80) ^ (crc & 0x80))
|
||
|
+ crc ^= 0x09;
|
||
|
+ d <<= 1;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return (crc << 1) | 1;
|
||
|
+}
|
||
|
+
|
||
|
+/* these _dly versions account for the dead time rules for reg access */
|
||
|
+static u16 readw_dly(u16 __iomem * pu16)
|
||
|
+{
|
||
|
+ glamo_reg_access_delay();
|
||
|
+ return readw(pu16);
|
||
|
+}
|
||
|
+
|
||
|
+static void writew_dly(u16 val, u16 __iomem * pu16)
|
||
|
+{
|
||
|
+ glamo_reg_access_delay();
|
||
|
+ writew(val, pu16);
|
||
|
+}
|
||
|
+
|
||
|
+static int get_data_buffer(struct glamo_mci_host *host,
|
||
|
+ volatile u32 *words, volatile u16 **pointer)
|
||
|
+{
|
||
|
+ struct scatterlist *sg;
|
||
|
+
|
||
|
+ *words = 0;
|
||
|
+ *pointer = NULL;
|
||
|
+
|
||
|
+ if (host->pio_active == XFER_NONE)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ if ((!host->mrq) || (!host->mrq->data))
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ if (host->pio_sgptr >= host->mrq->data->sg_len) {
|
||
|
+ dev_dbg(&host->pdev->dev, "no more buffers (%i/%i)\n",
|
||
|
+ host->pio_sgptr, host->mrq->data->sg_len);
|
||
|
+ return -EBUSY;
|
||
|
+ }
|
||
|
+ sg = &host->mrq->data->sg[host->pio_sgptr];
|
||
|
+
|
||
|
+ *words = sg->length >> 1; /* we are working with a 16-bit data bus */
|
||
|
+ *pointer = page_address(sg_page(sg)) + sg->offset;
|
||
|
+
|
||
|
+ BUG_ON(((long)(*pointer)) & 1);
|
||
|
+
|
||
|
+ host->pio_sgptr++;
|
||
|
+
|
||
|
+ /* dev_info(&host->pdev->dev, "new buffer (%i/%i)\n",
|
||
|
+ host->pio_sgptr, host->mrq->data->sg_len); */
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void do_pio_read(struct glamo_mci_host *host)
|
||
|
+{
|
||
|
+ int res;
|
||
|
+ u16 __iomem *from_ptr = host->base_data + (RESSIZE(host->mem_data) /
|
||
|
+ sizeof(u16) / 2);
|
||
|
+#ifdef DEBUG
|
||
|
+ u16 * block;
|
||
|
+#endif
|
||
|
+
|
||
|
+ while (1) {
|
||
|
+ res = get_data_buffer(host, &host->pio_words, &host->pio_ptr);
|
||
|
+ if (res) {
|
||
|
+ host->pio_active = XFER_NONE;
|
||
|
+ host->complete_what = COMPLETION_FINALIZE;
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev, "pio_read(): "
|
||
|
+ "complete (no more data).\n");
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev, "pio_read(): host->pio_words: %d\n",
|
||
|
+ host->pio_words);
|
||
|
+
|
||
|
+ host->pio_count += host->pio_words << 1;
|
||
|
+
|
||
|
+#ifdef DEBUG
|
||
|
+ block = (u16 *)host->pio_ptr;
|
||
|
+ res = host->pio_words << 1;
|
||
|
+#endif
|
||
|
+ while (host->pio_words--)
|
||
|
+ *host->pio_ptr++ = *from_ptr++;
|
||
|
+#ifdef DEBUG
|
||
|
+ print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1,
|
||
|
+ (void *)block, res, 1);
|
||
|
+#endif
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static int do_pio_write(struct glamo_mci_host *host)
|
||
|
+{
|
||
|
+ int res = 0;
|
||
|
+ volatile u16 __iomem *to_ptr = host->base_data;
|
||
|
+ int err = 0;
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev, "pio_write():\n");
|
||
|
+ while (!res) {
|
||
|
+ res = get_data_buffer(host, &host->pio_words, &host->pio_ptr);
|
||
|
+ if (res)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev, "pio_write():new source: [%i]@[%p]\n",
|
||
|
+ host->pio_words, host->pio_ptr);
|
||
|
+
|
||
|
+ host->pio_count += host->pio_words << 1;
|
||
|
+ while (host->pio_words--)
|
||
|
+ writew(*host->pio_ptr++, to_ptr++);
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev, "pio_write(): complete\n");
|
||
|
+ host->pio_active = XFER_NONE;
|
||
|
+ return err;
|
||
|
+}
|
||
|
+
|
||
|
+static void glamo_mci_irq(unsigned int irq, struct irq_desc *desc)
|
||
|
+{
|
||
|
+ struct glamo_mci_host *host = (struct glamo_mci_host *)
|
||
|
+ desc->handler_data;
|
||
|
+ u16 status;
|
||
|
+ struct mmc_command *cmd;
|
||
|
+ unsigned long iflags;
|
||
|
+
|
||
|
+ if (!host)
|
||
|
+ return;
|
||
|
+ if (!host->mrq)
|
||
|
+ return;
|
||
|
+ cmd = host->mrq->cmd;
|
||
|
+ if (!cmd)
|
||
|
+ return;
|
||
|
+
|
||
|
+ spin_lock_irqsave(&host->complete_lock, iflags);
|
||
|
+
|
||
|
+ status = readw_dly(host->base + GLAMO_REG_MMC_RB_STAT1);
|
||
|
+
|
||
|
+ /* ack this interrupt source */
|
||
|
+ writew(GLAMO_IRQ_MMC,
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_IRQ_CLEAR);
|
||
|
+
|
||
|
+ if (status & (GLAMO_STAT1_MMC_RTOUT |
|
||
|
+ GLAMO_STAT1_MMC_DTOUT))
|
||
|
+ cmd->error = -ETIMEDOUT;
|
||
|
+ if (status & (GLAMO_STAT1_MMC_BWERR |
|
||
|
+ GLAMO_STAT1_MMC_BRERR))
|
||
|
+ cmd->error = -EILSEQ;
|
||
|
+ if (cmd->error) {
|
||
|
+ dev_err(&host->pdev->dev, "Error after cmd: 0x%x\n", status);
|
||
|
+ goto done;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (host->pio_active == XFER_READ)
|
||
|
+ do_pio_read(host);
|
||
|
+
|
||
|
+ host->mrq->data->bytes_xfered = host->pio_count;
|
||
|
+ dev_dbg(&host->pdev->dev, "status = 0x%04x count=%d\n",
|
||
|
+ status, host->pio_count);
|
||
|
+
|
||
|
+ /* issue STOP if we have been given one to use */
|
||
|
+ if (host->mrq->stop) {
|
||
|
+ host->cmd_is_stop = 1;
|
||
|
+ glamo_mci_send_request(host->mmc);
|
||
|
+ host->cmd_is_stop = 0;
|
||
|
+ }
|
||
|
+done:
|
||
|
+ host->complete_what = COMPLETION_NONE;
|
||
|
+ host->mrq = NULL;
|
||
|
+ mmc_request_done(host->mmc, cmd->mrq);
|
||
|
+ spin_unlock_irqrestore(&host->complete_lock, iflags);
|
||
|
+}
|
||
|
+
|
||
|
+static int glamo_mci_send_command(struct glamo_mci_host *host,
|
||
|
+ struct mmc_command *cmd)
|
||
|
+{
|
||
|
+ u8 u8a[6];
|
||
|
+ u16 fire = 0;
|
||
|
+
|
||
|
+ /* if we can't do it, reject as busy */
|
||
|
+ if (!readw_dly(host->base + GLAMO_REG_MMC_RB_STAT1) &
|
||
|
+ GLAMO_STAT1_MMC_IDLE) {
|
||
|
+ host->mrq = NULL;
|
||
|
+ cmd->error = -EBUSY;
|
||
|
+ mmc_request_done(host->mmc, host->mrq);
|
||
|
+ return -EBUSY;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* create an array in wire order for CRC computation */
|
||
|
+ u8a[0] = 0x40 | (cmd->opcode & 0x3f);
|
||
|
+ u8a[1] = (u8)(cmd->arg >> 24);
|
||
|
+ u8a[2] = (u8)(cmd->arg >> 16);
|
||
|
+ u8a[3] = (u8)(cmd->arg >> 8);
|
||
|
+ u8a[4] = (u8)cmd->arg;
|
||
|
+ u8a[5] = CRC7(&u8a[0], 5); /* CRC7 on first 5 bytes of packet */
|
||
|
+
|
||
|
+ /* issue the wire-order array including CRC in register order */
|
||
|
+ writew_dly((u8a[4] << 8) | u8a[5], host->base + GLAMO_REG_MMC_CMD_REG1);
|
||
|
+ writew_dly((u8a[2] << 8) | u8a[3], host->base + GLAMO_REG_MMC_CMD_REG2);
|
||
|
+ writew_dly((u8a[0] << 8) | u8a[1], host->base + GLAMO_REG_MMC_CMD_REG3);
|
||
|
+
|
||
|
+ /* command index toggle */
|
||
|
+ fire |= (host->ccnt & 1) << 12;
|
||
|
+
|
||
|
+ /* set type of command */
|
||
|
+ switch (mmc_cmd_type(cmd)) {
|
||
|
+ case MMC_CMD_BC:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CMDT_BNR;
|
||
|
+ break;
|
||
|
+ case MMC_CMD_BCR:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CMDT_BR;
|
||
|
+ break;
|
||
|
+ case MMC_CMD_AC:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CMDT_AND;
|
||
|
+ break;
|
||
|
+ case MMC_CMD_ADTC:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CMDT_AD;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ /*
|
||
|
+ * if it expects a response, set the type expected
|
||
|
+ *
|
||
|
+ * R1, Length : 48bit, Normal response
|
||
|
+ * R1b, Length : 48bit, same R1, but added card busy status
|
||
|
+ * R2, Length : 136bit (really 128 bits with CRC snipped)
|
||
|
+ * R3, Length : 48bit (OCR register value)
|
||
|
+ * R4, Length : 48bit, SDIO_OP_CONDITION, Reverse SDIO Card
|
||
|
+ * R5, Length : 48bit, IO_RW_DIRECTION, Reverse SDIO Card
|
||
|
+ * R6, Length : 48bit (RCA register)
|
||
|
+ * R7, Length : 48bit (interface condition, VHS(voltage supplied),
|
||
|
+ * check pattern, CRC7)
|
||
|
+ */
|
||
|
+ switch (mmc_resp_type(cmd)) {
|
||
|
+ case MMC_RSP_R6: /* same index as R7 and R1 */
|
||
|
+ fire |= GLAMO_FIRE_MMC_RSPT_R1;
|
||
|
+ break;
|
||
|
+ case MMC_RSP_R1B:
|
||
|
+ fire |= GLAMO_FIRE_MMC_RSPT_R1b;
|
||
|
+ break;
|
||
|
+ case MMC_RSP_R2:
|
||
|
+ fire |= GLAMO_FIRE_MMC_RSPT_R2;
|
||
|
+ break;
|
||
|
+ case MMC_RSP_R3:
|
||
|
+ fire |= GLAMO_FIRE_MMC_RSPT_R3;
|
||
|
+ break;
|
||
|
+ /* R4 and R5 supported by chip not defined in linux/mmc/core.h (sdio) */
|
||
|
+ }
|
||
|
+ /*
|
||
|
+ * From the command index, set up the command class in the host ctrllr
|
||
|
+ *
|
||
|
+ * missing guys present on chip but couldn't figure out how to use yet:
|
||
|
+ * 0x0 "stream read"
|
||
|
+ * 0x9 "cancel running command"
|
||
|
+ */
|
||
|
+ switch (cmd->opcode) {
|
||
|
+ case MMC_READ_SINGLE_BLOCK:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_SBR; /* single block read */
|
||
|
+ break;
|
||
|
+ case MMC_SWITCH: /* 64 byte payload */
|
||
|
+ case 0x33: /* observed issued by MCI */
|
||
|
+ case MMC_READ_MULTIPLE_BLOCK:
|
||
|
+ /* we will get an interrupt off this */
|
||
|
+ if (!cmd->mrq->stop)
|
||
|
+ /* multiblock no stop */
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_MBRNS;
|
||
|
+ else
|
||
|
+ /* multiblock with stop */
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_MBRS;
|
||
|
+ break;
|
||
|
+ case MMC_WRITE_BLOCK:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_SBW; /* single block write */
|
||
|
+ break;
|
||
|
+ case MMC_WRITE_MULTIPLE_BLOCK:
|
||
|
+ if (cmd->mrq->stop)
|
||
|
+ /* multiblock with stop */
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_MBWS;
|
||
|
+ else
|
||
|
+ /* multiblock NO stop-- 'RESERVED'? */
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_MBWNS;
|
||
|
+ break;
|
||
|
+ case MMC_STOP_TRANSMISSION:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_STOP; /* STOP */
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ fire |= GLAMO_FIRE_MMC_CC_BASIC; /* "basic command" */
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ /* enforce timeout */
|
||
|
+ if (cmd->data) {
|
||
|
+ if (cmd->data->timeout_clks)
|
||
|
+ writew_dly(cmd->data->timeout_clks >> 4, /* / 16 clks */
|
||
|
+ host->base + GLAMO_REG_MMC_TIMEOUT);
|
||
|
+ else
|
||
|
+ writew_dly(0xfff, host->base + GLAMO_REG_MMC_TIMEOUT);
|
||
|
+ } else
|
||
|
+ writew(0xfff, host->base + GLAMO_REG_MMC_TIMEOUT);
|
||
|
+
|
||
|
+ /* Generate interrupt on txfer; drive strength max */
|
||
|
+ writew_dly((readw_dly(host->base + GLAMO_REG_MMC_BASIC) & 0xfe) |
|
||
|
+ 0x0800 | GLAMO_BASIC_MMC_NO_CLK_RD_WAIT |
|
||
|
+ GLAMO_BASIC_MMC_EN_COMPL_INT |
|
||
|
+ GLAMO_BASIC_MMC_EN_DR_STR0 |
|
||
|
+ GLAMO_BASIC_MMC_EN_DR_STR1,
|
||
|
+ host->base + GLAMO_REG_MMC_BASIC);
|
||
|
+
|
||
|
+ /* send the command out on the wire */
|
||
|
+ /* dev_info(&host->pdev->dev, "Using FIRE %04X\n", fire); */
|
||
|
+ writew_dly(fire, host->base + GLAMO_REG_MMC_CMD_FIRE);
|
||
|
+ cmd->error = 0;
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int glamo_mci_prepare_pio(struct glamo_mci_host *host,
|
||
|
+ struct mmc_data *data)
|
||
|
+{
|
||
|
+ /*
|
||
|
+ * the S-Media-internal RAM offset for our MMC buffer
|
||
|
+ * Read is halfway up the buffer and write is at the start
|
||
|
+ */
|
||
|
+ if (data->flags & MMC_DATA_READ) {
|
||
|
+ writew_dly((u16)(GLAMO_FB_SIZE + (RESSIZE(host->mem_data) / 2)),
|
||
|
+ host->base + GLAMO_REG_MMC_WDATADS1);
|
||
|
+ writew_dly((u16)((GLAMO_FB_SIZE +
|
||
|
+ (RESSIZE(host->mem_data) / 2)) >> 16),
|
||
|
+ host->base + GLAMO_REG_MMC_WDATADS2);
|
||
|
+ } else {
|
||
|
+ writew_dly((u16)GLAMO_FB_SIZE, host->base +
|
||
|
+ GLAMO_REG_MMC_RDATADS1);
|
||
|
+ writew_dly((u16)(GLAMO_FB_SIZE >> 16), host->base +
|
||
|
+ GLAMO_REG_MMC_RDATADS2);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* set up the block info */
|
||
|
+ writew_dly(data->blksz, host->base + GLAMO_REG_MMC_DATBLKLEN);
|
||
|
+ writew_dly(data->blocks, host->base + GLAMO_REG_MMC_DATBLKCNT);
|
||
|
+ dev_dbg(&host->pdev->dev, "(blksz=%d, count=%d)\n",
|
||
|
+ data->blksz, data->blocks);
|
||
|
+ host->pio_sgptr = 0;
|
||
|
+ host->pio_words = 0;
|
||
|
+ host->pio_count = 0;
|
||
|
+ host->pio_active = 0;
|
||
|
+ /* if write, prep the write into the shared RAM before the command */
|
||
|
+ if (data->flags & MMC_DATA_WRITE) {
|
||
|
+ host->pio_active = XFER_WRITE;
|
||
|
+ return do_pio_write(host);
|
||
|
+ }
|
||
|
+ host->pio_active = XFER_READ;
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void glamo_mci_send_request(struct mmc_host *mmc)
|
||
|
+{
|
||
|
+ struct glamo_mci_host *host = mmc_priv(mmc);
|
||
|
+ struct mmc_request *mrq = host->mrq;
|
||
|
+ struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
|
||
|
+ u16 * pu16 = (u16 *)&cmd->resp[0];
|
||
|
+ u16 * reg_resp = (u16 *)(host->base + GLAMO_REG_MMC_CMD_RSP1);
|
||
|
+ u16 status;
|
||
|
+ int n;
|
||
|
+
|
||
|
+ host->ccnt++;
|
||
|
+ /*
|
||
|
+ * somehow 2.6.24 MCI manages to issue MMC_WRITE_BLOCK *without* the
|
||
|
+ * MMC_DATA_WRITE flag, WTF? Work around the madness.
|
||
|
+ */
|
||
|
+ if (cmd->opcode == MMC_WRITE_BLOCK)
|
||
|
+ if (mrq->data)
|
||
|
+ mrq->data->flags |= MMC_DATA_WRITE;
|
||
|
+
|
||
|
+ /* this guy has data to read/write? */
|
||
|
+ if ((!host->cmd_is_stop) && cmd->data) {
|
||
|
+ int res;
|
||
|
+ host->dcnt++;
|
||
|
+ res = glamo_mci_prepare_pio(host, cmd->data);
|
||
|
+ if (res) {
|
||
|
+ cmd->error = -EIO;
|
||
|
+ cmd->data->error = -EIO;
|
||
|
+ mmc_request_done(mmc, mrq);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(&host->pdev->dev,"cmd 0x%x, "
|
||
|
+ "arg 0x%x data=%p mrq->stop=%p flags 0x%x\n",
|
||
|
+ cmd->opcode, cmd->arg, cmd->data, cmd->mrq->stop,
|
||
|
+ cmd->flags);
|
||
|
+
|
||
|
+ if (glamo_mci_send_command(host, cmd))
|
||
|
+ return;
|
||
|
+ /*
|
||
|
+ * we must spin until response is ready or timed out
|
||
|
+ * -- we don't get interrupts unless there is a bulk rx
|
||
|
+ */
|
||
|
+ do
|
||
|
+ status = readw_dly(host->base + GLAMO_REG_MMC_RB_STAT1);
|
||
|
+ while ((((status >> 15) & 1) != (host->ccnt & 1)) ||
|
||
|
+ (!(status & (GLAMO_STAT1_MMC_RB_RRDY |
|
||
|
+ GLAMO_STAT1_MMC_RTOUT |
|
||
|
+ GLAMO_STAT1_MMC_DTOUT |
|
||
|
+ GLAMO_STAT1_MMC_BWERR |
|
||
|
+ GLAMO_STAT1_MMC_BRERR))));
|
||
|
+
|
||
|
+ if (status & (GLAMO_STAT1_MMC_RTOUT |
|
||
|
+ GLAMO_STAT1_MMC_DTOUT))
|
||
|
+ cmd->error = -ETIMEDOUT;
|
||
|
+ if (status & (GLAMO_STAT1_MMC_BWERR |
|
||
|
+ GLAMO_STAT1_MMC_BRERR))
|
||
|
+ cmd->error = -EILSEQ;
|
||
|
+
|
||
|
+ if (host->cmd_is_stop)
|
||
|
+ return;
|
||
|
+
|
||
|
+ if (cmd->error) {
|
||
|
+ dev_err(&host->pdev->dev, "Error after cmd: 0x%x\n", status);
|
||
|
+ goto done;
|
||
|
+ }
|
||
|
+ /*
|
||
|
+ * mangle the response registers in two different exciting
|
||
|
+ * undocumented ways discovered by trial and error
|
||
|
+ */
|
||
|
+ if (mmc_resp_type(cmd) == MMC_RSP_R2)
|
||
|
+ /* grab the response */
|
||
|
+ for (n = 0; n < 8; n++) /* super mangle power 1 */
|
||
|
+ pu16[n ^ 6] = readw_dly(®_resp[n]);
|
||
|
+ else
|
||
|
+ for (n = 0; n < 3; n++) /* super mangle power 2 */
|
||
|
+ pu16[n] = (readw_dly(®_resp[n]) >> 8) |
|
||
|
+ (readw_dly(®_resp[n + 1]) << 8);
|
||
|
+ /*
|
||
|
+ * if we don't have bulk data to take care of, we're done
|
||
|
+ */
|
||
|
+ if (!cmd->data)
|
||
|
+ goto done;
|
||
|
+ if (!(cmd->data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)))
|
||
|
+ goto done;
|
||
|
+ /*
|
||
|
+ * Otherwise can can use the interrupt as async completion --
|
||
|
+ * if there is read data coming, or we wait for write data to complete,
|
||
|
+ * exit without mmc_request_done() as the payload interrupt
|
||
|
+ * will service it
|
||
|
+ */
|
||
|
+ dev_dbg(&host->pdev->dev, "Waiting for payload data\n");
|
||
|
+ /*
|
||
|
+ * if the glamo INT# line isn't wired (*cough* it can happen)
|
||
|
+ * I'm afraid we have to spin on the IRQ status bit and "be
|
||
|
+ * our own INT# line"
|
||
|
+ */
|
||
|
+ if (!glamo_mci_def_pdata.pglamo->irq_works) {
|
||
|
+ /* we have faith we will get an "interrupt"... */
|
||
|
+ while (!(readw_dly(glamo_mci_def_pdata.pglamo->base +
|
||
|
+ GLAMO_REG_IRQ_STATUS) & GLAMO_IRQ_MMC))
|
||
|
+ ;
|
||
|
+ /* yay we are an interrupt controller! -- call the ISR */
|
||
|
+ glamo_mci_irq(IRQ_GLAMO(GLAMO_IRQIDX_MMC),
|
||
|
+ irq_desc + IRQ_GLAMO(GLAMO_IRQIDX_MMC));
|
||
|
+ }
|
||
|
+ return;
|
||
|
+
|
||
|
+done:
|
||
|
+ host->complete_what = COMPLETION_NONE;
|
||
|
+ host->mrq = NULL;
|
||
|
+ mmc_request_done(host->mmc, cmd->mrq);
|
||
|
+}
|
||
|
+
|
||
|
+static void glamo_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||
|
+{
|
||
|
+ struct glamo_mci_host *host = mmc_priv(mmc);
|
||
|
+
|
||
|
+ host->cmd_is_stop = 0;
|
||
|
+ host->mrq = mrq;
|
||
|
+ glamo_mci_send_request(mmc);
|
||
|
+}
|
||
|
+
|
||
|
+static void glamo_mci_reset(struct glamo_mci_host *host)
|
||
|
+{
|
||
|
+ /* reset MMC controller */
|
||
|
+ writew_dly(GLAMO_CLOCK_MMC_RESET | GLAMO_CLOCK_MMC_DG_TCLK |
|
||
|
+ GLAMO_CLOCK_MMC_EN_TCLK | GLAMO_CLOCK_MMC_DG_M9CLK |
|
||
|
+ GLAMO_CLOCK_MMC_EN_M9CLK,
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_CLOCK_MMC);
|
||
|
+ msleep(1);
|
||
|
+ /* and disable reset */
|
||
|
+ writew_dly(GLAMO_CLOCK_MMC_DG_TCLK |
|
||
|
+ GLAMO_CLOCK_MMC_EN_TCLK | GLAMO_CLOCK_MMC_DG_M9CLK |
|
||
|
+ GLAMO_CLOCK_MMC_EN_M9CLK,
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_CLOCK_MMC);
|
||
|
+}
|
||
|
+
|
||
|
+static void glamo_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||
|
+{
|
||
|
+ struct glamo_mci_host *host = mmc_priv(mmc);
|
||
|
+ int mci_psc = 0;
|
||
|
+ int n = 0;
|
||
|
+
|
||
|
+ /* Set power */
|
||
|
+ switch(ios->power_mode) {
|
||
|
+ case MMC_POWER_ON:
|
||
|
+ case MMC_POWER_UP:
|
||
|
+ if (host->power_mode_current != MMC_POWER_OFF)
|
||
|
+ break;
|
||
|
+ if (host->vdd_current != ios->vdd) {
|
||
|
+ host->pdata->glamo_set_mci_power(ios->power_mode,
|
||
|
+ ios->vdd);
|
||
|
+ host->vdd_current = ios->vdd;
|
||
|
+ }
|
||
|
+ glamo_engine_enable(glamo_mci_def_pdata.pglamo,
|
||
|
+ GLAMO_ENGINE_MMC);
|
||
|
+ glamo_mci_reset(host);
|
||
|
+ break;
|
||
|
+
|
||
|
+ case MMC_POWER_OFF:
|
||
|
+ default:
|
||
|
+ if (host->power_mode_current == MMC_POWER_OFF)
|
||
|
+ break;
|
||
|
+ glamo_engine_disable(glamo_mci_def_pdata.pglamo,
|
||
|
+ GLAMO_ENGINE_MMC);
|
||
|
+ host->pdata->glamo_set_mci_power(MMC_POWER_OFF, 0);
|
||
|
+ host->vdd_current = -1;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ host->power_mode_current = ios->power_mode;
|
||
|
+
|
||
|
+ /* Set clock */
|
||
|
+/* if (ios->clock) { */
|
||
|
+ for (mci_psc = 0; mci_psc < 256; mci_psc++) {
|
||
|
+ host->real_rate = host->clk_rate / (mci_psc + 1);
|
||
|
+ if (host->real_rate <= ios->clock)
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ if (mci_psc > 255)
|
||
|
+ mci_psc = 255;
|
||
|
+ host->clk_div = mci_psc;
|
||
|
+ /* set the nearest prescaler factor
|
||
|
+ *
|
||
|
+ * register shared with SCLK divisor -- no chance of race because
|
||
|
+ * we don't use sensor interface
|
||
|
+ */
|
||
|
+ writew_dly((readw(glamo_mci_def_pdata.pglamo->base +
|
||
|
+ GLAMO_REG_CLOCK_GEN8) & 0xff00) | host->clk_div,
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_CLOCK_GEN8);
|
||
|
+ /* enable clock to divider input */
|
||
|
+ writew_dly(readw(glamo_mci_def_pdata.pglamo->base +
|
||
|
+ GLAMO_REG_CLOCK_GEN5_1) | GLAMO_CLOCK_GEN51_EN_DIV_TCLK,
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_CLOCK_GEN5_1);
|
||
|
+#if 0
|
||
|
+ } else { /* stop clock */
|
||
|
+ host->real_rate = 0;
|
||
|
+ /* remove clock from divider input */
|
||
|
+ writew(readw(glamo_mci_def_pdata.pglamo->base +
|
||
|
+ GLAMO_REG_CLOCK_GEN5_1) & (~GLAMO_CLOCK_GEN51_EN_DIV_TCLK),
|
||
|
+ glamo_mci_def_pdata.pglamo->base + GLAMO_REG_CLOCK_GEN5_1);
|
||
|
+ }
|
||
|
+#endif
|
||
|
+ if ((ios->power_mode == MMC_POWER_ON) ||
|
||
|
+ (ios->power_mode == MMC_POWER_UP)) {
|
||
|
+ dev_info(&host->pdev->dev,
|
||
|
+ "powered (vdd = %d) clk: %lukHz div=%d (req: %ukHz). "
|
||
|
+ "Bus width=%d\n",ios->vdd,
|
||
|
+ host->real_rate / 1000, mci_psc,
|
||
|
+ ios->clock / 1000, ios->bus_width);
|
||
|
+ } else
|
||
|
+ dev_info(&host->pdev->dev, "glamo_mci_set_ios: power down.\n");
|
||
|
+
|
||
|
+ /* set bus width */
|
||
|
+ host->bus_width = ios->bus_width;
|
||
|
+ if (host->bus_width == MMC_BUS_WIDTH_4)
|
||
|
+ n = GLAMO_BASIC_MMC_EN_4BIT_DATA;
|
||
|
+ writew_dly((readw_dly(host->base + GLAMO_REG_MMC_BASIC) &
|
||
|
+ (~GLAMO_BASIC_MMC_EN_4BIT_DATA)) | n,
|
||
|
+ host->base + GLAMO_REG_MMC_BASIC);
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+/*
|
||
|
+ * no physical write protect supported by us
|
||
|
+ */
|
||
|
+static int glamo_mci_get_ro(struct mmc_host *mmc)
|
||
|
+{
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static struct mmc_host_ops glamo_mci_ops = {
|
||
|
+ .request = glamo_mci_request,
|
||
|
+ .set_ios = glamo_mci_set_ios,
|
||
|
+ .get_ro = glamo_mci_get_ro,
|
||
|
+};
|
||
|
+
|
||
|
+static int glamo_mci_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct mmc_host *mmc;
|
||
|
+ struct glamo_mci_host *host;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ dev_info(&pdev->dev, "glamo_mci driver (C)2007 OpenMoko, Inc\n");
|
||
|
+
|
||
|
+ mmc = mmc_alloc_host(sizeof(struct glamo_mci_host), &pdev->dev);
|
||
|
+ if (!mmc) {
|
||
|
+ ret = -ENOMEM;
|
||
|
+ goto probe_out;
|
||
|
+ }
|
||
|
+
|
||
|
+ host = mmc_priv(mmc);
|
||
|
+ host->mmc = mmc;
|
||
|
+ host->pdev = pdev;
|
||
|
+ host->pdata = &glamo_mci_def_pdata;
|
||
|
+ host->power_mode_current = MMC_POWER_OFF;
|
||
|
+
|
||
|
+ host->complete_what = COMPLETION_NONE;
|
||
|
+ host->pio_active = XFER_NONE;
|
||
|
+
|
||
|
+ spin_lock_init(&host->complete_lock);
|
||
|
+
|
||
|
+ host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
+ if (!host->mem) {
|
||
|
+ dev_err(&pdev->dev,
|
||
|
+ "failed to get io memory region resouce.\n");
|
||
|
+
|
||
|
+ ret = -ENOENT;
|
||
|
+ goto probe_free_host;
|
||
|
+ }
|
||
|
+
|
||
|
+ host->mem = request_mem_region(host->mem->start,
|
||
|
+ RESSIZE(host->mem), pdev->name);
|
||
|
+
|
||
|
+ if (!host->mem) {
|
||
|
+ dev_err(&pdev->dev, "failed to request io memory region.\n");
|
||
|
+ ret = -ENOENT;
|
||
|
+ goto probe_free_host;
|
||
|
+ }
|
||
|
+
|
||
|
+ host->base = ioremap(host->mem->start, RESSIZE(host->mem));
|
||
|
+ if (!host->base) {
|
||
|
+ dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
|
||
|
+ ret = -EINVAL;
|
||
|
+ goto probe_free_mem_region;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* set the handler for our bit of the shared chip irq register */
|
||
|
+ set_irq_handler(IRQ_GLAMO(GLAMO_IRQIDX_MMC), glamo_mci_irq);
|
||
|
+ /* stash host as our handler's private data */
|
||
|
+ set_irq_data(IRQ_GLAMO(GLAMO_IRQIDX_MMC), host);
|
||
|
+
|
||
|
+ /* Get ahold of our data buffer we use for data in and out on MMC */
|
||
|
+ host->mem_data = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||
|
+ if (!host->mem_data) {
|
||
|
+ dev_err(&pdev->dev,
|
||
|
+ "failed to get io memory region resource.\n");
|
||
|
+ ret = -ENOENT;
|
||
|
+ goto probe_iounmap;
|
||
|
+ }
|
||
|
+
|
||
|
+ host->mem_data = request_mem_region(host->mem_data->start,
|
||
|
+ RESSIZE(host->mem_data), pdev->name);
|
||
|
+
|
||
|
+ if (!host->mem_data) {
|
||
|
+ dev_err(&pdev->dev, "failed to request io memory region.\n");
|
||
|
+ ret = -ENOENT;
|
||
|
+ goto probe_iounmap;
|
||
|
+ }
|
||
|
+ host->base_data = ioremap(host->mem_data->start,
|
||
|
+ RESSIZE(host->mem_data));
|
||
|
+ host->data_max_size = RESSIZE(host->mem_data);
|
||
|
+
|
||
|
+ if (host->base_data == 0) {
|
||
|
+ dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
|
||
|
+ ret = -EINVAL;
|
||
|
+ goto probe_free_mem_region_data;
|
||
|
+ }
|
||
|
+
|
||
|
+ host->vdd_current = 0;
|
||
|
+ host->clk_rate = 50000000; /* really it's 49152000 */
|
||
|
+ host->clk_div = 16;
|
||
|
+
|
||
|
+ /* explain our host controller capabilities */
|
||
|
+ mmc->ops = &glamo_mci_ops;
|
||
|
+ mmc->ocr_avail = host->pdata->ocr_avail;
|
||
|
+ mmc->caps = MMC_CAP_4_BIT_DATA |
|
||
|
+ MMC_CAP_MULTIWRITE |
|
||
|
+ MMC_CAP_MMC_HIGHSPEED |
|
||
|
+ MMC_CAP_SD_HIGHSPEED;
|
||
|
+ mmc->f_min = host->clk_rate / 256;
|
||
|
+ /*
|
||
|
+ * held at /4 due to concerns of 100R recommended series resistor
|
||
|
+ * allows 16MHz @ 4-bit --> 8MBytes/sec raw
|
||
|
+ */
|
||
|
+ mmc->f_max = host->clk_rate / 3;
|
||
|
+
|
||
|
+ mmc->max_blk_count = (1 << 16) - 1; /* GLAMO_REG_MMC_RB_BLKCNT */
|
||
|
+ mmc->max_blk_size = (1 << 12) - 1; /* GLAMO_REG_MMC_RB_BLKLEN */
|
||
|
+ mmc->max_req_size = RESSIZE(host->mem_data) / 2;
|
||
|
+ mmc->max_seg_size = mmc->max_req_size;
|
||
|
+ mmc->max_phys_segs = 1; /* hw doesn't talk about segs??? */
|
||
|
+ mmc->max_hw_segs = 1;
|
||
|
+
|
||
|
+ dev_info(&host->pdev->dev, "probe: mapped mci_base:%p irq:%u.\n",
|
||
|
+ host->base, host->irq);
|
||
|
+
|
||
|
+ if ((ret = mmc_add_host(mmc))) {
|
||
|
+ dev_err(&pdev->dev, "failed to add mmc host.\n");
|
||
|
+ goto probe_free_mem_region_data;
|
||
|
+ }
|
||
|
+
|
||
|
+ platform_set_drvdata(pdev, mmc);
|
||
|
+
|
||
|
+ dev_info(&pdev->dev,"initialisation done.\n");
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ probe_free_mem_region_data:
|
||
|
+ release_mem_region(host->mem_data->start, RESSIZE(host->mem_data));
|
||
|
+
|
||
|
+ probe_iounmap:
|
||
|
+ iounmap(host->base);
|
||
|
+
|
||
|
+ probe_free_mem_region:
|
||
|
+ release_mem_region(host->mem->start, RESSIZE(host->mem));
|
||
|
+
|
||
|
+ probe_free_host:
|
||
|
+ mmc_free_host(mmc);
|
||
|
+ probe_out:
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int glamo_mci_remove(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
|
||
|
+ struct glamo_mci_host *host = mmc_priv(mmc);
|
||
|
+
|
||
|
+ mmc_remove_host(mmc);
|
||
|
+ /* stop using our handler, revert it to default */
|
||
|
+ set_irq_handler(IRQ_GLAMO(GLAMO_IRQIDX_MMC), handle_level_irq);
|
||
|
+ iounmap(host->base);
|
||
|
+ iounmap(host->base_data);
|
||
|
+ release_mem_region(host->mem->start, RESSIZE(host->mem));
|
||
|
+ release_mem_region(host->mem_data->start, RESSIZE(host->mem_data));
|
||
|
+ mmc_free_host(mmc);
|
||
|
+
|
||
|
+ glamo_engine_disable(glamo_mci_def_pdata.pglamo, GLAMO_ENGINE_MMC);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+#ifdef CONFIG_PM
|
||
|
+
|
||
|
+static int glamo_mci_suspend(struct platform_device *dev, pm_message_t state)
|
||
|
+{
|
||
|
+ struct mmc_host *mmc = platform_get_drvdata(dev);
|
||
|
+
|
||
|
+ return mmc_suspend_host(mmc, state);
|
||
|
+}
|
||
|
+
|
||
|
+static int glamo_mci_resume(struct platform_device *dev)
|
||
|
+{
|
||
|
+ struct mmc_host *mmc = platform_get_drvdata(dev);
|
||
|
+
|
||
|
+ return mmc_resume_host(mmc);
|
||
|
+}
|
||
|
+
|
||
|
+#else /* CONFIG_PM */
|
||
|
+#define glamo_mci_suspend NULL
|
||
|
+#define glamo_mci_resume NULL
|
||
|
+#endif /* CONFIG_PM */
|
||
|
+
|
||
|
+
|
||
|
+static struct platform_driver glamo_mci_driver =
|
||
|
+{
|
||
|
+ .driver.name = "glamo-mci",
|
||
|
+ .probe = glamo_mci_probe,
|
||
|
+ .remove = glamo_mci_remove,
|
||
|
+ .suspend = glamo_mci_suspend,
|
||
|
+ .resume = glamo_mci_resume,
|
||
|
+};
|
||
|
+
|
||
|
+static int __init glamo_mci_init(void)
|
||
|
+{
|
||
|
+ platform_driver_register(&glamo_mci_driver);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void __exit glamo_mci_exit(void)
|
||
|
+{
|
||
|
+ platform_driver_unregister(&glamo_mci_driver);
|
||
|
+}
|
||
|
+
|
||
|
+module_init(glamo_mci_init);
|
||
|
+module_exit(glamo_mci_exit);
|
||
|
+
|
||
|
+MODULE_DESCRIPTION("Glamo MMC/SD Card Interface driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
+MODULE_AUTHOR("Andy Green <andy@openmoko.com>");
|
||
|
diff --git a/drivers/mfd/glamo/glamo-mci.h b/drivers/mfd/glamo/glamo-mci.h
|
||
|
new file mode 100644
|
||
|
index 0000000..40c3e24
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/mfd/glamo/glamo-mci.h
|
||
|
@@ -0,0 +1,76 @@
|
||
|
+/*
|
||
|
+ * linux/drivers/mmc/host/glamo-mmc.h - GLAMO MCI driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2007-2008 OpenMoko, Inc, Andy Green <andy@openmoko.com>
|
||
|
+ * based on S3C MMC driver -->
|
||
|
+ * Copyright (C) 2004-2006 Thomas Kleffel, 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.
|
||
|
+ */
|
||
|
+
|
||
|
+
|
||
|
+enum glamo_mci_waitfor {
|
||
|
+ COMPLETION_NONE,
|
||
|
+ COMPLETION_FINALIZE,
|
||
|
+ COMPLETION_CMDSENT,
|
||
|
+ COMPLETION_RSPFIN,
|
||
|
+ COMPLETION_XFERFINISH,
|
||
|
+ COMPLETION_XFERFINISH_RSPFIN,
|
||
|
+};
|
||
|
+
|
||
|
+struct glamo_mci_host {
|
||
|
+ struct platform_device *pdev;
|
||
|
+ struct glamo_mci_pdata *pdata;
|
||
|
+ struct mmc_host *mmc;
|
||
|
+ struct resource *mem;
|
||
|
+ struct resource *mem_data;
|
||
|
+ struct clk *clk;
|
||
|
+ void __iomem *base;
|
||
|
+ u16 __iomem *base_data;
|
||
|
+ int irq;
|
||
|
+ int irq_cd;
|
||
|
+ int dma;
|
||
|
+ int data_max_size;
|
||
|
+
|
||
|
+ int power_mode_current;
|
||
|
+ unsigned int vdd_current;
|
||
|
+
|
||
|
+ unsigned long clk_rate;
|
||
|
+ unsigned long clk_div;
|
||
|
+ unsigned long real_rate;
|
||
|
+ u8 prescaler;
|
||
|
+
|
||
|
+ unsigned sdiimsk;
|
||
|
+ int dodma;
|
||
|
+
|
||
|
+ volatile int dmatogo;
|
||
|
+
|
||
|
+ struct mmc_request *mrq;
|
||
|
+ int cmd_is_stop;
|
||
|
+
|
||
|
+ spinlock_t complete_lock;
|
||
|
+ volatile enum glamo_mci_waitfor
|
||
|
+ complete_what;
|
||
|
+
|
||
|
+ volatile int dma_complete;
|
||
|
+
|
||
|
+ volatile u32 pio_sgptr;
|
||
|
+ volatile u32 pio_words;
|
||
|
+ volatile u32 pio_count;
|
||
|
+ volatile u16 *pio_ptr;
|
||
|
+#define XFER_NONE 0
|
||
|
+#define XFER_READ 1
|
||
|
+#define XFER_WRITE 2
|
||
|
+ volatile u32 pio_active;
|
||
|
+
|
||
|
+ int bus_width;
|
||
|
+
|
||
|
+ char dbgmsg_cmd[301];
|
||
|
+ char dbgmsg_dat[301];
|
||
|
+ volatile char *status;
|
||
|
+
|
||
|
+ unsigned int ccnt, dcnt;
|
||
|
+ struct tasklet_struct pio_tasklet;
|
||
|
+};
|
||
|
diff --git a/drivers/mfd/glamo/glamo-regs.h b/drivers/mfd/glamo/glamo-regs.h
|
||
|
index 151cd66..8f6c45c 100644
|
||
|
--- a/drivers/mfd/glamo/glamo-regs.h
|
||
|
+++ b/drivers/mfd/glamo/glamo-regs.h
|
||
|
@@ -150,16 +150,28 @@ enum glamo_reg_mem_dram2 {
|
||
|
GLAMO_MEM_DRAM2_DEEP_PWRDOWN = (1 << 12),
|
||
|
};
|
||
|
|
||
|
+enum glamo_irq_index {
|
||
|
+ GLAMO_IRQIDX_HOSTBUS = 0,
|
||
|
+ GLAMO_IRQIDX_JPEG = 1,
|
||
|
+ GLAMO_IRQIDX_MPEG = 2,
|
||
|
+ GLAMO_IRQIDX_MPROC1 = 3,
|
||
|
+ GLAMO_IRQIDX_MPROC0 = 4,
|
||
|
+ GLAMO_IRQIDX_CMDQUEUE = 5,
|
||
|
+ GLAMO_IRQIDX_2D = 6,
|
||
|
+ GLAMO_IRQIDX_MMC = 7,
|
||
|
+ GLAMO_IRQIDX_RISC = 8,
|
||
|
+};
|
||
|
+
|
||
|
enum glamo_irq {
|
||
|
- GLAMO_IRQ_HOSTBUS = 0x0001,
|
||
|
- GLAMO_IRQ_JPEG = 0x0002,
|
||
|
- GLAMO_IRQ_MPEG = 0x0004,
|
||
|
- GLAMO_IRQ_MPROC1 = 0x0008,
|
||
|
- GLAMO_IRQ_MPROC0 = 0x0010,
|
||
|
- GLAMO_IRQ_CMDQUEUE = 0x0020,
|
||
|
- GLAMO_IRQ_2D = 0x0040,
|
||
|
- GLAMO_IRQ_MMC = 0x0080,
|
||
|
- GLAMO_IRQ_RISC = 0x0100,
|
||
|
+ GLAMO_IRQ_HOSTBUS = (1 << GLAMO_IRQIDX_HOSTBUS),
|
||
|
+ GLAMO_IRQ_JPEG = (1 << GLAMO_IRQIDX_JPEG),
|
||
|
+ GLAMO_IRQ_MPEG = (1 << GLAMO_IRQIDX_MPEG),
|
||
|
+ GLAMO_IRQ_MPROC1 = (1 << GLAMO_IRQIDX_MPROC1),
|
||
|
+ GLAMO_IRQ_MPROC0 = (1 << GLAMO_IRQIDX_MPROC0),
|
||
|
+ GLAMO_IRQ_CMDQUEUE = (1 << GLAMO_IRQIDX_CMDQUEUE),
|
||
|
+ GLAMO_IRQ_2D = (1 << GLAMO_IRQIDX_2D),
|
||
|
+ GLAMO_IRQ_MMC = (1 << GLAMO_IRQIDX_MMC),
|
||
|
+ GLAMO_IRQ_RISC = (1 << GLAMO_IRQIDX_RISC),
|
||
|
};
|
||
|
|
||
|
enum glamo_reg_clock_host {
|
||
|
@@ -197,6 +209,145 @@ enum glamo_reg_clock_mmc {
|
||
|
GLAMO_CLOCK_MMC_RESET = 0x1000,
|
||
|
};
|
||
|
|
||
|
+enum glamo_reg_basic_mmc {
|
||
|
+ /* set to disable CRC error rejection */
|
||
|
+ GLAMO_BASIC_MMC_DISABLE_CRC = 0x0001,
|
||
|
+ /* enable completion interrupt */
|
||
|
+ GLAMO_BASIC_MMC_EN_COMPL_INT = 0x0002,
|
||
|
+ /* stop MMC clock while enforced idle waiting for data from card */
|
||
|
+ GLAMO_BASIC_MMC_NO_CLK_RD_WAIT = 0x0004,
|
||
|
+ /* 0 = 1-bit bus to card, 1 = use 4-bit bus (has to be negotiated) */
|
||
|
+ GLAMO_BASIC_MMC_EN_4BIT_DATA = 0x0008,
|
||
|
+ /* enable 75K pullups on D3..D0 */
|
||
|
+ GLAMO_BASIC_MMC_EN_DATA_PUPS = 0x0010,
|
||
|
+ /* enable 75K pullup on CMD */
|
||
|
+ GLAMO_BASIC_MMC_EN_CMD_PUP = 0x0020,
|
||
|
+ /* IO drive strength 00=weak -> 11=strongest */
|
||
|
+ GLAMO_BASIC_MMC_EN_DR_STR0 = 0x0040,
|
||
|
+ GLAMO_BASIC_MMC_EN_DR_STR1 = 0x0080,
|
||
|
+ /* TCLK delay stage A, 0000 = 500ps --> 1111 = 8ns */
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYA0 = 0x0100,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYA1 = 0x0200,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYA2 = 0x0400,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYA3 = 0x0800,
|
||
|
+ /* TCLK delay stage B (cumulative), 0000 = 500ps --> 1111 = 8ns */
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYB0 = 0x1000,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYB1 = 0x2000,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYB2 = 0x4000,
|
||
|
+ GLAMO_BASIC_MMC_EN_TCLK_DLYB3 = 0x8000,
|
||
|
+};
|
||
|
+
|
||
|
+enum glamo_reg_stat1_mmc {
|
||
|
+ /* command "counter" (really: toggle) */
|
||
|
+ GLAMO_STAT1_MMC_CMD_CTR = 0x8000,
|
||
|
+ /* engine is idle */
|
||
|
+ GLAMO_STAT1_MMC_IDLE = 0x4000,
|
||
|
+ /* readback response is ready */
|
||
|
+ GLAMO_STAT1_MMC_RB_RRDY = 0x0200,
|
||
|
+ /* readback data is ready */
|
||
|
+ GLAMO_STAT1_MMC_RB_DRDY = 0x0100,
|
||
|
+ /* no response timeout */
|
||
|
+ GLAMO_STAT1_MMC_RTOUT = 0x0020,
|
||
|
+ /* no data timeout */
|
||
|
+ GLAMO_STAT1_MMC_DTOUT = 0x0010,
|
||
|
+ /* CRC error on block write */
|
||
|
+ GLAMO_STAT1_MMC_BWERR = 0x0004,
|
||
|
+ /* CRC error on block read */
|
||
|
+ GLAMO_STAT1_MMC_BRERR = 0x0002
|
||
|
+};
|
||
|
+
|
||
|
+enum glamo_reg_fire_mmc {
|
||
|
+ /* command "counter" (really: toggle)
|
||
|
+ * the STAT1 register reflects this so you can ensure you don't look
|
||
|
+ * at status for previous command
|
||
|
+ */
|
||
|
+ GLAMO_FIRE_MMC_CMD_CTR = 0x8000,
|
||
|
+ /* sets kind of response expected */
|
||
|
+ GLAMO_FIRE_MMC_RES_MASK = 0x0700,
|
||
|
+ /* sets command type */
|
||
|
+ GLAMO_FIRE_MMC_TYP_MASK = 0x00C0,
|
||
|
+ /* sets command class */
|
||
|
+ GLAMO_FIRE_MMC_CLS_MASK = 0x000F,
|
||
|
+};
|
||
|
+
|
||
|
+enum glamo_fire_mmc_response_types {
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R1 = 0x0000,
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R1b = 0x0100,
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R2 = 0x0200,
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R3 = 0x0300,
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R4 = 0x0400,
|
||
|
+ GLAMO_FIRE_MMC_RSPT_R5 = 0x0500,
|
||
|
+};
|
||
|
+
|
||
|
+enum glamo_fire_mmc_command_types {
|
||
|
+ /* broadcast, no response */
|
||
|
+ GLAMO_FIRE_MMC_CMDT_BNR = 0x0000,
|
||
|
+ /* broadcast, with response */
|
||
|
+ GLAMO_FIRE_MMC_CMDT_BR = 0x0040,
|
||
|
+ /* addressed, no data */
|
||
|
+ GLAMO_FIRE_MMC_CMDT_AND = 0x0080,
|
||
|
+ /* addressed, with data */
|
||
|
+ GLAMO_FIRE_MMC_CMDT_AD = 0x00C0,
|
||
|
+};
|
||
|
+
|
||
|
+enum glamo_fire_mmc_command_class {
|
||
|
+ /* "Stream Read" */
|
||
|
+ GLAMO_FIRE_MMC_CC_STRR = 0x0000,
|
||
|
+ /* Single Block Read */
|
||
|
+ GLAMO_FIRE_MMC_CC_SBR = 0x0001,
|
||
|
+ /* Multiple Block Read With Stop */
|
||
|
+ GLAMO_FIRE_MMC_CC_MBRS = 0x0002,
|
||
|
+ /* Multiple Block Read No Stop */
|
||
|
+ GLAMO_FIRE_MMC_CC_MBRNS = 0x0003,
|
||
|
+ /* RESERVED for "Stream Write" */
|
||
|
+ GLAMO_FIRE_MMC_CC_STRW = 0x0004,
|
||
|
+ /* "Stream Write" */
|
||
|
+ GLAMO_FIRE_MMC_CC_SBW = 0x0005,
|
||
|
+ /* RESERVED for Multiple Block Write With Stop */
|
||
|
+ GLAMO_FIRE_MMC_CC_MBWS = 0x0006,
|
||
|
+ /* Multiple Block Write No Stop */
|
||
|
+ GLAMO_FIRE_MMC_CC_MBWNS = 0x0007,
|
||
|
+ /* STOP command */
|
||
|
+ GLAMO_FIRE_MMC_CC_STOP = 0x0008,
|
||
|
+ /* Cancel on Running Command */
|
||
|
+ GLAMO_FIRE_MMC_CC_CANCL = 0x0009,
|
||
|
+ /* "Basic Command" */
|
||
|
+ GLAMO_FIRE_MMC_CC_BASIC = 0x000a,
|
||
|
+};
|
||
|
+
|
||
|
+/* these are offsets from the start of the MMC register region */
|
||
|
+enum glamo_register_mmc {
|
||
|
+ /* MMC command, b15..8 = cmd arg b7..0; b7..1 = CRC; b0 = end bit */
|
||
|
+ GLAMO_REG_MMC_CMD_REG1 = 0x00,
|
||
|
+ /* MMC command, b15..0 = cmd arg b23 .. 8 */
|
||
|
+ GLAMO_REG_MMC_CMD_REG2 = 0x02,
|
||
|
+ /* MMC command, b15=start, b14=transmission,
|
||
|
+ * b13..8=cmd idx, b7..0=cmd arg b31..24
|
||
|
+ */
|
||
|
+ GLAMO_REG_MMC_CMD_REG3 = 0x04,
|
||
|
+ GLAMO_REG_MMC_CMD_FIRE = 0x06,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP1 = 0x10,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP2 = 0x12,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP3 = 0x14,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP4 = 0x16,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP5 = 0x18,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP6 = 0x1a,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP7 = 0x1c,
|
||
|
+ GLAMO_REG_MMC_CMD_RSP8 = 0x1e,
|
||
|
+ GLAMO_REG_MMC_RB_STAT1 = 0x20,
|
||
|
+ GLAMO_REG_MMC_RB_BLKCNT = 0x22,
|
||
|
+ GLAMO_REG_MMC_RB_BLKLEN = 0x24,
|
||
|
+ GLAMO_REG_MMC_BASIC = 0x30,
|
||
|
+ GLAMO_REG_MMC_RDATADS1 = 0x34,
|
||
|
+ GLAMO_REG_MMC_RDATADS2 = 0x36,
|
||
|
+ GLAMO_REG_MMC_WDATADS1 = 0x38,
|
||
|
+ GLAMO_REG_MMC_WDATADS2 = 0x3a,
|
||
|
+ GLAMO_REG_MMC_DATBLKCNT = 0x3c,
|
||
|
+ GLAMO_REG_MMC_DATBLKLEN = 0x3e,
|
||
|
+ GLAMO_REG_MMC_TIMEOUT = 0x40,
|
||
|
+
|
||
|
+};
|
||
|
+
|
||
|
enum glamo_reg_clock_isp {
|
||
|
GLAMO_CLOCK_ISP_DG_I1CLK = 0x0001,
|
||
|
GLAMO_CLOCK_ISP_EN_I1CLK = 0x0002,
|
||
|
--
|
||
|
1.5.6.3
|
||
|
|