mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
4dad599b8b
The rtsx USB parent device, has logic to detect when a card is inserted into the card slot. Although, the logic can't detect when a card is removed. This makes things a bit tricky, which is why the current method is simply to turn on MMC_CAP_NEEDS_POLL during probe. Using MMC_CAP_NEEDS_POLL means lots of energy being wasted, as the mmc host becomes runtime resumed frequently by the mmc core, while it polls for new cards being inserted. To address this problem, let's start relying on that the rtsx USB driver runtime resumes its child device, which is the rtsx_usb_sdmmc device, when it detects that a new card being inserted. This means dropping MMC_CAP_NEEDS_POLL from being set during probe. Instead let's implement a ->runtime_resume() callback to schedule a detect work and to set MMC_CAP_NEEDS_POLL. In this way, polling is enabled as long as there is card inserted, thus we can rely on the mmc core to detect also when the card becomes removed. Furthermore, to avoid polling forever after a card has been removed, let's implement a ->runtime_suspend() callback and make it clear MMC_CAP_NEEDS_POLL. Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> Tested-by: Kai-Heng Feng <kai.heng.feng@canonical.com> Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
1481 lines
36 KiB
C
1481 lines
36 KiB
C
/* Realtek USB 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 version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* 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:
|
|
* Roger Tseng <rogerable@realtek.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/sd.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <linux/rtsx_usb.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
#if defined(CONFIG_LEDS_CLASS) || (defined(CONFIG_LEDS_CLASS_MODULE) && \
|
|
defined(CONFIG_MMC_REALTEK_USB_MODULE))
|
|
#include <linux/leds.h>
|
|
#include <linux/workqueue.h>
|
|
#define RTSX_USB_USE_LEDS_CLASS
|
|
#endif
|
|
|
|
struct rtsx_usb_sdmmc {
|
|
struct platform_device *pdev;
|
|
struct rtsx_ucr *ucr;
|
|
struct mmc_host *mmc;
|
|
struct mmc_request *mrq;
|
|
|
|
struct mutex host_mutex;
|
|
|
|
u8 ssc_depth;
|
|
unsigned int clock;
|
|
bool vpclk;
|
|
bool double_clk;
|
|
bool host_removal;
|
|
bool card_exist;
|
|
bool initial_mode;
|
|
bool ddr_mode;
|
|
|
|
unsigned char power_mode;
|
|
|
|
#ifdef RTSX_USB_USE_LEDS_CLASS
|
|
struct led_classdev led;
|
|
char led_name[32];
|
|
struct work_struct led_work;
|
|
#endif
|
|
};
|
|
|
|
static inline struct device *sdmmc_dev(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
return &(host->pdev->dev);
|
|
}
|
|
|
|
static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
rtsx_usb_ep0_write_register(ucr, CARD_STOP,
|
|
SD_STOP | SD_CLR_ERR,
|
|
SD_STOP | SD_CLR_ERR);
|
|
|
|
rtsx_usb_clear_dma_err(ucr);
|
|
rtsx_usb_clear_fsm_err(ucr);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
u8 val = 0;
|
|
|
|
rtsx_usb_ep0_read_register(ucr, SD_STAT1, &val);
|
|
dev_dbg(sdmmc_dev(host), "SD_STAT1: 0x%x\n", val);
|
|
rtsx_usb_ep0_read_register(ucr, SD_STAT2, &val);
|
|
dev_dbg(sdmmc_dev(host), "SD_STAT2: 0x%x\n", val);
|
|
rtsx_usb_ep0_read_register(ucr, SD_BUS_STAT, &val);
|
|
dev_dbg(sdmmc_dev(host), "SD_BUS_STAT: 0x%x\n", val);
|
|
}
|
|
#else
|
|
#define sd_print_debug_regs(host)
|
|
#endif /* DEBUG */
|
|
|
|
static int sd_read_data(struct rtsx_usb_sdmmc *host, struct mmc_command *cmd,
|
|
u16 byte_cnt, u8 *buf, int buf_len, int timeout)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
u8 trans_mode;
|
|
|
|
if (!buf)
|
|
buf_len = 0;
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
if (cmd != NULL) {
|
|
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__
|
|
, cmd->opcode);
|
|
if (cmd->opcode == MMC_SEND_TUNING_BLOCK)
|
|
trans_mode = SD_TM_AUTO_TUNING;
|
|
else
|
|
trans_mode = SD_TM_NORMAL_READ;
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD0, 0xFF, (u8)(cmd->opcode) | 0x40);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD1, 0xFF, (u8)(cmd->arg >> 24));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD2, 0xFF, (u8)(cmd->arg >> 16));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD3, 0xFF, (u8)(cmd->arg >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD4, 0xFF, (u8)cmd->arg);
|
|
} else {
|
|
trans_mode = SD_TM_AUTO_READ_3;
|
|
}
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H,
|
|
0xFF, (u8)(byte_cnt >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
|
|
|
|
rtsx_usb_add_cmd(ucr, 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_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER,
|
|
0xFF, trans_mode | SD_TRANSFER_START);
|
|
rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
|
|
SD_TRANSFER_END, SD_TRANSFER_END);
|
|
|
|
if (cmd != NULL) {
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD1, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD2, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD3, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD4, 0, 0);
|
|
}
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_CR, timeout);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_send_cmd failed (err = %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = rtsx_usb_get_rsp(ucr, !cmd ? 1 : 5, timeout);
|
|
if (err || (ucr->rsp_buf[0] & SD_TRANSFER_ERR)) {
|
|
sd_print_debug_regs(host);
|
|
|
|
if (!err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"Transfer failed (SD_TRANSFER = %02x)\n",
|
|
ucr->rsp_buf[0]);
|
|
err = -EIO;
|
|
} else {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_get_rsp failed (err = %d)\n", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
if (cmd != NULL) {
|
|
cmd->resp[0] = get_unaligned_be32(ucr->rsp_buf + 1);
|
|
dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
|
|
cmd->resp[0]);
|
|
}
|
|
|
|
if (buf && buf_len) {
|
|
/* 2-byte aligned part */
|
|
err = rtsx_usb_read_ppbuf(ucr, buf, byte_cnt - (byte_cnt % 2));
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_read_ppbuf failed (err = %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* unaligned byte */
|
|
if (byte_cnt % 2)
|
|
return rtsx_usb_read_register(ucr,
|
|
PPBUF_BASE2 + byte_cnt,
|
|
buf + byte_cnt - 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sd_write_data(struct rtsx_usb_sdmmc *host, struct mmc_command *cmd,
|
|
u16 byte_cnt, u8 *buf, int buf_len, int timeout)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
u8 trans_mode;
|
|
|
|
if (!buf)
|
|
buf_len = 0;
|
|
|
|
if (buf && buf_len) {
|
|
err = rtsx_usb_write_ppbuf(ucr, buf, buf_len);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_write_ppbuf failed (err = %d)\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
trans_mode = (cmd != NULL) ? SD_TM_AUTO_WRITE_2 : SD_TM_AUTO_WRITE_3;
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
if (cmd != NULL) {
|
|
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__,
|
|
cmd->opcode);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD0, 0xFF, (u8)(cmd->opcode) | 0x40);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD1, 0xFF, (u8)(cmd->arg >> 24));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD2, 0xFF, (u8)(cmd->arg >> 16));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD3, 0xFF, (u8)(cmd->arg >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CMD4, 0xFF, (u8)cmd->arg);
|
|
}
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H,
|
|
0xFF, (u8)(byte_cnt >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0);
|
|
|
|
rtsx_usb_add_cmd(ucr, 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_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER, 0xFF,
|
|
trans_mode | SD_TRANSFER_START);
|
|
rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
|
|
SD_TRANSFER_END, SD_TRANSFER_END);
|
|
|
|
if (cmd != NULL) {
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD1, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD2, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD3, 0, 0);
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_CMD4, 0, 0);
|
|
}
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_CR, timeout);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_send_cmd failed (err = %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = rtsx_usb_get_rsp(ucr, !cmd ? 1 : 5, timeout);
|
|
if (err) {
|
|
sd_print_debug_regs(host);
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_get_rsp failed (err = %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
if (cmd != NULL) {
|
|
cmd->resp[0] = get_unaligned_be32(ucr->rsp_buf + 1);
|
|
dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
|
|
cmd->resp[0]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sd_send_cmd_get_rsp(struct rtsx_usb_sdmmc *host,
|
|
struct mmc_command *cmd)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
u8 cmd_idx = (u8)cmd->opcode;
|
|
u32 arg = cmd->arg;
|
|
int err = 0;
|
|
int timeout = 100;
|
|
int i;
|
|
u8 *ptr;
|
|
int stat_idx = 0;
|
|
int len = 2;
|
|
u8 rsp_type;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n",
|
|
__func__, cmd_idx, arg);
|
|
|
|
/* Response type:
|
|
* R0
|
|
* R1, R5, R6, R7
|
|
* R1b
|
|
* R2
|
|
* R3, R4
|
|
*/
|
|
switch (mmc_resp_type(cmd)) {
|
|
case MMC_RSP_NONE:
|
|
rsp_type = SD_RSP_TYPE_R0;
|
|
break;
|
|
case MMC_RSP_R1:
|
|
rsp_type = SD_RSP_TYPE_R1;
|
|
break;
|
|
case MMC_RSP_R1_NO_CRC:
|
|
rsp_type = SD_RSP_TYPE_R1 | SD_NO_CHECK_CRC7;
|
|
break;
|
|
case MMC_RSP_R1B:
|
|
rsp_type = SD_RSP_TYPE_R1b;
|
|
break;
|
|
case MMC_RSP_R2:
|
|
rsp_type = SD_RSP_TYPE_R2;
|
|
break;
|
|
case MMC_RSP_R3:
|
|
rsp_type = SD_RSP_TYPE_R3;
|
|
break;
|
|
default:
|
|
dev_dbg(sdmmc_dev(host), "cmd->flag is not valid\n");
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (rsp_type == SD_RSP_TYPE_R1b)
|
|
timeout = cmd->busy_timeout ? cmd->busy_timeout : 3000;
|
|
|
|
if (cmd->opcode == SD_SWITCH_VOLTAGE) {
|
|
err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
|
|
SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
|
|
SD_CLK_TOGGLE_EN);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD0, 0xFF, 0x40 | cmd_idx);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD1, 0xFF, (u8)(arg >> 24));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD2, 0xFF, (u8)(arg >> 16));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD3, 0xFF, (u8)(arg >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CMD4, 0xFF, (u8)arg);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF, rsp_type);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
|
|
0x01, PINGPONG_BUFFER);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER,
|
|
0xFF, SD_TM_CMD_RSP | SD_TRANSFER_START);
|
|
rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
|
|
SD_TRANSFER_END | SD_STAT_IDLE,
|
|
SD_TRANSFER_END | SD_STAT_IDLE);
|
|
|
|
if (rsp_type == SD_RSP_TYPE_R2) {
|
|
/* Read data from ping-pong buffer */
|
|
for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++)
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, (u16)i, 0, 0);
|
|
stat_idx = 16;
|
|
} else if (rsp_type != SD_RSP_TYPE_R0) {
|
|
/* Read data from SD_CMDx registers */
|
|
for (i = SD_CMD0; i <= SD_CMD4; i++)
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, (u16)i, 0, 0);
|
|
stat_idx = 5;
|
|
}
|
|
len += stat_idx;
|
|
|
|
rtsx_usb_add_cmd(ucr, READ_REG_CMD, SD_STAT1, 0, 0);
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_CR, 100);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_send_cmd error (err = %d)\n", err);
|
|
goto out;
|
|
}
|
|
|
|
err = rtsx_usb_get_rsp(ucr, len, timeout);
|
|
if (err || (ucr->rsp_buf[0] & SD_TRANSFER_ERR)) {
|
|
sd_print_debug_regs(host);
|
|
sd_clear_error(host);
|
|
|
|
if (!err) {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"Transfer failed (SD_TRANSFER = %02x)\n",
|
|
ucr->rsp_buf[0]);
|
|
err = -EIO;
|
|
} else {
|
|
dev_dbg(sdmmc_dev(host),
|
|
"rtsx_usb_get_rsp failed (err = %d)\n", err);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (rsp_type == SD_RSP_TYPE_R0) {
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
/* Skip result of CHECK_REG_CMD */
|
|
ptr = ucr->rsp_buf + 1;
|
|
|
|
/* Check (Start,Transmission) bit of Response */
|
|
if ((ptr[0] & 0xC0) != 0) {
|
|
err = -EILSEQ;
|
|
dev_dbg(sdmmc_dev(host), "Invalid response bit\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Check CRC7 */
|
|
if (!(rsp_type & SD_NO_CHECK_CRC7)) {
|
|
if (ptr[stat_idx] & SD_CRC7_ERR) {
|
|
err = -EILSEQ;
|
|
dev_dbg(sdmmc_dev(host), "CRC7 error\n");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (rsp_type == SD_RSP_TYPE_R2) {
|
|
/*
|
|
* The controller offloads the last byte {CRC-7, end bit 1'b1}
|
|
* of response type R2. Assign dummy CRC, 0, and end bit to the
|
|
* byte(ptr[16], goes into the LSB of resp[3] later).
|
|
*/
|
|
ptr[16] = 1;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
cmd->resp[i] = get_unaligned_be32(ptr + 1 + i * 4);
|
|
dev_dbg(sdmmc_dev(host), "cmd->resp[%d] = 0x%08x\n",
|
|
i, cmd->resp[i]);
|
|
}
|
|
} else {
|
|
cmd->resp[0] = get_unaligned_be32(ptr + 1);
|
|
dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n",
|
|
cmd->resp[0]);
|
|
}
|
|
|
|
out:
|
|
cmd->error = err;
|
|
}
|
|
|
|
static int sd_rw_multi(struct rtsx_usb_sdmmc *host, struct mmc_request *mrq)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
struct mmc_data *data = mrq->data;
|
|
int read = (data->flags & MMC_DATA_READ) ? 1 : 0;
|
|
u8 cfg2, trans_mode;
|
|
int err;
|
|
u8 flag;
|
|
size_t data_len = data->blksz * data->blocks;
|
|
unsigned int pipe;
|
|
|
|
if (read) {
|
|
dev_dbg(sdmmc_dev(host), "%s: read %zu bytes\n",
|
|
__func__, data_len);
|
|
cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 |
|
|
SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0;
|
|
trans_mode = SD_TM_AUTO_READ_3;
|
|
} else {
|
|
dev_dbg(sdmmc_dev(host), "%s: write %zu bytes\n",
|
|
__func__, data_len);
|
|
cfg2 = SD_NO_CALCULATE_CRC7 | SD_CHECK_CRC16 |
|
|
SD_NO_WAIT_BUSY_END | SD_NO_CHECK_CRC7 | SD_RSP_LEN_0;
|
|
trans_mode = SD_TM_AUTO_WRITE_3;
|
|
}
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, 0x00);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BYTE_CNT_H, 0xFF, 0x02);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_L,
|
|
0xFF, (u8)data->blocks);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BLOCK_CNT_H,
|
|
0xFF, (u8)(data->blocks >> 8));
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
|
|
0x01, RING_BUFFER);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC3,
|
|
0xFF, (u8)(data_len >> 24));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC2,
|
|
0xFF, (u8)(data_len >> 16));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC1,
|
|
0xFF, (u8)(data_len >> 8));
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC0,
|
|
0xFF, (u8)data_len);
|
|
if (read) {
|
|
flag = MODE_CDIR;
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL,
|
|
0x03 | DMA_PACK_SIZE_MASK,
|
|
DMA_DIR_FROM_CARD | DMA_EN | DMA_512);
|
|
} else {
|
|
flag = MODE_CDOR;
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL,
|
|
0x03 | DMA_PACK_SIZE_MASK,
|
|
DMA_DIR_TO_CARD | DMA_EN | DMA_512);
|
|
}
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG2, 0xFF, cfg2);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_TRANSFER, 0xFF,
|
|
trans_mode | SD_TRANSFER_START);
|
|
rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, SD_TRANSFER,
|
|
SD_TRANSFER_END, SD_TRANSFER_END);
|
|
|
|
err = rtsx_usb_send_cmd(ucr, flag, 100);
|
|
if (err)
|
|
return err;
|
|
|
|
if (read)
|
|
pipe = usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN);
|
|
else
|
|
pipe = usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT);
|
|
|
|
err = rtsx_usb_transfer_data(ucr, pipe, data->sg, data_len,
|
|
data->sg_len, NULL, 10000);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host), "rtsx_usb_transfer_data error %d\n"
|
|
, err);
|
|
sd_clear_error(host);
|
|
return err;
|
|
}
|
|
|
|
return rtsx_usb_get_rsp(ucr, 1, 2000);
|
|
}
|
|
|
|
static inline void sd_enable_initial_mode(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
rtsx_usb_write_register(host->ucr, SD_CFG1,
|
|
SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_128);
|
|
}
|
|
|
|
static inline void sd_disable_initial_mode(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
rtsx_usb_write_register(host->ucr, SD_CFG1,
|
|
SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_0);
|
|
}
|
|
|
|
static void sd_normal_rw(struct rtsx_usb_sdmmc *host,
|
|
struct mmc_request *mrq)
|
|
{
|
|
struct mmc_command *cmd = mrq->cmd;
|
|
struct mmc_data *data = mrq->data;
|
|
u8 *buf;
|
|
|
|
buf = kzalloc(data->blksz, GFP_NOIO);
|
|
if (!buf) {
|
|
cmd->error = -ENOMEM;
|
|
return;
|
|
}
|
|
|
|
if (data->flags & MMC_DATA_READ) {
|
|
if (host->initial_mode)
|
|
sd_disable_initial_mode(host);
|
|
|
|
cmd->error = sd_read_data(host, cmd, (u16)data->blksz, buf,
|
|
data->blksz, 200);
|
|
|
|
if (host->initial_mode)
|
|
sd_enable_initial_mode(host);
|
|
|
|
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 = sd_write_data(host, cmd, (u16)data->blksz, buf,
|
|
data->blksz, 200);
|
|
}
|
|
|
|
kfree(buf);
|
|
}
|
|
|
|
static int sd_change_phase(struct rtsx_usb_sdmmc *host, u8 sample_point, int tx)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s: %s sample_point = %d\n",
|
|
__func__, tx ? "TX" : "RX", sample_point);
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, CLK_CHANGE);
|
|
|
|
if (tx)
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL,
|
|
0x0F, sample_point);
|
|
else
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK1_CTL,
|
|
0x0F, sample_point);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, PHASE_NOT_RESET, 0);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL,
|
|
PHASE_NOT_RESET, PHASE_NOT_RESET);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, 0);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1, SD_ASYNC_FIFO_RST, 0);
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline u32 get_phase_point(u32 phase_map, unsigned int idx)
|
|
{
|
|
idx &= MAX_PHASE;
|
|
return phase_map & (1 << idx);
|
|
}
|
|
|
|
static int get_phase_len(u32 phase_map, unsigned int idx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_PHASE + 1; i++) {
|
|
if (get_phase_point(phase_map, idx + i) == 0)
|
|
return i;
|
|
}
|
|
return MAX_PHASE + 1;
|
|
}
|
|
|
|
static u8 sd_search_final_phase(struct rtsx_usb_sdmmc *host, u32 phase_map)
|
|
{
|
|
int start = 0, len = 0;
|
|
int start_final = 0, len_final = 0;
|
|
u8 final_phase = 0xFF;
|
|
|
|
if (phase_map == 0) {
|
|
dev_dbg(sdmmc_dev(host), "Phase: [map:%x]\n", phase_map);
|
|
return final_phase;
|
|
}
|
|
|
|
while (start < MAX_PHASE + 1) {
|
|
len = get_phase_len(phase_map, start);
|
|
if (len_final < len) {
|
|
start_final = start;
|
|
len_final = len;
|
|
}
|
|
start += len ? len : 1;
|
|
}
|
|
|
|
final_phase = (start_final + len_final / 2) & MAX_PHASE;
|
|
dev_dbg(sdmmc_dev(host), "Phase: [map:%x] [maxlen:%d] [final:%d]\n",
|
|
phase_map, len_final, final_phase);
|
|
|
|
return final_phase;
|
|
}
|
|
|
|
static void sd_wait_data_idle(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
int err, i;
|
|
u8 val = 0;
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
err = rtsx_usb_ep0_read_register(host->ucr,
|
|
SD_DATA_STATE, &val);
|
|
if (val & SD_DATA_IDLE)
|
|
return;
|
|
|
|
usleep_range(100, 1000);
|
|
}
|
|
}
|
|
|
|
static int sd_tuning_rx_cmd(struct rtsx_usb_sdmmc *host,
|
|
u8 opcode, u8 sample_point)
|
|
{
|
|
int err;
|
|
struct mmc_command cmd = {};
|
|
|
|
err = sd_change_phase(host, sample_point, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
cmd.opcode = MMC_SEND_TUNING_BLOCK;
|
|
err = sd_read_data(host, &cmd, 0x40, NULL, 0, 100);
|
|
if (err) {
|
|
/* Wait till SD DATA IDLE */
|
|
sd_wait_data_idle(host);
|
|
sd_clear_error(host);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sd_tuning_phase(struct rtsx_usb_sdmmc *host,
|
|
u8 opcode, u16 *phase_map)
|
|
{
|
|
int err, i;
|
|
u16 raw_phase_map = 0;
|
|
|
|
for (i = MAX_PHASE; i >= 0; i--) {
|
|
err = sd_tuning_rx_cmd(host, opcode, (u8)i);
|
|
if (!err)
|
|
raw_phase_map |= 1 << i;
|
|
}
|
|
|
|
if (phase_map)
|
|
*phase_map = raw_phase_map;
|
|
}
|
|
|
|
static int sd_tuning_rx(struct rtsx_usb_sdmmc *host, u8 opcode)
|
|
{
|
|
int err, i;
|
|
u16 raw_phase_map[RX_TUNING_CNT] = {0}, phase_map;
|
|
u8 final_phase;
|
|
|
|
/* setting fixed default TX phase */
|
|
err = sd_change_phase(host, 0x01, 1);
|
|
if (err) {
|
|
dev_dbg(sdmmc_dev(host), "TX phase setting failed\n");
|
|
return err;
|
|
}
|
|
|
|
/* tuning RX phase */
|
|
for (i = 0; i < RX_TUNING_CNT; i++) {
|
|
sd_tuning_phase(host, opcode, &(raw_phase_map[i]));
|
|
|
|
if (raw_phase_map[i] == 0)
|
|
break;
|
|
}
|
|
|
|
phase_map = 0xFFFF;
|
|
for (i = 0; i < RX_TUNING_CNT; i++) {
|
|
dev_dbg(sdmmc_dev(host), "RX raw_phase_map[%d] = 0x%04x\n",
|
|
i, raw_phase_map[i]);
|
|
phase_map &= raw_phase_map[i];
|
|
}
|
|
dev_dbg(sdmmc_dev(host), "RX phase_map = 0x%04x\n", phase_map);
|
|
|
|
if (phase_map) {
|
|
final_phase = sd_search_final_phase(host, phase_map);
|
|
if (final_phase == 0xFF)
|
|
return -EINVAL;
|
|
|
|
err = sd_change_phase(host, final_phase, 0);
|
|
if (err)
|
|
return err;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_get_ro(struct mmc_host *mmc)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
u16 val;
|
|
|
|
if (host->host_removal)
|
|
return -ENOMEDIUM;
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
/* Check SD card detect */
|
|
err = rtsx_usb_get_card_status(ucr, &val);
|
|
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
|
|
/* Treat failed detection as non-ro */
|
|
if (err)
|
|
return 0;
|
|
|
|
if (val & SD_WP)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_get_cd(struct mmc_host *mmc)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
u16 val;
|
|
|
|
if (host->host_removal)
|
|
return -ENOMEDIUM;
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
/* Check SD card detect */
|
|
err = rtsx_usb_get_card_status(ucr, &val);
|
|
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
/* Treat failed detection as non-exist */
|
|
if (err)
|
|
goto no_card;
|
|
|
|
if (val & SD_CD) {
|
|
host->card_exist = true;
|
|
return 1;
|
|
}
|
|
|
|
no_card:
|
|
host->card_exist = false;
|
|
return 0;
|
|
}
|
|
|
|
static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
struct mmc_command *cmd = mrq->cmd;
|
|
struct mmc_data *data = mrq->data;
|
|
unsigned int data_size = 0;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
|
|
|
|
if (host->host_removal) {
|
|
cmd->error = -ENOMEDIUM;
|
|
goto finish;
|
|
}
|
|
|
|
if ((!host->card_exist)) {
|
|
cmd->error = -ENOMEDIUM;
|
|
goto finish_detect_card;
|
|
}
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
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) {
|
|
sd_send_cmd_get_rsp(host, cmd);
|
|
} else if ((!(data_size % 512) && cmd->opcode != MMC_SEND_EXT_CSD) ||
|
|
mmc_op_multi(cmd->opcode)) {
|
|
sd_send_cmd_get_rsp(host, cmd);
|
|
|
|
if (!cmd->error) {
|
|
sd_rw_multi(host, mrq);
|
|
|
|
if (mmc_op_multi(cmd->opcode) && mrq->stop) {
|
|
sd_send_cmd_get_rsp(host, mrq->stop);
|
|
rtsx_usb_write_register(ucr, MC_FIFO_CTL,
|
|
FIFO_FLUSH, FIFO_FLUSH);
|
|
}
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
finish_detect_card:
|
|
if (cmd->error) {
|
|
/*
|
|
* detect card when fail to update card existence state and
|
|
* speed up card removal when retry
|
|
*/
|
|
sdmmc_get_cd(mmc);
|
|
dev_dbg(sdmmc_dev(host), "cmd->error = %d\n", cmd->error);
|
|
}
|
|
|
|
finish:
|
|
mutex_lock(&host->host_mutex);
|
|
host->mrq = NULL;
|
|
mutex_unlock(&host->host_mutex);
|
|
|
|
mmc_request_done(mmc, mrq);
|
|
}
|
|
|
|
static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
|
|
unsigned char bus_width)
|
|
{
|
|
int err = 0;
|
|
static const u8 width[] = {
|
|
[MMC_BUS_WIDTH_1] = SD_BUS_WIDTH_1BIT,
|
|
[MMC_BUS_WIDTH_4] = SD_BUS_WIDTH_4BIT,
|
|
[MMC_BUS_WIDTH_8] = SD_BUS_WIDTH_8BIT,
|
|
};
|
|
|
|
if (bus_width <= MMC_BUS_WIDTH_8)
|
|
err = rtsx_usb_write_register(host->ucr, SD_CFG1,
|
|
0x03, width[bus_width]);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sd_pull_ctl_disable_lqfp48(struct rtsx_ucr *ucr)
|
|
{
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
|
|
|
|
return rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
}
|
|
|
|
static int sd_pull_ctl_disable_qfn24(struct rtsx_ucr *ucr)
|
|
{
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x56);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59);
|
|
|
|
return rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
}
|
|
|
|
static int sd_pull_ctl_enable_lqfp48(struct rtsx_ucr *ucr)
|
|
{
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0xAA);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0xAA);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0xA9);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
|
|
|
|
return rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
}
|
|
|
|
static int sd_pull_ctl_enable_qfn24(struct rtsx_ucr *ucr)
|
|
{
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0xA5);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x9A);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0xA5);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x9A);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x65);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x5A);
|
|
|
|
return rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
}
|
|
|
|
static int sd_power_on(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
|
|
rtsx_usb_init_cmd(ucr);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
|
|
CARD_SHARE_MASK, CARD_SHARE_SD);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN,
|
|
SD_CLK_EN, SD_CLK_EN);
|
|
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
if (err)
|
|
return err;
|
|
|
|
if (CHECK_PKG(ucr, LQFP48))
|
|
err = sd_pull_ctl_enable_lqfp48(ucr);
|
|
else
|
|
err = sd_pull_ctl_enable_qfn24(ucr);
|
|
if (err)
|
|
return err;
|
|
|
|
err = rtsx_usb_write_register(ucr, CARD_PWR_CTL,
|
|
POWER_MASK, PARTIAL_POWER_ON);
|
|
if (err)
|
|
return err;
|
|
|
|
usleep_range(800, 1000);
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
|
|
POWER_MASK|LDO3318_PWR_MASK, POWER_ON|LDO_ON);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE,
|
|
SD_OUTPUT_EN, SD_OUTPUT_EN);
|
|
|
|
return rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
}
|
|
|
|
static int sd_power_off(struct rtsx_usb_sdmmc *host)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, SD_CLK_EN, 0);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, SD_OUTPUT_EN, 0);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
|
|
POWER_MASK, POWER_OFF);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
|
|
POWER_MASK|LDO3318_PWR_MASK, POWER_OFF|LDO_SUSPEND);
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
if (err)
|
|
return err;
|
|
|
|
if (CHECK_PKG(ucr, LQFP48))
|
|
return sd_pull_ctl_disable_lqfp48(ucr);
|
|
return sd_pull_ctl_disable_qfn24(ucr);
|
|
}
|
|
|
|
static int sd_set_power_mode(struct rtsx_usb_sdmmc *host,
|
|
unsigned char power_mode)
|
|
{
|
|
int err;
|
|
|
|
if (power_mode != MMC_POWER_OFF)
|
|
power_mode = MMC_POWER_ON;
|
|
|
|
if (power_mode == host->power_mode)
|
|
return 0;
|
|
|
|
if (power_mode == MMC_POWER_OFF) {
|
|
err = sd_power_off(host);
|
|
pm_runtime_put_noidle(sdmmc_dev(host));
|
|
} else {
|
|
pm_runtime_get_noresume(sdmmc_dev(host));
|
|
err = sd_power_on(host);
|
|
}
|
|
|
|
if (!err)
|
|
host->power_mode = power_mode;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sd_set_timing(struct rtsx_usb_sdmmc *host,
|
|
unsigned char timing, bool *ddr_mode)
|
|
{
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
|
|
*ddr_mode = false;
|
|
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
switch (timing) {
|
|
case MMC_TIMING_UHS_SDR104:
|
|
case MMC_TIMING_UHS_SDR50:
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
|
|
0x0C | SD_ASYNC_FIFO_RST,
|
|
SD_30_MODE | SD_ASYNC_FIFO_RST);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
|
|
CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1);
|
|
break;
|
|
|
|
case MMC_TIMING_UHS_DDR50:
|
|
*ddr_mode = true;
|
|
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
|
|
0x0C | SD_ASYNC_FIFO_RST,
|
|
SD_DDR_MODE | SD_ASYNC_FIFO_RST);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
|
|
CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PUSH_POINT_CTL,
|
|
DDR_VAR_TX_CMD_DAT, DDR_VAR_TX_CMD_DAT);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
|
|
DDR_VAR_RX_DAT | DDR_VAR_RX_CMD,
|
|
DDR_VAR_RX_DAT | DDR_VAR_RX_CMD);
|
|
break;
|
|
|
|
case MMC_TIMING_MMC_HS:
|
|
case MMC_TIMING_SD_HS:
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
|
|
0x0C, SD_20_MODE);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
|
|
CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PUSH_POINT_CTL,
|
|
SD20_TX_SEL_MASK, SD20_TX_14_AHEAD);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
|
|
SD20_RX_SEL_MASK, SD20_RX_14_DELAY);
|
|
break;
|
|
|
|
default:
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_CFG1, 0x0C, SD_20_MODE);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF,
|
|
CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
|
|
SD_PUSH_POINT_CTL, 0xFF, 0);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL,
|
|
SD20_RX_SEL_MASK, SD20_RX_POS_EDGE);
|
|
break;
|
|
}
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
sd_set_power_mode(host, ios->power_mode);
|
|
sd_set_bus_width(host, ios->bus_width);
|
|
sd_set_timing(host, ios->timing, &host->ddr_mode);
|
|
|
|
host->vpclk = false;
|
|
host->double_clk = true;
|
|
|
|
switch (ios->timing) {
|
|
case MMC_TIMING_UHS_SDR104:
|
|
case MMC_TIMING_UHS_SDR50:
|
|
host->ssc_depth = SSC_DEPTH_2M;
|
|
host->vpclk = true;
|
|
host->double_clk = false;
|
|
break;
|
|
case MMC_TIMING_UHS_DDR50:
|
|
case MMC_TIMING_UHS_SDR25:
|
|
host->ssc_depth = SSC_DEPTH_1M;
|
|
break;
|
|
default:
|
|
host->ssc_depth = SSC_DEPTH_512K;
|
|
break;
|
|
}
|
|
|
|
host->initial_mode = (ios->clock <= 1000000) ? true : false;
|
|
host->clock = ios->clock;
|
|
|
|
rtsx_usb_switch_clock(host->ucr, host->clock, host->ssc_depth,
|
|
host->initial_mode, host->double_clk, host->vpclk);
|
|
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
dev_dbg(sdmmc_dev(host), "%s end\n", __func__);
|
|
}
|
|
|
|
static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err = 0;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s: signal_voltage = %d\n",
|
|
__func__, ios->signal_voltage);
|
|
|
|
if (host->host_removal)
|
|
return -ENOMEDIUM;
|
|
|
|
if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_120)
|
|
return -EPERM;
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
err = rtsx_usb_card_exclusive_check(ucr, RTSX_USB_SD_CARD);
|
|
if (err) {
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
return err;
|
|
}
|
|
|
|
/* Let mmc core do the busy checking, simply stop the forced-toggle
|
|
* clock(while issuing CMD11) and switch voltage.
|
|
*/
|
|
rtsx_usb_init_cmd(ucr);
|
|
|
|
if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
|
|
SD_IO_USING_1V8, SD_IO_USING_3V3);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
|
|
TUNE_SD18_MASK, TUNE_SD18_3V3);
|
|
} else {
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_BUS_STAT,
|
|
SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
|
|
SD_CLK_FORCE_STOP);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
|
|
SD_IO_USING_1V8, SD_IO_USING_1V8);
|
|
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
|
|
TUNE_SD18_MASK, TUNE_SD18_1V8);
|
|
}
|
|
|
|
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int sdmmc_card_busy(struct mmc_host *mmc)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err;
|
|
u8 stat;
|
|
u8 mask = SD_DAT3_STATUS | SD_DAT2_STATUS | SD_DAT1_STATUS
|
|
| SD_DAT0_STATUS;
|
|
|
|
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
|
|
SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP,
|
|
SD_CLK_TOGGLE_EN);
|
|
if (err)
|
|
goto out;
|
|
|
|
mdelay(1);
|
|
|
|
err = rtsx_usb_read_register(ucr, SD_BUS_STAT, &stat);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = rtsx_usb_write_register(ucr, SD_BUS_STAT,
|
|
SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0);
|
|
out:
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
/* check if any pin between dat[0:3] is low */
|
|
if ((stat & mask) != mask)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
int err = 0;
|
|
|
|
if (host->host_removal)
|
|
return -ENOMEDIUM;
|
|
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
if (!host->ddr_mode)
|
|
err = sd_tuning_rx(host, MMC_SEND_TUNING_BLOCK);
|
|
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct mmc_host_ops rtsx_usb_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,
|
|
.card_busy = sdmmc_card_busy,
|
|
.execute_tuning = sdmmc_execute_tuning,
|
|
};
|
|
|
|
#ifdef RTSX_USB_USE_LEDS_CLASS
|
|
static void rtsx_usb_led_control(struct led_classdev *led,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = container_of(led,
|
|
struct rtsx_usb_sdmmc, led);
|
|
|
|
if (host->host_removal)
|
|
return;
|
|
|
|
host->led.brightness = brightness;
|
|
schedule_work(&host->led_work);
|
|
}
|
|
|
|
static void rtsx_usb_update_led(struct work_struct *work)
|
|
{
|
|
struct rtsx_usb_sdmmc *host =
|
|
container_of(work, struct rtsx_usb_sdmmc, led_work);
|
|
struct rtsx_ucr *ucr = host->ucr;
|
|
|
|
pm_runtime_get_noresume(sdmmc_dev(host));
|
|
mutex_lock(&ucr->dev_mutex);
|
|
|
|
if (host->power_mode == MMC_POWER_OFF)
|
|
goto out;
|
|
|
|
if (host->led.brightness == LED_OFF)
|
|
rtsx_usb_turn_off_led(ucr);
|
|
else
|
|
rtsx_usb_turn_on_led(ucr);
|
|
|
|
out:
|
|
mutex_unlock(&ucr->dev_mutex);
|
|
pm_runtime_put_sync_suspend(sdmmc_dev(host));
|
|
}
|
|
#endif
|
|
|
|
static void rtsx_usb_init_host(struct rtsx_usb_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_UHS_SDR50 |
|
|
MMC_CAP_ERASE | MMC_CAP_SYNC_RUNTIME_PM;
|
|
mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE |
|
|
MMC_CAP2_NO_SDIO;
|
|
|
|
mmc->max_current_330 = 400;
|
|
mmc->max_current_180 = 800;
|
|
mmc->ops = &rtsx_usb_sdmmc_ops;
|
|
mmc->max_segs = 256;
|
|
mmc->max_seg_size = 65536;
|
|
mmc->max_blk_size = 512;
|
|
mmc->max_blk_count = 65535;
|
|
mmc->max_req_size = 524288;
|
|
|
|
host->power_mode = MMC_POWER_OFF;
|
|
}
|
|
|
|
static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
|
|
{
|
|
struct mmc_host *mmc;
|
|
struct rtsx_usb_sdmmc *host;
|
|
struct rtsx_ucr *ucr;
|
|
#ifdef RTSX_USB_USE_LEDS_CLASS
|
|
int err;
|
|
#endif
|
|
|
|
ucr = usb_get_intfdata(to_usb_interface(pdev->dev.parent));
|
|
if (!ucr)
|
|
return -ENXIO;
|
|
|
|
dev_dbg(&(pdev->dev), ": Realtek USB SD/MMC controller found\n");
|
|
|
|
mmc = mmc_alloc_host(sizeof(*host), &pdev->dev);
|
|
if (!mmc)
|
|
return -ENOMEM;
|
|
|
|
host = mmc_priv(mmc);
|
|
host->ucr = ucr;
|
|
host->mmc = mmc;
|
|
host->pdev = pdev;
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
mutex_init(&host->host_mutex);
|
|
rtsx_usb_init_host(host);
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
#ifdef RTSX_USB_USE_LEDS_CLASS
|
|
snprintf(host->led_name, sizeof(host->led_name),
|
|
"%s::", mmc_hostname(mmc));
|
|
host->led.name = host->led_name;
|
|
host->led.brightness = LED_OFF;
|
|
host->led.default_trigger = mmc_hostname(mmc);
|
|
host->led.brightness_set = rtsx_usb_led_control;
|
|
|
|
err = led_classdev_register(mmc_dev(mmc), &host->led);
|
|
if (err)
|
|
dev_err(&(pdev->dev),
|
|
"Failed to register LED device: %d\n", err);
|
|
INIT_WORK(&host->led_work, rtsx_usb_update_led);
|
|
|
|
#endif
|
|
mmc_add_host(mmc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtsx_usb_sdmmc_drv_remove(struct platform_device *pdev)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = platform_get_drvdata(pdev);
|
|
struct mmc_host *mmc;
|
|
|
|
if (!host)
|
|
return 0;
|
|
|
|
mmc = host->mmc;
|
|
host->host_removal = true;
|
|
|
|
mutex_lock(&host->host_mutex);
|
|
if (host->mrq) {
|
|
dev_dbg(&(pdev->dev),
|
|
"%s: Controller removed during transfer\n",
|
|
mmc_hostname(mmc));
|
|
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);
|
|
|
|
#ifdef RTSX_USB_USE_LEDS_CLASS
|
|
cancel_work_sync(&host->led_work);
|
|
led_classdev_unregister(&host->led);
|
|
#endif
|
|
|
|
mmc_free_host(mmc);
|
|
pm_runtime_disable(&pdev->dev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
dev_dbg(&(pdev->dev),
|
|
": Realtek USB SD/MMC module has been removed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int rtsx_usb_sdmmc_runtime_suspend(struct device *dev)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = dev_get_drvdata(dev);
|
|
|
|
host->mmc->caps &= ~MMC_CAP_NEEDS_POLL;
|
|
return 0;
|
|
}
|
|
|
|
static int rtsx_usb_sdmmc_runtime_resume(struct device *dev)
|
|
{
|
|
struct rtsx_usb_sdmmc *host = dev_get_drvdata(dev);
|
|
|
|
host->mmc->caps |= MMC_CAP_NEEDS_POLL;
|
|
if (sdmmc_get_cd(host->mmc) == 1)
|
|
mmc_detect_change(host->mmc, 0);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops rtsx_usb_sdmmc_dev_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(rtsx_usb_sdmmc_runtime_suspend,
|
|
rtsx_usb_sdmmc_runtime_resume, NULL)
|
|
};
|
|
|
|
static const struct platform_device_id rtsx_usb_sdmmc_ids[] = {
|
|
{
|
|
.name = "rtsx_usb_sdmmc",
|
|
}, {
|
|
/* sentinel */
|
|
}
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, rtsx_usb_sdmmc_ids);
|
|
|
|
static struct platform_driver rtsx_usb_sdmmc_driver = {
|
|
.probe = rtsx_usb_sdmmc_drv_probe,
|
|
.remove = rtsx_usb_sdmmc_drv_remove,
|
|
.id_table = rtsx_usb_sdmmc_ids,
|
|
.driver = {
|
|
.name = "rtsx_usb_sdmmc",
|
|
.pm = &rtsx_usb_sdmmc_dev_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(rtsx_usb_sdmmc_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>");
|
|
MODULE_DESCRIPTION("Realtek USB SD/MMC Card Host Driver");
|