diff options
author | Gerrit Code Review <gerrit2@git-hel.nvidia.com> | 2009-12-15 23:33:13 +0200 |
---|---|---|
committer | Gerrit Code Review <gerrit2@git-hel.nvidia.com> | 2009-12-15 23:33:13 +0200 |
commit | 9e1819cc52aa0a897d10ec357aa89489bcc39d1a (patch) | |
tree | 144803749d8ac57c6722bcf53b89af26c3e6ade5 /drivers | |
parent | 24c17475126d7e59c1e178d55eb5b108ec1e18dd (diff) | |
parent | 86c3895709779facb7b52560780128b8c9ad75c0 (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/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 2 | ||||
-rw-r--r-- | drivers/mmc/host/tegra_sdio.c | 578 |
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); + |