From: Wei WANG <wei_w...@realsil.com.cn>

Realtek SD/MMC card interface driver is used to access
SD/MMC card, with the help of Realtek card reader adapter driver.

Signed-off-by: Wei WANG <wei_w...@realsil.com.cn>
---
 drivers/mmc/host/Kconfig      |    7 +
 drivers/mmc/host/Makefile     |    2 +
 drivers/mmc/host/rtsx_sdmmc.c |  350 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 drivers/mmc/host/rtsx_sdmmc.c

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index aa131b3..d9942e6 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -612,3 +612,10 @@ config MMC_USHC
 
          Note: These controllers only support SDIO cards and do not
          support MMC or SD memory cards.
+
+config MMC_REALTEK
+       tristate "Realtek SD/MMC Card Interface Driver"
+       depends on REALTEK_CR_CORE
+       help
+         Say Y here to include driver code to support the Realtek
+         SD/MMC card interface.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 8922b06..a4cd15a 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -45,6 +45,8 @@ obj-$(CONFIG_MMC_JZ4740)      += jz4740_mmc.o
 obj-$(CONFIG_MMC_VUB300)       += vub300.o
 obj-$(CONFIG_MMC_USHC)         += ushc.o
 
+obj-$(CONFIG_MMC_REALTEK)      += rtsx_sdmmc.o
+
 obj-$(CONFIG_MMC_SDHCI_PLTFM)          += sdhci-pltfm.o
 obj-$(CONFIG_MMC_SDHCI_CNS3XXX)                += sdhci-cns3xxx.o
 obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX)      += sdhci-esdhc-imx.o
diff --git a/drivers/mmc/host/rtsx_sdmmc.c b/drivers/mmc/host/rtsx_sdmmc.c
new file mode 100644
index 0000000..3b587fc
--- /dev/null
+++ b/drivers/mmc/host/rtsx_sdmmc.c
@@ -0,0 +1,350 @@
+/* Realtek SD/MMC Card Interface driver
+ *
+ * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the
+ * Free Software Foundation; either version 2, 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Wei WANG <wei_w...@realsil.com.cn>
+ *   No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China
+ */
+
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/card.h>
+#include <linux/rtsx_core.h>
+
+#include <asm/unaligned.h>
+
+#define DRV_NAME       "rtsx_sdmmc"
+
+#define MAX_RW_REG_CNT                 1024
+
+#define RTSX_MAX_BLOCK_COUNT           65536
+#define RTSX_MAX_BLOCK_LENGTH          2048
+
+struct realtek_sdmmc {
+       struct rtsx_dev         *dev;
+       struct mmc_host         *mmc;
+       struct mmc_request      *mrq;
+
+       struct mutex            host_mutex;
+
+       int                     eject;
+};
+
+static struct rtsx_device_id realtek_sdmmc_ids[] = {
+       {RTSX_TYPE_SD},
+       {}
+};
+
+MODULE_DEVICE_TABLE(rtsx, realtek_sdmmc_ids);
+
+static void sd_send_cmd_get_rsp(struct realtek_sdmmc *host,
+               struct mmc_command *cmd)
+{
+       cmd->error = rtsx_sdmmc_send_cmd_get_rsp(host->dev, (u8)cmd->opcode,
+                       cmd->arg, mmc_resp_type(cmd), cmd->resp);
+}
+
+static int sd_rw_multi(struct realtek_sdmmc *host, struct mmc_request *mrq)
+{
+       struct mmc_host *mmc = host->mmc;
+       struct mmc_card *card = mmc->card;
+       struct mmc_data *data = mrq->data;
+       int uhs = mmc_sd_card_uhs(card);
+       int read = (data->flags & MMC_DATA_READ) ? 1 : 0;
+
+       return rtsx_sdmmc_rw_multi(host->dev, data->sg, data->blksz,
+                       data->blocks, data->sg_len, read, uhs);
+}
+
+static void sd_normal_rw(struct realtek_sdmmc *host, struct mmc_request *mrq)
+{
+       struct mmc_command *cmd = mrq->cmd;
+       struct mmc_data *data = mrq->data;
+       u8 _cmd[5], *buf;
+
+       _cmd[0] = 0x40 | (u8)cmd->opcode;
+       put_unaligned_be32(cmd->arg, (u32 *)(&_cmd[1]));
+
+       buf = kzalloc(data->blksz, GFP_NOIO);
+       if (!buf) {
+               cmd->error = -ENOMEM;
+               return;
+       }
+
+       if (data->flags & MMC_DATA_READ) {
+               cmd->error = rtsx_sdmmc_read_data(host->dev, _cmd,
+                               (u16)data->blksz, buf, data->blksz, 200);
+               sg_copy_from_buffer(data->sg, data->sg_len, buf, data->blksz);
+       } else {
+               sg_copy_to_buffer(data->sg, data->sg_len, buf, data->blksz);
+               cmd->error = rtsx_sdmmc_write_data(host->dev, _cmd,
+                               (u16)data->blksz, buf, data->blksz, 200);
+       }
+
+       kfree(buf);
+}
+
+static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+       struct mmc_command *cmd = mrq->cmd;
+       struct mmc_data *data = mrq->data;
+       unsigned int data_size = 0;
+
+       if (host->eject) {
+               cmd->error = -ENOMEDIUM;
+               goto finish;
+       }
+
+       mutex_lock(&host->host_mutex);
+       host->mrq = mrq;
+       mutex_unlock(&host->host_mutex);
+
+       if (mrq->data)
+               data_size = data->blocks * data->blksz;
+
+       if (!data_size || mmc_op_multi(cmd->opcode) ||
+                       (cmd->opcode == MMC_READ_SINGLE_BLOCK) ||
+                       (cmd->opcode == MMC_WRITE_BLOCK)) {
+               sd_send_cmd_get_rsp(host, cmd);
+
+               if (!cmd->error && data_size) {
+                       sd_rw_multi(host, mrq);
+
+                       if (mmc_op_multi(cmd->opcode) && mrq->stop)
+                               sd_send_cmd_get_rsp(host, mrq->stop);
+               }
+       } else {
+               sd_normal_rw(host, mrq);
+       }
+
+       if (mrq->data) {
+               if (cmd->error || data->error)
+                       data->bytes_xfered = 0;
+               else
+                       data->bytes_xfered = data->blocks * data->blksz;
+       }
+
+finish:
+       if (cmd->error)
+               pr_debug("cmd->error = %d\n", cmd->error);
+
+       mutex_lock(&host->host_mutex);
+       host->mrq = NULL;
+       mutex_unlock(&host->host_mutex);
+
+       mmc_request_done(mmc, mrq);
+}
+
+static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+       int vpclk = 0, double_clk = 1;
+       u8 ssc_depth;
+
+       rtsx_sdmmc_set_bus_width(host->dev, ios->bus_width);
+       rtsx_sdmmc_set_power_mode(host->dev, ios->power_mode);
+       rtsx_sdmmc_set_timing(host->dev, ios->timing);
+
+       switch (ios->timing) {
+       case MMC_TIMING_UHS_SDR104:
+       case MMC_TIMING_UHS_SDR50:
+               ssc_depth = RTSX_SSC_DEPTH_2M;
+               vpclk = 1;
+               double_clk = 0;
+               break;
+
+       case MMC_TIMING_UHS_DDR50:
+       case MMC_TIMING_UHS_SDR25:
+               ssc_depth = RTSX_SSC_DEPTH_1M;
+               break;
+
+       default:
+               ssc_depth = RTSX_SSC_DEPTH_500K;
+               break;
+       }
+
+       rtsx_switch_clock(host->dev,
+                       ios->clock, ssc_depth, double_clk, vpclk);
+}
+
+static int sdmmc_get_ro(struct mmc_host *mmc)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+
+       return rtsx_sdmmc_get_ro(host->dev);
+}
+
+static int sdmmc_get_cd(struct mmc_host *mmc)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+
+       return rtsx_sdmmc_get_cd(host->dev);
+}
+
+static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+
+       return rtsx_sdmmc_switch_voltage(host->dev, ios->signal_voltage);
+}
+
+static int sdmmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+       struct realtek_sdmmc *host = mmc_priv(mmc);
+
+       return rtsx_sdmmc_execute_tuning(host->dev);
+}
+
+static const struct mmc_host_ops realtek_sdmmc_ops = {
+       .request = sdmmc_request,
+       .set_ios = sdmmc_set_ios,
+       .get_ro = sdmmc_get_ro,
+       .get_cd = sdmmc_get_cd,
+       .start_signal_voltage_switch = sdmmc_switch_voltage,
+       .execute_tuning = sdmmc_execute_tuning,
+};
+
+static void init_extra_caps(struct realtek_sdmmc *host)
+{
+       struct mmc_host *mmc = host->mmc;
+       struct rtsx_dev *sock = host->dev;
+       struct rtsx_adapter *adapter = sock_to_adapter(sock);
+
+       pr_debug("adapter->extra_caps = 0x%x\n", adapter->extra_caps);
+
+       if (adapter->extra_caps & EXTRA_CAPS_SD_SDR50)
+               mmc->caps |= MMC_CAP_UHS_SDR50;
+       if (adapter->extra_caps & EXTRA_CAPS_SD_SDR104)
+               mmc->caps |= MMC_CAP_UHS_SDR104;
+       if (adapter->extra_caps & EXTRA_CAPS_SD_DDR50)
+               mmc->caps |= MMC_CAP_UHS_DDR50;
+       if (adapter->extra_caps & EXTRA_CAPS_MMC_HSDDR)
+               mmc->caps |= MMC_CAP_1_8V_DDR;
+       if (adapter->extra_caps & EXTRA_CAPS_MMC_8BIT)
+               mmc->caps |= MMC_CAP_8_BIT_DATA;
+}
+
+static void realtek_init_host(struct realtek_sdmmc *host)
+{
+       struct mmc_host *mmc = host->mmc;
+
+       mmc->f_min = 250000;
+       mmc->f_max = 208000000;
+       mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
+       mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED |
+               MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST |
+               MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+               MMC_CAP_MAX_CURRENT_200 | MMC_CAP_MAX_CURRENT_400 |
+               MMC_CAP_MAX_CURRENT_600 | MMC_CAP_MAX_CURRENT_800;
+       mmc->ops = &realtek_sdmmc_ops;
+
+       init_extra_caps(host);
+
+       mmc->max_segs = 256;
+       mmc->max_blk_size = RTSX_MAX_BLOCK_LENGTH;
+       mmc->max_blk_count = RTSX_MAX_BLOCK_COUNT;
+       mmc->max_seg_size = mmc->max_blk_size * mmc->max_blk_count;
+       mmc->max_req_size = mmc->max_seg_size;
+}
+
+static int realtek_sdmmc_probe(struct rtsx_dev *sock)
+{
+       struct mmc_host *mmc;
+       struct realtek_sdmmc *host;
+
+       pr_info(DRV_NAME ": Realtek SDMMC controller found\n");
+
+       mmc = mmc_alloc_host(sizeof(struct realtek_sdmmc), &sock->dev);
+       if (!mmc)
+               return -ENOMEM;
+
+       host = mmc_priv(mmc);
+       rtsx_set_drvdata(sock, mmc);
+       host->dev = sock;
+       host->mmc = mmc;
+
+       mutex_init(&host->host_mutex);
+
+       realtek_init_host(host);
+
+       mmc_add_host(mmc);
+
+       return 0;
+}
+
+static void __devexit realtek_sdmmc_remove(struct rtsx_dev *sock)
+{
+       struct mmc_host *mmc = rtsx_get_drvdata(sock);
+       struct realtek_sdmmc *host;
+
+       host = mmc_priv(mmc);
+       host->eject = 1;
+
+       mutex_lock(&host->host_mutex);
+       if (host->mrq) {
+               pr_debug("%s: Controller removed during transfer\n",
+                               mmc_hostname(mmc));
+
+               rtsx_complete_unfinished_transfer(sock);
+
+               host->mrq->cmd->error = -ENOMEDIUM;
+               if (host->mrq->stop)
+                       host->mrq->stop->error = -ENOMEDIUM;
+               mmc_request_done(mmc, host->mrq);
+       }
+       mutex_unlock(&host->host_mutex);
+
+       mmc_remove_host(mmc);
+       mmc_free_host(mmc);
+
+       pr_info(DRV_NAME
+               ": Realtek SDMMC controller has been removed\n");
+}
+
+static struct rtsx_driver realtek_sdmmc_driver = {
+       .driver = {
+               .name = DRV_NAME,
+               .owner = THIS_MODULE,
+       },
+       .id_table = realtek_sdmmc_ids,
+       .probe = realtek_sdmmc_probe,
+       .remove = realtek_sdmmc_remove,
+};
+
+static int __init realtek_sdmmc_drv_init(void)
+{
+       pr_info(DRV_NAME ": Realtek SD/MMC Card Reader driver\n");
+
+       return rtsx_register_driver(&realtek_sdmmc_driver);
+}
+
+static void __exit realtek_sdmmc_drv_exit(void)
+{
+       rtsx_unregister_driver(&realtek_sdmmc_driver);
+}
+
+module_init(realtek_sdmmc_drv_init);
+module_exit(realtek_sdmmc_drv_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Realtek Corp.");
+MODULE_DESCRIPTION("Realtek SD/MMC Card Interface Driver");
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to