From: Wei WANG <wei_wang@xxxxxxxxxxxxxx> 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_wang@xxxxxxxxxxxxxx> --- 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..a90dd79a --- /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_wang@xxxxxxxxxxxxxx> + * 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) + dev_dbg(&(host->dev->dev), "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); + + dev_dbg(&(sock->dev), "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->max_current_330 = 400; + mmc->max_current_180 = 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) { + dev_dbg(&(sock->dev), + "%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) +{ + 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-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html