unplugged-kernel/drivers/mmc/host/mediatek/ComboA/mt6885/autok_dvfs.c

1237 lines
31 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <asm/segment.h>
#include <linux/uaccess.h>
#include <linux/buffer_head.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/regulator/consumer.h>
#include "autok_dvfs.h"
#include "mtk_sd.h"
#include "dbg.h"
#include <mmc/core/sdio_ops.h>
#include <mmc/core/core.h>
#include <mmc/core/mmc_ops.h>
#include <mmc/core/card.h>
static char const * const sdio_autok_res_path[] = {
"/data/sdio_autok_0", "/data/sdio_autok_1",
"/data/sdio_autok_2", "/data/sdio_autok_3",
};
/* After merge still have over 10 OOOOOOOOOOO window */
#define AUTOK_MERGE_MIN_WIN 10
#define SDIO_AUTOK_DIFF_MARGIN 3
#ifdef CONFIG_MTK_SDIO_SUPPORT
static struct file *msdc_file_open(const char *path, int flags, int rights)
{
struct file *filp = NULL;
#ifdef SDIO_HQA
mm_segment_t oldfs;
int err = 0;
oldfs = get_fs();
set_fs(get_ds());
filp = filp_open(path, flags, rights);
set_fs(oldfs);
if (IS_ERR(filp)) {
err = PTR_ERR(filp);
return NULL;
}
#endif
return filp;
}
static int msdc_file_read(struct file *file, unsigned long long offset,
unsigned char *data, unsigned int size)
{
int ret = 0;
#ifdef SDIO_HQA
mm_segment_t oldfs;
oldfs = get_fs();
set_fs(get_ds());
ret = vfs_read(file, data, size, &offset);
set_fs(oldfs);
#endif
return ret;
}
static int msdc_file_write(struct file *file, unsigned long long offset,
unsigned char *data, unsigned int size)
{
int ret = 0;
#ifdef SDIO_HQA
mm_segment_t oldfs;
oldfs = get_fs();
set_fs(get_ds());
ret = vfs_write(file, data, size, &offset);
set_fs(oldfs);
#endif
return ret;
}
#endif
int sdio_autok_res_exist(struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct file *filp = NULL;
int i;
for (i = 0; i < AUTOK_VCORE_NUM; i++) {
filp = msdc_file_open(sdio_autok_res_path[i], O_RDONLY, 0644);
if (filp == NULL) {
pr_notice("autok result not exist\n");
return 0;
}
filp_close(filp, NULL);
}
#endif
return 1;
}
int sdio_autok_res_apply(struct msdc_host *host, int vcore)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct file *filp = NULL;
size_t size;
u8 *res;
int ret = -1;
int i;
if (vcore < AUTOK_VCORE_LEVEL0 || vcore >= AUTOK_VCORE_NUM)
vcore = AUTOK_VCORE_LEVEL0;
res = host->autok_res[vcore];
filp = msdc_file_open(sdio_autok_res_path[vcore], O_RDONLY, 0644);
if (filp == NULL) {
pr_notice("autok result open fail\n");
return ret;
}
size = msdc_file_read(filp, 0, res, TUNING_PARA_SCAN_COUNT);
if (size == TUNING_PARA_SCAN_COUNT) {
autok_tuning_parameter_init(host, res);
for (i = 1; i < TUNING_PARA_SCAN_COUNT; i++)
pr_notice("autok result exist!, result[%d] = %d\n",
i, res[i]);
ret = 0;
}
filp_close(filp, NULL);
return ret;
#endif
return 0;
}
int sdio_autok_res_save(struct msdc_host *host, int vcore, u8 *res)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct file *filp = NULL;
size_t size;
int ret = -1;
if (res == NULL)
return ret;
if (vcore < AUTOK_VCORE_LEVEL0 || vcore >= AUTOK_VCORE_NUM)
vcore = AUTOK_VCORE_LEVEL0;
filp = msdc_file_open(sdio_autok_res_path[vcore],
O_CREAT | O_WRONLY, 0644);
if (filp == NULL) {
pr_notice("autok result open fail\n");
return ret;
}
size = msdc_file_write(filp, 0, res, TUNING_PARA_SCAN_COUNT);
if (size == TUNING_PARA_SCAN_COUNT)
ret = 0;
vfs_fsync(filp, 0);
filp_close(filp, NULL);
return ret;
#endif
return 0;
}
int autok_res_check(u8 *res_h, u8 *res_l)
{
int ret = 0;
int i;
for (i = 0; i < TUNING_PARAM_COUNT; i++) {
if ((i == CMD_RD_D_DLY1) || (i == DAT_RD_D_DLY1)) {
if ((res_h[i] > res_l[i])
&& (res_h[i] - res_l[i] > SDIO_AUTOK_DIFF_MARGIN))
ret = -1;
if ((res_l[i] > res_h[i])
&& (res_l[i] - res_h[i] > SDIO_AUTOK_DIFF_MARGIN))
ret = -1;
} else if ((i == CMD_RD_D_DLY1_SEL)
|| (i == DAT_RD_D_DLY1_SEL)) {
/* this is cover by previous check,
* just by pass if 0 and 1 in cmd/dat delay
*/
} else {
if (res_h[i] != res_l[i])
ret = -1;
}
}
#ifndef SDIO_HW_DVFS_CONDITIONAL
ret = -1;
#endif
pr_notice("%s %d!\n", __func__, ret);
return ret;
}
#ifdef CONFIG_MTK_SDIO_SUPPORT
static u32 sdio_reg_backup[AUTOK_VCORE_NUM * BACKUP_REG_COUNT_SDIO];
#endif
#ifdef CONFIG_MTK_SDIO_SUPPORT
u16 sdio_reg_backup_offsets_src[] = {
OFFSET_MSDC_IOCON,
OFFSET_MSDC_PATCH_BIT0,
OFFSET_MSDC_PATCH_BIT1,
OFFSET_MSDC_PATCH_BIT2,
OFFSET_MSDC_PAD_TUNE0,
OFFSET_MSDC_PAD_TUNE1,
OFFSET_EMMC50_PAD_DS_TUNE,
OFFSET_EMMC50_PAD_CMD_TUNE,
OFFSET_EMMC50_PAD_DAT01_TUNE,
OFFSET_EMMC50_PAD_DAT23_TUNE,
OFFSET_EMMC50_PAD_DAT45_TUNE,
OFFSET_EMMC50_PAD_DAT67_TUNE,
OFFSET_EMMC50_CFG0,
OFFSET_EMMC50_CFG1
};
u16 sdio_dvfs_reg_backup_offsets[] = {
OFFSET_MSDC_IOCON_1,
OFFSET_MSDC_PATCH_BIT0_1,
OFFSET_MSDC_PATCH_BIT1_1,
OFFSET_MSDC_PATCH_BIT2_1,
OFFSET_MSDC_PAD_TUNE0_1,
OFFSET_MSDC_PAD_TUNE1_1,
OFFSET_EMMC50_PAD_DS_TUNE_1,
OFFSET_EMMC50_PAD_CMD_TUNE_1,
OFFSET_EMMC50_PAD_DAT01_TUNE_1,
OFFSET_EMMC50_PAD_DAT23_TUNE_1,
OFFSET_EMMC50_PAD_DAT45_TUNE_1,
OFFSET_EMMC50_PAD_DAT67_TUNE_1,
OFFSET_EMMC50_CFG0_1,
OFFSET_EMMC50_CFG1_1
};
#endif
void msdc_dvfs_reg_restore(struct msdc_host *host)
{
#if defined(VCOREFS_READY)
void __iomem *base = host->base;
int i, j;
u32 *reg_backup_ptr;
if (!host->dvfs_reg_backup)
return;
reg_backup_ptr = host->dvfs_reg_backup;
for (i = 0; i < AUTOK_VCORE_NUM; i++) {
for (j = 0; j < host->dvfs_reg_backup_cnt; j++) {
MSDC_WRITE32(
host->base + MSDC_DVFS_SET_SIZE * i
+ host->dvfs_reg_offsets[j],
*reg_backup_ptr);
reg_backup_ptr++;
}
for (j = 0; j < host->dvfs_reg_backup_cnt_top; j++) {
MSDC_WRITE32(
host->base_top + MSDC_TOP_SET_SIZE * i
+ host->dvfs_reg_offsets_top[j],
*reg_backup_ptr);
reg_backup_ptr++;
}
}
/* Enable HW DVFS */
MSDC_WRITE32(MSDC_CFG,
MSDC_READ32(MSDC_CFG) | (MSDC_CFG_DVFS_EN | MSDC_CFG_DVFS_HW));
#endif
}
void sdio_dvfs_reg_restore(struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
void __iomem *base = host->base;
/* High Vcore */
MSDC_WRITE32(MSDC_IOCON_1, sdio_reg_backup[0][0]);
MSDC_WRITE32(MSDC_PATCH_BIT0_1, sdio_reg_backup[0][1]);
MSDC_WRITE32(MSDC_PATCH_BIT1_1, sdio_reg_backup[0][2]);
MSDC_WRITE32(MSDC_PATCH_BIT2_1, sdio_reg_backup[0][3]);
MSDC_WRITE32(MSDC_PAD_TUNE0_1, sdio_reg_backup[0][4]);
MSDC_WRITE32(MSDC_PAD_TUNE1_1, sdio_reg_backup[0][5]);
MSDC_WRITE32(MSDC_DAT_RDDLY0_1, sdio_reg_backup[0][6]);
MSDC_WRITE32(MSDC_DAT_RDDLY1_1, sdio_reg_backup[0][7]);
MSDC_WRITE32(MSDC_DAT_RDDLY2_1, sdio_reg_backup[0][8]);
MSDC_WRITE32(MSDC_DAT_RDDLY3_1, sdio_reg_backup[0][9]);
/* Low Vcore */
MSDC_WRITE32(MSDC_IOCON_2, sdio_reg_backup[1][0]);
MSDC_WRITE32(MSDC_PATCH_BIT0_2, sdio_reg_backup[1][1]);
MSDC_WRITE32(MSDC_PATCH_BIT1_2, sdio_reg_backup[1][2]);
MSDC_WRITE32(MSDC_PATCH_BIT2_2, sdio_reg_backup[1][3]);
MSDC_WRITE32(MSDC_PAD_TUNE0_2, sdio_reg_backup[1][4]);
MSDC_WRITE32(MSDC_PAD_TUNE1_2, sdio_reg_backup[1][5]);
MSDC_WRITE32(MSDC_DAT_RDDLY0_2, sdio_reg_backup[1][6]);
MSDC_WRITE32(MSDC_DAT_RDDLY1_2, sdio_reg_backup[1][7]);
MSDC_WRITE32(MSDC_DAT_RDDLY2_2, sdio_reg_backup[1][8]);
MSDC_WRITE32(MSDC_DAT_RDDLY3_2, sdio_reg_backup[1][9]);
/* Enable HW DVFS */
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_DVFS_EN, 1);
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_DVFS_HW, 1);
#endif
}
#ifdef CONFIG_MTK_SDIO_SUPPORT
static void sdio_dvfs_reg_backup(struct msdc_host *host)
{
void __iomem *base = host->base;
/* High Vcore */
sdio_reg_backup[0][0] = MSDC_READ32(MSDC_IOCON_1);
sdio_reg_backup[0][1] = MSDC_READ32(MSDC_PATCH_BIT0_1);
sdio_reg_backup[0][2] = MSDC_READ32(MSDC_PATCH_BIT1_1);
sdio_reg_backup[0][3] = MSDC_READ32(MSDC_PATCH_BIT2_1);
sdio_reg_backup[0][4] = MSDC_READ32(MSDC_PAD_TUNE0_1);
sdio_reg_backup[0][5] = MSDC_READ32(MSDC_PAD_TUNE1_1);
sdio_reg_backup[0][6] = MSDC_READ32(MSDC_DAT_RDDLY0_1);
sdio_reg_backup[0][7] = MSDC_READ32(MSDC_DAT_RDDLY1_1);
sdio_reg_backup[0][8] = MSDC_READ32(MSDC_DAT_RDDLY2_1);
sdio_reg_backup[0][9] = MSDC_READ32(MSDC_DAT_RDDLY3_1);
/* Low Vcore */
sdio_reg_backup[1][0] = MSDC_READ32(MSDC_IOCON_2);
sdio_reg_backup[1][1] = MSDC_READ32(MSDC_PATCH_BIT0_2);
sdio_reg_backup[1][2] = MSDC_READ32(MSDC_PATCH_BIT1_2);
sdio_reg_backup[1][3] = MSDC_READ32(MSDC_PATCH_BIT2_2);
sdio_reg_backup[1][4] = MSDC_READ32(MSDC_PAD_TUNE0_2);
sdio_reg_backup[1][5] = MSDC_READ32(MSDC_PAD_TUNE1_2);
sdio_reg_backup[1][6] = MSDC_READ32(MSDC_DAT_RDDLY0_2);
sdio_reg_backup[1][7] = MSDC_READ32(MSDC_DAT_RDDLY1_2);
sdio_reg_backup[1][8] = MSDC_READ32(MSDC_DAT_RDDLY2_2);
sdio_reg_backup[1][9] = MSDC_READ32(MSDC_DAT_RDDLY3_2);
}
#endif
void sdio_set_hw_dvfs(int vcore, int done, struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
void __iomem *base = host->base;
if (vcore >= AUTOK_VCORE_LEVEL1) {
MSDC_WRITE32(MSDC_IOCON_1, MSDC_READ32(MSDC_IOCON));
MSDC_WRITE32(MSDC_PATCH_BIT0_1, MSDC_READ32(MSDC_PATCH_BIT0));
MSDC_WRITE32(MSDC_PATCH_BIT1_1, MSDC_READ32(MSDC_PATCH_BIT1));
MSDC_WRITE32(MSDC_PATCH_BIT2_1, MSDC_READ32(MSDC_PATCH_BIT2));
MSDC_WRITE32(MSDC_PAD_TUNE0_1, MSDC_READ32(MSDC_PAD_TUNE0));
MSDC_WRITE32(MSDC_PAD_TUNE1_1, MSDC_READ32(MSDC_PAD_TUNE1));
MSDC_WRITE32(MSDC_DAT_RDDLY0_1, MSDC_READ32(MSDC_DAT_RDDLY0));
MSDC_WRITE32(MSDC_DAT_RDDLY1_1, MSDC_READ32(MSDC_DAT_RDDLY1));
MSDC_WRITE32(MSDC_DAT_RDDLY2_1, MSDC_READ32(MSDC_DAT_RDDLY2));
MSDC_WRITE32(MSDC_DAT_RDDLY3_1, MSDC_READ32(MSDC_DAT_RDDLY3));
} else {
MSDC_WRITE32(MSDC_IOCON_2, MSDC_READ32(MSDC_IOCON));
MSDC_WRITE32(MSDC_PATCH_BIT0_2, MSDC_READ32(MSDC_PATCH_BIT0));
MSDC_WRITE32(MSDC_PATCH_BIT1_2, MSDC_READ32(MSDC_PATCH_BIT1));
MSDC_WRITE32(MSDC_PATCH_BIT2_2, MSDC_READ32(MSDC_PATCH_BIT2));
MSDC_WRITE32(MSDC_PAD_TUNE0_2, MSDC_READ32(MSDC_PAD_TUNE0));
MSDC_WRITE32(MSDC_PAD_TUNE1_2, MSDC_READ32(MSDC_PAD_TUNE1));
MSDC_WRITE32(MSDC_DAT_RDDLY0_2, MSDC_READ32(MSDC_DAT_RDDLY0));
MSDC_WRITE32(MSDC_DAT_RDDLY1_2, MSDC_READ32(MSDC_DAT_RDDLY1));
MSDC_WRITE32(MSDC_DAT_RDDLY2_2, MSDC_READ32(MSDC_DAT_RDDLY2));
MSDC_WRITE32(MSDC_DAT_RDDLY3_2, MSDC_READ32(MSDC_DAT_RDDLY3));
}
if (done) {
/* Enable HW DVFS */
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_DVFS_EN, 1);
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_DVFS_HW, 1);
/* Backup the register, restore when resume */
sdio_dvfs_reg_backup(host);
}
#endif
}
/* For backward compatible, remove later */
int wait_sdio_autok_ready(void *data)
{
return 0;
}
EXPORT_SYMBOL(wait_sdio_autok_ready);
static void msdc_set_hw_dvfs(int vcore, struct msdc_host *host)
{
}
void sdio_autok_wait_dvfs_ready(void)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
int dvfs;
dvfs = is_vcorefs_can_work();
/* DVFS not ready, just wait */
while (dvfs == 0) {
pr_notice("DVFS not ready\n");
msleep(100);
dvfs = is_vcorefs_can_work();
}
if (dvfs == -1)
pr_notice("DVFS feature not enable\n");
if (dvfs == 1)
pr_notice("DVFS ready\n");
#endif
}
int sd_execute_dvfs_autok(struct msdc_host *host, u32 opcode)
{
int ret = 0;
int vcore = AUTOK_VCORE_MERGE;
u8 *res;
res = host->autok_res[vcore];
if (host->mmc->ios.timing == MMC_TIMING_UHS_SDR104 ||
host->mmc->ios.timing == MMC_TIMING_UHS_SDR50) {
if (host->is_autok_done == 0) {
pr_notice("[AUTOK]SDcard autok\n");
ret = autok_execute_tuning(host, res);
memcpy(host->autok_res[AUTOK_VCORE_LEVEL0],
host->autok_res[AUTOK_VCORE_MERGE],
TUNING_PARA_SCAN_COUNT);
host->is_autok_done = 1;
} else {
autok_init_sdr104(host);
autok_tuning_parameter_init(host, res);
}
}
return ret;
}
int emmc_execute_dvfs_autok(struct msdc_host *host, u32 opcode)
{
int ret = 0;
int vcore = 0;
u8 *res;
#if defined(VCOREFS_READY)
if (host->use_hw_dvfs == 0) {
vcore = AUTOK_VCORE_MERGE;
} else {
vcore = get_cur_vcore_opp();
if (vcore >= AUTOK_VCORE_NUM)
vcore = AUTOK_VCORE_NUM - 1;
}
#endif
res = host->autok_res[vcore];
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200) {
#ifdef MSDC_HQA
msdc_HQA_set_voltage(host);
#endif
if (opcode == MMC_SEND_STATUS) {
pr_notice("[AUTOK]eMMC HS200 Tune CMD only\n");
ret = hs200_execute_tuning_cmd(host, res);
} else {
pr_notice("[AUTOK]eMMC HS200 Tune\n");
ret = hs200_execute_tuning(host, res);
}
if (host->hs400_mode == false) {
host->is_autok_done = 1;
complete(&host->autok_done);
}
} else if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
#ifdef MSDC_HQA
msdc_HQA_set_voltage(host);
#endif
if (opcode == MMC_SEND_STATUS) {
pr_notice("[AUTOK]eMMC HS400 Tune CMD only\n");
ret = hs400_execute_tuning_cmd(host, res);
} else {
pr_notice("[AUTOK]eMMC HS400 Tune\n");
ret = hs400_execute_tuning(host, res);
}
host->is_autok_done = 1;
complete(&host->autok_done);
}
if (host->use_hw_dvfs == 1)
msdc_set_hw_dvfs(vcore, host);
return ret;
}
#ifdef CONFIG_MTK_SDIO_SUPPORT
void msdc_dvfs_reg_backup_init(struct msdc_host *host)
{
if (host->hw->host_function == MSDC_SDIO && host->use_hw_dvfs) {
host->dvfs_reg_backup = sdio_reg_backup;
host->dvfs_reg_offsets = sdio_dvfs_reg_backup_offsets;
host->dvfs_reg_offsets_src = sdio_reg_backup_offsets_src;
host->dvfs_reg_backup_cnt = BACKUP_REG_COUNT_SDIO;
}
}
#else
void msdc_dvfs_reg_backup_init(struct msdc_host *host)
{
pr_debug("msdc%d:notice %s info", host->id, __func__);
}
#endif
void sdio_execute_dvfs_autok_mode(struct msdc_host *host, bool ddr208)
{
#ifndef FPGA_PLATFORM
#ifdef CONFIG_MTK_SDIO_SUPPORT
void __iomem *base = host->base;
int sdio_res_exist = 0;
int vcore;
int i;
int (*autok_init)(struct msdc_host *host);
int (*autok_execute)(struct msdc_host *host, u8 *res);
if (ddr208) {
autok_init = autok_init_ddr208;
autok_execute = autok_sdio30_plus_tuning;
pr_notice("[AUTOK]SDIO DDR208 Tune\n");
} else {
autok_init = autok_init_sdr104;
autok_execute = autok_execute_tuning;
pr_notice("[AUTOK]SDIO SDR104 Tune\n");
}
if (host->is_autok_done) {
autok_init(host);
/* Check which vcore setting to apply */
vcore = vcorefs_get_hw_opp();
pr_notice("[AUTOK]Apply first tune para vcore = %d\n", vcore);
autok_tuning_parameter_init(host, host->autok_res[vcore]);
if (host->use_hw_dvfs == 0) {
pr_notice("[AUTOK]No need change para when dvfs\n");
} else {
pr_notice("[AUTOK]Need change para when dvfs\n");
/* Use HW DVFS */
msdc_dvfs_reg_restore(host);
}
return;
}
/* HQA need read autok setting from file */
sdio_res_exist = sdio_autok_res_exist(host);
/* Wait DFVS ready for excute autok here */
sdio_autok_wait_dvfs_ready();
for (i = 0; i < AUTOK_VCORE_NUM; i++) {
if (vcorefs_request_dvfs_opp(KIR_AUTOK_SDIO, i) != 0)
pr_notice(
"vcorefs_request_dvfs_opp@LEVEL%d fail!\n", i);
if (sdio_res_exist)
sdio_autok_res_apply(host, i);
else
autok_execute(host, host->autok_res[i]);
msdc_set_hw_dvfs(i, host);
}
/* Backup the register, restore when resume */
msdc_dvfs_reg_backup(host);
if (autok_res_check(host->autok_res[AUTOK_VCORE_LEVEL3],
host->autok_res[AUTOK_VCORE_LEVEL0]) == 0) {
pr_notice("[AUTOK]No need change para when dvfs\n");
} else {
pr_notice("[AUTOK]Need change para when dvfs\n");
/* Use HW DVFS */
host->use_hw_dvfs = 1;
host->dvfs_id = KIR_AUTOK_SDIO;
/* Enable HW DVFS, but setting used now is at register offset
* <=0x104. Setting at register offset >=0x300 will effect
* after SPM handshakes with MSDC.
*/
MSDC_WRITE32(MSDC_CFG,
MSDC_READ32(MSDC_CFG)
| (MSDC_CFG_DVFS_EN | MSDC_CFG_DVFS_HW));
}
/* Un-request, return 0 pass */
if (vcorefs_request_dvfs_opp(KIR_AUTOK_SDIO, OPP_UNREQ) != 0)
pr_notice("vcorefs_request_dvfs_opp@OPP_UNREQ fail!\n");
/* Tell DVFS can start now because AUTOK done */
spm_msdc_dvfs_setting(host->dvfs_id, 1);
host->is_autok_done = 1;
complete(&host->autok_done);
#endif
#endif
}
#ifdef CONFIG_MTK_SDIO_SUPPORT
static int msdc_io_rw_direct_host(struct mmc_host *host, int write,
unsigned int fn, unsigned int addr, u8 in, u8 *out)
{
struct mmc_command cmd = {0};
int err;
if (!host || fn > 7)
return -EINVAL;
/* sanity check */
if (addr & ~0x1FFFF)
return -EINVAL;
cmd.opcode = SD_IO_RW_DIRECT;
cmd.arg = write ? 0x80000000 : 0x00000000;
cmd.arg |= fn << 28;
cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
cmd.arg |= addr << 9;
cmd.arg |= in;
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
return err;
if (mmc_host_is_spi(host)) {
/* host driver already reported errors */
} else {
if (cmd.resp[0] & R5_ERROR)
return -EIO;
if (cmd.resp[0] & R5_FUNCTION_NUMBER)
return -EINVAL;
if (cmd.resp[0] & R5_OUT_OF_RANGE)
return -ERANGE;
}
if (out) {
if (mmc_host_is_spi(host))
*out = (cmd.resp[0] >> 8) & 0xFF;
else
*out = cmd.resp[0] & 0xFF;
}
return 0;
}
#endif
/* #define DEVICE_RX_READ_DEBUG */
void sdio_plus_set_device_rx(struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct mmc_host *mmc = host->mmc;
void __iomem *base = host->base;
u32 msdc_cfg;
int retry = 3, cnt = 1000;
#ifdef DEVICE_RX_READ_DEBUG
unsigned char data;
#endif
int ret = 0;
msdc_cfg = MSDC_READ32(MSDC_CFG);
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_CKMOD_HS400, 0);
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_CKMOD, 0);
MSDC_SET_FIELD(MSDC_CFG, MSDC_CFG_CKDIV, 5);
msdc_retry(!(MSDC_READ32(MSDC_CFG) & MSDC_CFG_CKSTB),
retry, cnt, host->id);
#ifdef DEVICE_RX_READ_DEBUG
pr_notice("%s +++++++++++++++++++++++++=\n", __func__);
ret = msdc_io_rw_direct_host(mmc, 0, 0, 0x2, 0, &data);
pr_notice("0x2 data: %x , ret: %x\n", data, ret);
#endif
ret = msdc_io_rw_direct_host(mmc, 1, 0, 0x2, 0x2, 0);
#ifdef DEVICE_RX_READ_DEBUG
ret = msdc_io_rw_direct_host(mmc, 0, 0, 0x2, 0, &data);
pr_notice("0x2 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x11C, 0, &data);
pr_notice("0x11C data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x124, 0, &data);
pr_notice("0x124 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x125, 0, &data);
pr_notice("0x125 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x126, 0, &data);
pr_notice("0x126 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x127, 0, &data);
pr_notice("0x127 data: %x , ret: %x\n", data, ret);
#endif
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x11C, 0x90, 0);
#if 0 /* Device data RX window cover by host data TX */
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x124, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x125, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x126, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x127, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x128, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x129, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x12A, 0x87, 0);
ret = msdc_io_rw_direct_host(mmc, 1, 1, 0x12B, 0x87, 0);
#endif
#ifdef DEVICE_RX_READ_DEBUG
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x11C, 0, &data);
pr_notice("0x11C data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x124, 0, &data);
pr_notice("0x124 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x125, 0, &data);
pr_notice("0x125 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x126, 0, &data);
pr_notice("0x126 data: %x , ret: %x\n", data, ret);
ret = msdc_io_rw_direct_host(mmc, 0, 1, 0x127, 0, &data);
pr_notice("0x127 data: %x , ret: %x\n", data, ret);
#endif
MSDC_WRITE32(MSDC_CFG, msdc_cfg);
msdc_retry(!(MSDC_READ32(MSDC_CFG) & MSDC_CFG_CKSTB),
retry, cnt, host->id);
#endif
}
#define SDIO_CCCR_MTK_DDR208 0xF2
#define SDIO_MTK_DDR208 0x3
#define SDIO_MTK_DDR208_SUPPORT 0x2
int sdio_plus_set_device_ddr208(struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct mmc_host *mmc = host->mmc;
static u8 autok_res104[TUNING_PARA_SCAN_COUNT];
unsigned char data;
int err = 0;
if (host->is_autok_done) {
autok_init_sdr104(host);
autok_tuning_parameter_init(host, autok_res104);
} else {
autok_execute_tuning(host, autok_res104);
}
/* Read SDIO Device CCCR[0x00F2]
* Bit[1] Always 1, Support DDR208 Mode.
* Bit[0]
* 1:Enable DDR208.
* 0:Disable DDR208.
*/
err = msdc_io_rw_direct_host(mmc, 0, 0, SDIO_CCCR_MTK_DDR208, 0, &data);
/* Re-autok sdr104 if default setting fail */
if (err) {
autok_execute_tuning(host, autok_res104);
err = msdc_io_rw_direct_host(mmc,
0, 0, SDIO_CCCR_MTK_DDR208, 0, &data);
}
if (err) {
pr_notice("Read SDIO_CCCR_MTK_DDR208 fail\n");
goto end;
}
if ((data & SDIO_MTK_DDR208_SUPPORT) == 0) {
pr_notice("Device not support SDIO_MTK_DDR208\n");
err = -1;
goto end;
}
/* Switch to DDR208 Flow :
* 1. First switch to DDR50 mode;
* 2. Then Host CMD52 Write 0x03/0x01 to CCCR[0x00F2]
*/
err = msdc_io_rw_direct_host(mmc, 0, 0, SDIO_CCCR_SPEED, 0, &data);
if (err) {
pr_notice("Read SDIO_CCCR_SPEED fail\n");
goto end;
}
data = (data & (~SDIO_SPEED_BSS_MASK)) | SDIO_SPEED_DDR50;
err = msdc_io_rw_direct_host(mmc, 1, 0, SDIO_CCCR_SPEED, data, NULL);
if (err) {
pr_notice("Set SDIO_CCCR_SPEED to DDR fail\n");
goto end;
}
err = msdc_io_rw_direct_host(mmc, 1, 0, SDIO_CCCR_MTK_DDR208,
SDIO_MTK_DDR208, NULL);
if (err) {
pr_notice("Set SDIO_MTK_DDR208 fail\n");
goto end;
}
end:
return err;
#endif
return 0;
}
void sdio_execute_dvfs_autok(struct msdc_host *host)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
/* Set device timming for latch data */
sdio_plus_set_device_rx(host);
/* Not support DDR208 and only Autok SDR104 */
if (!(host->hw->flags & MSDC_SDIO_DDR208)) {
sdio_execute_dvfs_autok_mode(host, 0);
return;
}
if (sdio_plus_set_device_ddr208(host))
return;
/* Set HS400 clock mode and DIV = 0 */
msdc_clk_stable(host, 3, 0, 1);
/* Find DDR208 timing */
sdio_execute_dvfs_autok_mode(host, 1);
#endif
}
#if defined(VCOREFS_READY)
static int autok_opp[AUTOK_VCORE_NUM] = {
VCORE_DVFS_OPP_2, /* 0.825V, OPP_0 is invalid */
VCORE_DVFS_OPP_6, /* 0.725V */
VCORE_DVFS_OPP_9, /* 0.65V */
};
#endif
#ifdef CONFIG_MTK_EMMC_HW_CQ
static int emmc_autok_switch_cqe(struct msdc_host *host, bool enable)
{
bool cmdq_mode = 0;
int err = 0;
if (host->mmc->card) {
cmdq_mode = !!mmc_card_cmdq(host->mmc->card);
if (cmdq_mode && !enable) {
err = host->mmc->cmdq_ops->halt(host->mmc, 1);
if (err) {
pr_notice("%s: halt:failed: %d\n",
__func__, err);
return err;
}
/* disable for xf data */
host->mmc->cmdq_ops->disable(host->mmc, true);
} else if (cmdq_mode && enable) {
/* enable for cqhci */
host->mmc->cmdq_ops->enable(host->mmc);
err = host->mmc->cmdq_ops->halt(host->mmc, 0);
if (err) {
pr_notice("%s: unhalt:failed: %d\n",
__func__, err);
return err;
}
}
}
return err;
}
#endif
#ifdef SD_RUNTIME_AUTOK_MERGE
int sd_runtime_autok_merge(struct msdc_host *host)
{
int merge_result, merge_mode, merge_window, merge_count;
int i, ret = 0;
u8 *res;
for (merge_count = 1; merge_count < AUTOK_VCORE_NUM; merge_count++) {
if (host->autok_res[merge_count][0] == NULL) {
pr_info("[AUTOK]merge_count = %d\n", merge_count+1);
res = host->autok_res[merge_count];
ret = autok_execute_tuning(host, res);
break;
}
}
merge_mode = MERGE_HS200_SDR104;
merge_result = autok_vcore_merge_sel(host, merge_mode);
for (i = CMD_MAX_WIN; i <= H_CLK_TX_MAX_WIN; i++) {
merge_window = host->autok_res[AUTOK_VCORE_MERGE][i];
if (merge_window < AUTOK_MERGE_MIN_WIN)
merge_result = -1;
if (merge_window != 0xFF)
pr_info("[AUTOK]merge_value = %d\n", merge_window);
}
if (merge_result == 0) {
autok_tuning_parameter_init(host,
host->autok_res[AUTOK_VCORE_MERGE]);
pr_info("[AUTOK]No need change para when dvfs\n");
} else {
/* merge fail clear host->autok_res[merge_count][0] */
host->autok_res[merge_count][0] = NULL;
autok_tuning_parameter_init(host,
host->autok_res[AUTOK_VCORE_LEVEL0]);
}
return ret;
}
#endif
/*
* Vcore dvfs module MUST ensure having executed
* the function before mmcblk0 inited + 3s,
* otherwise will fail because of entering runtime
* supsend.
* invoked by SPM
*/
int emmc_autok(void)
{
#if !defined(FPGA_PLATFORM) && defined(VCOREFS_READY)
struct msdc_host *host = mtk_msdc_host[0];
void __iomem *base;
int merge_result, merge_mode, merge_window;
int i, vcore_step1 = -1, vcore_step2 = 0;
struct regulator *reg_vcore;
/*
* Static variable required by vcore dvfs module, otherwise deadlock
* happens.
*/
static struct pm_qos_request autok_force;
if (!host || !host->mmc) {
pr_notice("eMMC device not ready\n");
return -1;
}
if (!(host->mmc->caps2 & MMC_CAP2_HS400_1_8V)
&& !(host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR))
return 0;
/* Wait completion of AUTOK triggered by eMMC initialization */
if (!wait_for_completion_timeout(&host->autok_done, 10 * HZ)) {
pr_notice("eMMC 1st autok not done\n");
return -1;
}
pr_info("emmc autok\n");
base = host->base;
mmc_claim_host(host->mmc);
#ifdef CONFIG_MTK_EMMC_HW_CQ
if (emmc_autok_switch_cqe(host, 0))
pr_notice("WARN:%s:cqe disable fail", __func__);
#endif
pm_qos_add_request(&autok_force, PM_QOS_VCORE_DVFS_FORCE_OPP,
PM_QOS_VCORE_DVFS_FORCE_OPP_DEFAULT_VALUE);
for (i = 0; i < AUTOK_VCORE_NUM; i++) {
pm_qos_update_request(&autok_force, autok_opp[i]);
reg_vcore = devm_regulator_get_optional(mmc_dev(host->mmc),
"vcore");
vcore_step2 = regulator_get_voltage(reg_vcore);
if (vcore_step2 == -1) {
pr_notice("WARN:%s:get voltage fail\n", __func__);
return -1;
}
pr_notice("msdc fix vcore: %d\n", vcore_step2);
if (vcore_step2 == vcore_step1) {
pr_info("skip duplicated vcore autok\n");
memcpy(host->autok_res[i], host->autok_res[i-1],
TUNING_PARA_SCAN_COUNT);
} else {
emmc_execute_dvfs_autok(host,
MMC_SEND_TUNING_BLOCK_HS200);
if (host->use_hw_dvfs == 0)
memcpy(host->autok_res[i],
host->autok_res[AUTOK_VCORE_MERGE],
TUNING_PARA_SCAN_COUNT);
}
vcore_step1 = vcore_step2;
}
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400)
merge_mode = MERGE_HS400;
else
merge_mode = MERGE_HS200_SDR104;
merge_result = autok_vcore_merge_sel(host, merge_mode);
for (i = CMD_MAX_WIN; i <= H_CLK_TX_MAX_WIN; i++) {
merge_window = host->autok_res[AUTOK_VCORE_MERGE][i];
if (merge_window < AUTOK_MERGE_MIN_WIN)
merge_result = -1;
if (merge_window != 0xFF)
pr_info("[AUTOK]merge_value = %d\n", merge_window);
}
if (merge_result == 0) {
autok_tuning_parameter_init(host,
host->autok_res[AUTOK_VCORE_MERGE]);
pr_info("[AUTOK]No need change para when dvfs\n");
} else if (host->use_hw_dvfs == 1) {
pr_info("[AUTOK]Need change para when dvfs\n");
} else if (host->use_hw_dvfs == 0) {
autok_tuning_parameter_init(host,
host->autok_res[AUTOK_VCORE_LEVEL0]);
pr_info("[AUTOK]Need lock vcore\n");
host->lock_vcore = 1;
}
pm_qos_remove_request(&autok_force);
#ifdef CONFIG_MTK_EMMC_HW_CQ
if (emmc_autok_switch_cqe(host, 1))
pr_notice("WARN:%s:cqe enable fail", __func__);
#endif
mmc_release_host(host->mmc);
#endif
return 0;
}
EXPORT_SYMBOL(emmc_autok);
/* FIX ME: Since card can be insert at any time but this is invoked only once
* when DVFS ready, this function is not suitable for card insert after boot
*/
int sd_autok(void)
{
struct msdc_host *host = mtk_msdc_host[1];
if (!host || !host->mmc) {
pr_notice("SD card not ready\n");
return -1;
}
pr_notice("sd autok\n");
return 0;
}
EXPORT_SYMBOL(sd_autok);
int sdio_autok(void)
{
#ifdef CONFIG_MTK_SDIO_SUPPORT
struct msdc_host *host = mtk_msdc_host[2];
int timeout = 0;
while (!host || !host->hw) {
pr_notice("SDIO host not ready\n");
msleep(1000);
timeout++;
if (timeout == 20) {
pr_notice("SDIO host not exist\n");
return -1;
}
}
if (host->hw->host_function != MSDC_SDIO) {
pr_notice("SDIO device not in this host\n");
return -1;
}
pr_notice("sdio autok\n");
/* Device never ready in moudle_init.
* Call spm_msdc_dvfs_setting if device autok done.
*/
#if 0
if (!wait_for_completion_timeout(&host->autok_done, 30 * HZ)) {
pr_notice("SDIO wait device autok ready timeout");
return -1;
}
pr_notice("sdio autok done!");
#endif
#endif
return 0;
}
EXPORT_SYMBOL(sdio_autok);
void msdc_dump_autok(char **buff, unsigned long *size,
struct seq_file *m, struct msdc_host *host)
{
int i, j;
int bit_pos, byte_pos, start;
char buf[65];
SPREAD_PRINTF(buff, size, m, "[AUTOK]VER : 0x%02x%02x%02x%02x\r\n",
host->autok_res[0][AUTOK_VER3],
host->autok_res[0][AUTOK_VER2],
host->autok_res[0][AUTOK_VER1],
host->autok_res[0][AUTOK_VER0]);
for (i = AUTOK_VCORE_LEVEL1; i >= AUTOK_VCORE_LEVEL0; i--) {
start = CMD_SCAN_R0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]CMD Rising \t: %s\r\n", buf);
start = CMD_SCAN_F0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]CMD Falling \t: %s\r\n", buf);
start = DAT_SCAN_R0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DAT Rising \t: %s\r\n", buf);
start = DAT_SCAN_F0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DAT Falling \t: %s\r\n", buf);
/* cmd response use ds pin, but window is
* different with data pin, because cmd response is SDR.
*/
start = DS_CMD_SCAN_0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DS CMD Window \t: %s\r\n", buf);
start = DS_DAT_SCAN_0;
for (j = 0; j < 64; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DS DAT Window \t: %s\r\n", buf);
start = D_DATA_SCAN_0;
for (j = 0; j < 32; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]Device Data RX \t: %s\r\n", buf);
start = H_DATA_SCAN_0;
for (j = 0; j < 32; j++) {
bit_pos = j % 8;
byte_pos = j / 8 + start;
if (host->autok_res[i][byte_pos] & (1 << bit_pos))
buf[j] = 'X';
else
buf[j] = 'O';
}
buf[j] = '\0';
SPREAD_PRINTF(buff, size, m,
"[AUTOK]Host Data TX \t: %s\r\n", buf);
SPREAD_PRINTF(buff, size, m,
"[AUTOK]CMD [EDGE:%d CMD_FIFO_EDGE:%d DLY1:%d DLY2:%d]\r\n",
host->autok_res[i][0], host->autok_res[i][1],
host->autok_res[i][5], host->autok_res[i][7]);
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DAT [RDAT_EDGE:%d RD_FIFO_EDGE:%d WD_FIFO_EDGE:%d]\r\n",
host->autok_res[i][2], host->autok_res[i][3],
host->autok_res[i][4]);
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DAT [LATCH_CK:%d DLY1:%d DLY2:%d]\r\n",
host->autok_res[i][13], host->autok_res[i][9],
host->autok_res[i][11]);
SPREAD_PRINTF(buff, size, m,
"[AUTOK]DS [DLY1:%d DLY2:%d DLY3:%d]\r\n",
host->autok_res[i][14], host->autok_res[i][16],
host->autok_res[i][18]);
SPREAD_PRINTF(buff, size, m, "[AUTOK]DAT [TX SEL:%d]\r\n",
host->autok_res[i][20]);
}
}