summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorGerrit Code Review <gerrit2@git-hel.nvidia.com>2009-12-15 23:33:13 +0200
committerGerrit Code Review <gerrit2@git-hel.nvidia.com>2009-12-15 23:33:13 +0200
commit9e1819cc52aa0a897d10ec357aa89489bcc39d1a (patch)
tree144803749d8ac57c6722bcf53b89af26c3e6ade5 /drivers
parent24c17475126d7e59c1e178d55eb5b108ec1e18dd (diff)
parent86c3895709779facb7b52560780128b8c9ad75c0 (diff)
Merge change I86c38957 into android-tegra-2.6.29
* changes: mmc: add MMC driver for NVIDIA Tegra SDIO DDK
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mmc/host/Kconfig7
-rw-r--r--drivers/mmc/host/Makefile2
-rw-r--r--drivers/mmc/host/tegra_sdio.c578
3 files changed, 586 insertions, 1 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 07c2edb02184..3f8a5bb69e42 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -56,6 +56,13 @@ config MMC_SDHCI_TEGRA
This enables the internal SDHCI-compatible interfaces in NVIDIA
Tegra SoCs
+config MMC_TEGRA_SDIO
+ boolean "MMC support for SDIO interface on NVIDIA Tegra SoCs"
+ depends on ARCH_TEGRA_1x_SOC && MACH_TEGRA_GENERIC
+ help
+ This enables the SDIO interface on NVIDIA Tegra SoCs
+
+
config MMC_RICOH_MMC
tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)"
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 2213463550ff..b95a3e450adc 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -28,4 +28,4 @@ endif
obj-$(CONFIG_MMC_S3C) += s3cmci.o
obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o
obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o
-
+obj-$(CONFIG_MMC_TEGRA_SDIO) += tegra_sdio.o
diff --git a/drivers/mmc/host/tegra_sdio.c b/drivers/mmc/host/tegra_sdio.c
new file mode 100644
index 000000000000..1503287880ec
--- /dev/null
+++ b/drivers/mmc/host/tegra_sdio.c
@@ -0,0 +1,578 @@
+/*
+ * drivers/mmc/host/tegra_sdio.c
+ *
+ * MMC host driver for NVIDIA Tegra SoCs using NvDdk and NvRm APIs
+ *
+ * Copyright (C) 2009 NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+// #define DEBUG
+// #define VERBOSE_DEBUG
+
+/*
+ * TODO:
+ * 1. Revisit locking
+ * 2. use zero-copy DMA instead of buffer copy.
+ * 3. Detect card removal properly and handle the trasactions on the fly
+ * 4. Don't know how to enable SD card irq.
+ * 5. Some of the values like max clock, voltages supported and bit rates are
+ * hard coded. Need to get those from DDK.
+ */
+
+
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/workqueue.h>
+#include <linux/kthread.h>
+
+#include <linux/mmc/host.h>
+
+#include "mach/nvrm_linux.h"
+#include "nvddk_sdio.h"
+#include "nvos.h"
+#include "nvodm_sdio.h"
+
+#define DRIVER_DESC "Nvidia Tegra SDIO/SD driver"
+#define DRIVER_NAME "tegra_sdio"
+
+#ifdef VERBOSE_DEBUG
+#define DBG(f, x...) \
+ pr_info(DRIVER_NAME " [%s()]: " f, __func__,## x)
+#else
+#define DBG(f, x...)
+#endif
+
+#define USE_SPINLOCK 0
+
+#if USE_SPINLOCK
+#define LOCK_T spinlock_t
+#define CREATELOCK(_l) spin_lock_init(&(_l))
+#define DELETELOCK(_l)
+#define LOCK(_l) spin_lock(&(_l))
+#define UNLOCK(_l) spin_unlock(&(_l))
+#define ATOMIC(_l,_f) spin_lock_irqsave(&(_l),(_f))
+#define UNATOMIC(_l,_f) spin_unlock_irqrestore(&(_l),(_f))
+#else
+#define LOCK_T struct mutex
+#define CREATELOCK(_l) mutex_init(&(_l))
+#define DELETELOCK(_l)
+#define LOCK(_l) mutex_lock(&(_l))
+#define UNLOCK(_l) mutex_unlock(&(_l))
+#define ATOMIC(_l,_f) local_irq_save((_f))
+#define UNATOMIC(_l,_f) local_irq_restore((_f))
+#endif
+
+struct tegra_sdio_host{
+ struct mmc_host *mmc;
+ struct platform_device *pdev;
+ LOCK_T Lock;
+ NvU32 Instance;
+ NvDdkSdioSDBusVoltage voltage;
+ struct work_struct Workqueue;
+ struct mmc_request *req;
+ NvBool bCardPresent;
+ NvDdkSdioDeviceHandle hDdk;
+ NvDdkSdioHostCapabilities hostCaps;
+ NvDdkSdioInterfaceCapabilities interfaceCaps;
+ NvOsSemaphoreHandle hSem;
+ NvOsSemaphoreHandle hCardSemaphore;
+ NvOdmSdioHandle hSdioHandle;
+ struct task_struct *cardDetectTask;
+ NvBool bKillCardDetectThread;
+ unsigned int clock;
+ unsigned int bus_width;
+ NvU32 StartOffset;
+};
+
+#ifdef VERBOSE_DEBUG
+static void tegra_DumpPackets(struct mmc_data *data)
+{
+ void *addr = (void *)sg_virt(data->sg);
+ int i;
+
+ for (i=0; i< sg_dma_len(data->sg); i++) {
+ if (!(i%8))
+ printk("\n");
+ printk("%x ", ((unsigned char *)addr)[i]);
+ }
+ printk("\n");
+}
+#endif
+
+static int tegra_sdio_carddetect(void *arg)
+{
+ struct tegra_sdio_host *host = (struct tegra_sdio_host *)arg;
+ while (1) {
+ NvOsSemaphoreWait(host->hCardSemaphore);
+ if (host->bKillCardDetectThread)
+ break;
+ mmc_detect_change(host->mmc, 0);
+ }
+ return 0;
+}
+
+static inline NvDdkSdioRespType tegra_ConvertRespType(struct mmc_command *cmd)
+{
+ NvDdkSdioRespType resp = 0;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ resp = NvDdkSdioRespType_NoResp;
+ break;
+ case MMC_RSP_R1: /* MMC_RSP_R5 & MMC_RSP_R6 */
+ resp = NvDdkSdioRespType_R1;
+ break;
+ case MMC_RSP_R1B:
+ resp = NvDdkSdioRespType_R1b;
+ break;
+ case MMC_RSP_R2:
+ resp = NvDdkSdioRespType_R2;
+ break;
+ case MMC_RSP_R3: /* Same as MMC_RSP_R4 */
+ resp = NvDdkSdioRespType_R3;
+ break;
+ default:
+ BUG_ON(1);
+ }
+ return resp;
+}
+
+
+static inline void tegra_GetRepsonse(struct tegra_sdio_host *host,
+ struct mmc_command *cmd)
+{
+ if (cmd->flags & MMC_RSP_136) {
+ int i;
+ NvU32 resp[4];
+ NvDdkSdioGetCommandResponse(host->hDdk, 0,
+ tegra_ConvertRespType(cmd), resp);
+
+ /* Re-format to the way the linux stack expects */
+ for (i = 0;i < 4;i++) {
+ cmd->resp[i] = resp[3-i] << 8;
+ if (i != 3)
+ cmd->resp[i] |= ((NvU8 *)resp)[(3-i) * 4 -1];
+ }
+ } else {
+ NvDdkSdioGetCommandResponse(host->hDdk, 0,
+ tegra_ConvertRespType(cmd),
+ (NvU32 *)(cmd->resp));
+ }
+ return;
+}
+
+//
+// Work queue which does all the heavy lifting.
+//
+
+static void tegra_sdio_wq(struct work_struct *w)
+{
+ struct tegra_sdio_host *host = container_of(w, struct tegra_sdio_host,
+ Workqueue);
+ struct mmc_request *req;
+ NvDdkSdioCommand cmd;
+ NvU32 status;
+ NvError err;
+
+ LOCK(host->Lock);
+
+ req = host->req;
+
+ NvOsMemset(&cmd, 0, sizeof(cmd));
+ cmd.CommandCode = req->cmd->opcode;
+ cmd.CommandType = NvDdkSdioCommandType_Normal;
+ cmd.CmdArgument = req->cmd->arg;
+ cmd.ResponseType = tegra_ConvertRespType(req->cmd);
+ if (req->data) {
+ struct mmc_data *data = req->data;
+ void *addr;
+ int sg_cnt;
+ int size;
+ NvBool cmd12Enable = NV_FALSE;
+
+ /* FIXME only works for block addressing cards! */
+ if (host->StartOffset)
+ cmd.CmdArgument += host->StartOffset >> 9;
+
+ BUG_ON(data->blksz * data->blocks > 524288);
+ BUG_ON(data->blksz > host->mmc->max_blk_size);
+ BUG_ON(data->blocks > 65535);
+
+ sg_cnt = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ DMA_BIDIRECTIONAL);
+ BUG_ON(sg_cnt != 1);
+
+ cmd.IsDataCommand = NV_TRUE;
+ cmd.BlockSize = data->blksz;
+ if (data->stop) {
+ /* only support CMD12 of all the stop commands.
+ * This seems to work!
+ */
+ if (data->stop->opcode == 12 && !data->stop->arg) {
+ cmd12Enable = NV_TRUE;
+ } else {
+ BUG_ON(1);
+ }
+ }
+
+ size = data->blocks * data->blksz;
+ addr = (void *)sg_virt(data->sg);
+
+ if (data->flags & MMC_DATA_READ)
+ err = NvDdkSdioRead(host->hDdk, size, addr, &cmd,
+ cmd12Enable, &status);
+ else
+ err = NvDdkSdioWrite(host->hDdk, size, addr, &cmd,
+ cmd12Enable, &status);
+
+ if (err == NvSuccess && status == NvDdkSdioError_None) {
+ req->cmd->error = 0;
+ req->data->error = 0;
+ data->bytes_xfered = data->blksz * data->blocks;
+
+ } else {
+ /* FIXME set the appropriate error code */
+ DBG("Controller reported error 0x%x API error 0x%x\n",
+ status, err);
+ data->bytes_xfered = 0;
+ req->cmd->error = -EIO;
+ req->data->error = -EIO;
+ }
+
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, DMA_BIDIRECTIONAL);
+
+ tegra_GetRepsonse(host, req->cmd);
+
+ } else {
+ cmd.IsDataCommand = NV_FALSE;
+ cmd.BlockSize = 512;
+ err = NvDdkSdioSendCommand(host->hDdk, &cmd, &status);
+ if (err == NvSuccess) {
+ if (status == NvDdkSdioError_None)
+ req->cmd->error = 0;
+ else
+ req->cmd->error = -EINVAL;
+ } else
+ DBG("DDK failed controller status = 0x%x\n", status);
+
+ tegra_GetRepsonse(host, req->cmd);
+ }
+
+ UNLOCK(host->Lock);
+
+ host->req = NULL;
+ mmc_request_done(host->mmc, req);
+ return;
+}
+
+//
+// MMC callbacks
+//
+
+static void tegra_sdio_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct tegra_sdio_host *host;
+
+ host = mmc_priv(mmc);
+
+ LOCK(host->Lock);
+
+ host->req = req;
+ if (!host->bCardPresent) {
+ host->req->cmd->error = -ENOMEDIUM;
+ }
+
+ schedule_work(&host->Workqueue);
+ UNLOCK(host->Lock);
+}
+
+static int tegra_sdio_get_ro(struct mmc_host *mmc)
+{
+ struct tegra_sdio_host *host;
+ NvBool bIsWriteProtected = NV_FALSE;
+ NvError err;
+
+ host = mmc_priv(mmc);
+ err = NvDdkSdioIsWriteProtected(host->hDdk, s_hGpioGlobal,
+ &bIsWriteProtected);
+ if (err != NvSuccess) {
+ pr_err(DRIVER_NAME "IsWriteProtected error(%d)\n", err);
+ if (err == NvError_SdioCardNotPresent) {
+ return 0;
+ } else {
+ return -ENOSYS;
+ }
+ }
+ return (bIsWriteProtected == NV_TRUE) ? 1 : 0;
+}
+
+
+static void tegra_sdio_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct tegra_sdio_host *host;
+ NvError err;
+
+ host = mmc_priv(mmc);
+
+ /*
+ * We first get the MMC_POWER_OFF, then MMC_POWER_UP and
+ * then the stack swtiches to the MMC_POWER_ON mode.
+ */
+ if (ios->power_mode == MMC_POWER_OFF) {
+ /* How do we power off the controller and the io? */
+ }
+
+ /* Set the clock. FIXME DDK doesn't allow the clock to be set to 0 */
+ if (ios->clock) {
+ if (ios->clock != host->clock) {
+ NvU32 configuredFreq;
+ err = NvDdkSdioSetClockFrequency(host->hDdk,
+ ios->clock/1000, &configuredFreq);
+ BUG_ON(err!=NvSuccess);
+ DBG("SDIO clock set to (%dKHz) req (%dKHz) err (%d)\n",
+ configuredFreq, ios->clock/1000, err);
+ host->clock = ios->clock;
+ }
+ }
+
+ /* Set the bus width */
+ if (host->bus_width != ios->bus_width) {
+ NvDdkSdioDataWidth width;
+ if (ios->bus_width == MMC_BUS_WIDTH_8) {
+ width = NvDdkSdioDataWidth_8Bit;
+ } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ width = NvDdkSdioDataWidth_4Bit;
+ } else if (ios->bus_width == MMC_BUS_WIDTH_1) {
+ width = NvDdkSdioDataWidth_1Bit;
+ } else {
+ /* Got wrong parameters */
+ width = 0;
+ BUG_ON(1);
+ }
+ NvDdkSdioSetHostBusWidth(host->hDdk, width);
+ host->bus_width = ios->bus_width;
+ }
+
+ /* timming - is there any difference between SD high speed and mmc high
+ * speed? */
+ if (ios->timing == MMC_TIMING_SD_HS || ios->timing == MMC_TIMING_SD_HS)
+ NvDdkSdioHighSpeedEnable(host->hDdk);
+ else
+ NvDdkSdioHighSpeedDisable(host->hDdk);
+
+ return;
+}
+
+static int tegra_sdio_cardpresent(struct mmc_host *mmc)
+{
+ struct tegra_sdio_host *host;
+ NvBool bIsCardPResent;
+
+ host = mmc_priv(mmc);
+ NvDdkSdioIsCardInserted(host->hDdk, &bIsCardPResent);
+
+ DBG("Sdio slot %s:%s\n", mmc_hostname(mmc),
+ ((bIsCardPResent == NV_TRUE) ? "full" : "empty"));
+
+ return (bIsCardPResent == NV_TRUE) ? 1 : 0;
+}
+
+static void tegra_sdio_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ /* FIXME what should we do here? */
+ return;
+}
+
+static struct mmc_host_ops tegra_sdio_ops =
+{
+ .request = tegra_sdio_request,
+ .set_ios = tegra_sdio_set_ios,
+ .get_ro = tegra_sdio_get_ro,
+ .get_cd = tegra_sdio_cardpresent,
+ .enable_sdio_irq = tegra_sdio_enable_sdio_irq,
+};
+
+static int
+tegra_sdio_probe(struct platform_device *pdev)
+{
+ struct tegra_sdio_host *host = NULL;
+ struct mmc_host *mmc = NULL;
+ int ret = -ENODEV;
+ NvError err;
+ struct tegra_sdio_pdata *pdata = pdev->dev.platform_data;
+
+ mmc = mmc_alloc_host(sizeof(struct tegra_sdio_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ dev_set_drvdata(&pdev->dev, host);
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->pdev = pdev;
+ host->Instance = pdev->id;
+ host->voltage = NvDdkSdioSDBusVoltage_invalid;
+ CREATELOCK(host->Lock);
+ INIT_WORK(&host->Workqueue, tegra_sdio_wq);
+ host->hSdioHandle = NvOdmSdioOpen(pdev->id);
+ if (!host->hSdioHandle) {
+ goto fail;
+ }
+ NvDdkSdioGetCapabilities(s_hRmGlobal, &host->hostCaps,
+ &host->interfaceCaps, pdev->id);
+
+ /* Set SD/MMC configuration */
+ mmc->ops = &tegra_sdio_ops;
+ mmc->caps = 0;
+ if (host->interfaceCaps.MmcInterfaceWidth >= 4) {
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ if (host->interfaceCaps.MmcInterfaceWidth == 8) {
+ mmc->caps |= MMC_CAP_8_BIT_DATA;
+ }
+ }
+
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+ /* Is there a caps bit to select if the controller supports high speed
+ * mode or not? */
+ if (1) {
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+ }
+
+ /* Supports upto to 52M i.e compliant to high speed and EMMC and
+ * SD specs */
+ mmc->f_max = 52000000;
+ mmc->f_min = 400000;
+ mmc->max_seg_size = 524288;
+ mmc->max_req_size = 524288;
+ mmc->max_phys_segs = 128;
+ mmc->max_hw_segs = 1;
+ mmc->max_blk_size = 512;
+ mmc->max_blk_count = 65535;
+
+ mmc->ocr_avail = 0;
+ mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34;
+
+ if ((NvOsSemaphoreCreate(&host->hCardSemaphore, 0) != NvSuccess)
+ || (NvOsSemaphoreCreate(&host->hSem, 0) != NvSuccess)) {
+ goto fail;
+ }
+
+ host->bKillCardDetectThread = NV_FALSE;
+ host->cardDetectTask = kthread_create(tegra_sdio_carddetect,
+ (void *)host, "sdio_card_detect/%d", pdev->id);
+ if (IS_ERR(host->cardDetectTask)) {
+ goto fail;
+ }
+ wake_up_process( host->cardDetectTask );
+
+ err = NvDdkSdioOpen(s_hRmGlobal, s_hGpioGlobal, &host->hDdk,
+ &host->hSem, &host->hCardSemaphore, pdev->id);
+ if (err != NvSuccess) {
+ goto fail;
+ }
+ host->clock = 0;
+ host->bus_width = (unsigned int)-1;
+ if (pdata)
+ host->StartOffset = pdata->StartOffset;
+
+ ret = mmc_add_host(mmc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add mmc host\n");
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, mmc);
+ dev_info(&pdev->dev, "Initialization done\n");
+
+ return 0;
+
+fail:
+ if (host) {
+ if (host->cardDetectTask) {
+ host->bKillCardDetectThread = NV_TRUE;
+ NvOsSemaphoreSignal(host->hCardSemaphore);
+ (void)kthread_stop( host->cardDetectTask );
+ }
+ if (host->hSem)
+ NvOsSemaphoreDestroy(host->hSem);
+ if (host->hCardSemaphore)
+ NvOsSemaphoreDestroy(host->hCardSemaphore);
+ if (host->hSdioHandle)
+ NvOdmSdioClose(host->hSdioHandle);
+ if (host->hDdk)
+ NvDdkSdioClose(host->hDdk);
+ }
+ if (mmc)
+ mmc_free_host(mmc);
+
+ return ret;
+}
+
+static int __devexit
+tegra_sdio_remove(struct platform_device *pdev)
+{
+ struct tegra_sdio_host *host = dev_get_drvdata(&pdev->dev);
+
+ if (!host) return 0;
+
+ host->bKillCardDetectThread = NV_TRUE;
+ NvOsSemaphoreSignal(host->hCardSemaphore);
+ (void)kthread_stop( host->cardDetectTask );
+
+ if (host->hDdk)
+ NvDdkSdioClose(host->hDdk);
+
+ if (host->hSdioHandle)
+ NvOdmSdioClose(host->hSdioHandle);
+
+ mmc_free_host(host->mmc);
+ dev_set_drvdata(&pdev->dev, NULL);
+ return 0;
+}
+
+static struct platform_driver tegra_sdio_driver = {
+ .probe = tegra_sdio_probe,
+ .remove = __devexit_p(tegra_sdio_remove),
+ .suspend = NULL,
+ .resume = NULL,
+ .driver = {
+ .name = "tegra-sdio",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init tegra_sdio_init(void)
+{
+ return platform_driver_register(&tegra_sdio_driver);
+}
+
+static void __exit tegra_sdio_exit(void)
+{
+ platform_driver_unregister(&tegra_sdio_driver);
+}
+
+module_init(tegra_sdio_init);
+module_exit(tegra_sdio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(DRIVER_DESC);
+