/* Realtek PCI-Express SD/MMC Card Interface driver
*
* Copyright(c) 2009-2013 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@realsil.com.cn>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include <linux/mmc/card.h>
#include <linux/mfd/rtsx_pci.h>
#include <asm/unaligned.h>
struct realtek_pci_sdmmc {
struct platform_device *pdev;
struct rtsx_pcr *pcr;
struct mmc_host *mmc;
struct mmc_request *mrq;
struct workqueue_struct *workq;
#define SDMMC_WORKQ_NAME "rtsx_pci_sdmmc_workq"
struct work_struct work;
struct mutex host_mutex;
u8 ssc_depth;
unsigned int clock;
bool vpclk;
bool double_clk;
bool eject;
bool initial_mode;
int power_state;
#define SDMMC_POWER_ON 1
#define SDMMC_POWER_OFF 0
unsigned int sg_count;
s32 cookie;
unsigned int cookie_sg_count;
bool using_cookie;
};
static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host)
{
return &(host->pdev->dev);
}
static inline void sd_clear_error(struct realtek_pci_sdmmc *host)
{
rtsx_pci_write_register(host->pcr, CARD_STOP,
SD_STOP | SD_CLR_ERR, SD_STOP | SD_CLR_ERR);
}
#ifdef DEBUG
static void sd_print_debug_regs(struct realtek_pci_sdmmc *host)
{
struct rtsx_pcr *pcr = host->pcr;
u16 i;
u8 *ptr;
/* Print SD host internal registers */
rtsx_pci_init_cmd(pcr);
for (i = 0xFDA0; i <= 0xFDAE; i++)
rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0);
for (i = 0xFD52; i <= 0xFD69; i++)
rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0);
rtsx_pci_send_cmd(pcr, 100);
ptr = rtsx_pci_get_cmd_data(pcr);
for (i = 0xFDA0; i <= 0xFDAE; i++)
dev_dbg(sdmmc_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
for (i = 0xFD52; i <= 0xFD69; i++)
dev_dbg(sdmmc_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
}
#else
#define sd_print_debug_regs(host)
#endif /* DEBUG */
/*
* sd_pre_dma_transfer - do dma_map_sg() or using cookie
*
* @pre: if called in pre_req()
* return:
* 0 - do dma_map_sg()
* 1 - using cookie
*/
static int sd_pre_dma_transfer(struct realtek_pci_sdmmc *host,
struct mmc_data *data, bool pre)
{
struct rtsx_pcr *pcr = host->pcr;
int read = data->flags & MMC_DATA_READ;
int count = 0;
int using_cookie = 0;
if (!pre && data->host_cookie && data->host_cookie != host->cookie) {
dev_err(sdmmc_dev(host),
"error: data->host_cookie = %d, host->cookie = %d\n",
data->host_cookie, host->cookie);
data->host_cookie = 0;
}
if (pre || data->host_cookie != host->cookie) {
count = rtsx_pci_dma_map_sg(pcr, data->sg, data->sg_len, read);
} else {
count = host->cookie_sg_count;
using_cookie = 1;
}
if (pre) {
host->cookie_sg_count = count;
if (++host->cookie < 0)
host->cookie = 1;
data->host_cookie = host->cookie;
} else {
host->sg_count = count;
}
return using_cookie;
}
static void sdmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
bool is_first_req)
{
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
struct mmc_data *data = mrq->data;
if (data->host_cookie) {
dev_err(sdmmc_dev(host),
"error: reset data->host_cookie = %d\n",
data->host_cookie);
data->host_cookie = 0;
}
sd_pre_dma_transfer(host, data, true);
dev_dbg(sdmmc_dev(host), "pre dma sg: %d\n", host->cookie_sg_count);
}
static void sdmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
int err)
{
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
struct rtsx_pcr *pcr = host->pcr;
struct mmc_data *data = mrq->data;
int read = data->flags & MMC_DATA_READ;
rtsx_pci_dma_unmap_sg(pcr, data->sg, data->sg_len, read);
data->host_cookie = 0;
}
static int sd_read_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt,
u8 *buf, int buf_len, int timeout)
{
struct rtsx_pcr *pcr = host->pcr;
int err, i;
u8 trans_mode;
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__, cmd[0] - 0x40);
if (!buf)
buf_len = 0;
if ((cmd[0] & 0x3F) == MMC_SEND_TUNING_BLOCK)
trans_mode = SD_TM_AUTO_TUNING;
else
trans_mode = SD_TM_NORMAL_READ;
rtsx_pci_init_cmd(pcr);
for (i = 0; i < 5; i++)
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD0 + i, 0xFF, cmd[i]);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_H,
0xFF, (u8)(byte_cnt >> 8));
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG2, 0xFF,
SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6);
if (trans_mode != SD_TM_AUTO_TUNING)
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER,
0xFF, trans_mode | SD_TRANSFER_START);
rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER,
SD_TRANSFER_END, SD_TRANSFER_END);
err = rtsx_pci_send_cmd(pcr, timeout);
if (err < 0) {
sd_print_debug_regs(host);
dev_dbg(sdmmc_dev(host),
"rtsx_pci_send_cmd fail (err = %d)\n", err);
return err;
}
if (buf && buf_len) {
err = rtsx_pci_read_ppbuf(pcr, buf, buf_len);
if (err < 0) {
dev_dbg(sdmmc_dev(host),
"rtsx_pci_read_ppbuf fail (err = %d)\n", err);
return err;
}
}
return 0;
}
static int sd_write_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt,
u8 *buf, int buf_len, int timeout)
{
struct rtsx_pcr *pcr = host->pcr;
int err, i;
u8 trans_mode;
if (!buf)
buf_len = 0;
if (buf && buf_len) {
err = rtsx_pci_write_ppbuf(pcr, buf, buf_len);
if (err < 0) {
dev_dbg(sdmmc_dev(host),
"rtsx_pci_write_ppbuf fail (err = %d)\n", err);
return err;
}
}
trans_mode = cmd ? SD_TM_AUTO_WRITE_2 : SD_TM_AUTO_WRITE_3;
rtsx_pci_init_cmd(pcr);
if (cmd) {
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d\n", __func__,
cmd[0] - 0x40);
for (i = 0; i < 5; i++)
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
SD_CMD0 + i, 0xFF, cmd[i]);
}
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_H,
0xFF, (u8)(byte_cnt >> 8));
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG2, 0xFF,
SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6);
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER, 0xFF,
trans_mode | SD_TRANSFER_START);
rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER,
SD_TRANSFER_END, SD_TRANSFER_END);
err = rtsx_pci_send_cmd(pcr, timeout);
if (err < 0) {
sd_print_debug_regs(host);
dev_dbg(sdmmc_dev(host),
"rtsx_pci_send_cmd fail (err = %d)\n", err);
return err;
}
return 0;
}
static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host,
struct mmc_command *cmd)
{
struct rtsx_pcr *pcr = host->pcr;
u8 cmd_idx = (u8)cmd->opcode;
u32 arg = cmd->arg;
int err = 0;
int timeout = 100;
int i;
u8 *ptr;
int stat_idx = 0;
u8 rsp_type;
int rsp_len = 5;
bool clock_toggled = false;
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n",
__func__, cmd_idx, arg);
/* Response type:
* R0
* R1, R5, R