nt9856x/BSP/linux-kernel/drivers/mmc/host/na51055_mmc.c
2023-03-28 15:07:53 +08:00

1902 lines
51 KiB
C
Executable File

/**
NVT mmc function
NVT mmc driver
@file na51055_mmc.c
@ingroup
@note
Copyright Novatek Microelectronics Corp. 2016. 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.
*/
#include "na51055_mmchost.h"
#include "na51055_mmcreg.h"
#include <linux/sysfs.h>
#include <linux/platform_device.h>
#include <linux/mmc/sd.h>
#include <plat/nvt-sramctl.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#endif
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/clk-provider.h>
/* For archs that don't support NO_IRQ (such as mips), provide a dummy value */
#ifndef NO_IRQ
#define NO_IRQ 0
#endif
static unsigned rw_threshold = 32;
module_param(rw_threshold, uint, S_IRUGO);
MODULE_PARM_DESC(rw_threshold,
"Read/Write threshold. Default = 32");
static unsigned __initdata use_dma = 1;
module_param(use_dma, uint, 0);
MODULE_PARM_DESC(use_dma, "Whether to use DMA or not. Default = 1");
static unsigned int scan_indly_engineering_mode = 0;
module_param_named(scan_indly_engineering_mode, scan_indly_engineering_mode, uint, 0600);
static int indly_debug = -1;
module_param_named(indly_debug, indly_debug, int, 0600);
static unsigned int debug_on = 0;
module_param_named(debug_on, debug_on, uint, 0600);
static unsigned int data_debug_on = 0;
module_param_named(data_debug_on, data_debug_on, uint, 0600);
static unsigned int isr_debug_on = 0;
module_param_named(isr_debug_on, isr_debug_on, uint, 0600);
static unsigned int flow_debug_on = 0;
module_param_named(flow_debug_on, flow_debug_on, uint, 0600);
static unsigned int routine_debug_on = 0;
module_param_named(routine_debug_on, routine_debug_on, uint, 0600);
#define SDIO_TR(fmt, ...) do { \
if (debug_on && host) \
pr_info("SDIO%d: "fmt, host->id, ##__VA_ARGS__); \
} while (0)
#define SDIO_DATA(fmt, ...) do { \
if (data_debug_on && host) \
pr_info("SDIO%d: "fmt, host->id, ##__VA_ARGS__); \
} while (0)
#define SDIO_ISR(fmt, ...) do { \
if (isr_debug_on && host) \
pr_info("SDIO%d: "fmt, host->id, ##__VA_ARGS__); \
} while (0)
#define SDIO_FLOW(fmt, ...) do { \
if (flow_debug_on && host) \
dev_info(mmc_dev(host->mmc), fmt, ##__VA_ARGS__); \
} while (0)
#define SDIO_ROUTINE(fmt, ...) do { \
if (routine_debug_on && host) \
dev_info(mmc_dev(host->mmc), fmt, ##__VA_ARGS__); \
} while (0)
static u32 default_pad_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_20MA, PAD_DRIVING_15MA, PAD_DRIVING_15MA, \
PAD_DRIVING_20MA, PAD_DRIVING_15MA, PAD_DRIVING_15MA, \
PAD_DRIVING_30MA, PAD_DRIVING_25MA, PAD_DRIVING_25MA, \
PAD_DRIVING_40MA, PAD_DRIVING_30MA, PAD_DRIVING_30MA};
static u32 default_pad_io_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_10MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_15MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_15MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_15MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA};
static u32 default_pad_emmc_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_6MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_16MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_16MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA, \
PAD_DRIVING_16MA, PAD_DRIVING_6MA, PAD_DRIVING_6MA};
static u32 default_pad_528_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_15MA, PAD_DRIVING_15MA, PAD_DRIVING_15MA, \
PAD_DRIVING_15MA, PAD_DRIVING_15MA, PAD_DRIVING_15MA, \
PAD_DRIVING_25MA, PAD_DRIVING_25MA, PAD_DRIVING_25MA, \
PAD_DRIVING_25MA, PAD_DRIVING_15MA, PAD_DRIVING_25MA};
static u32 default_pad_528_io_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_15MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_15MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_15MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_15MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA};
static u32 default_pad_528_emmc_driving[SDIO_MAX_MODE_DRIVING] =
{PAD_DRIVING_8MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_8MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_8MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA, \
PAD_DRIVING_8MA, PAD_DRIVING_8MA, PAD_DRIVING_8MA};
#define MAX_CMD_LENGTH 30
#define MAX_ARG_NUM 6
typedef struct proc_mmc {
struct proc_dir_entry *pproc_module_root;
struct proc_dir_entry *pproc_dbg_entry;
} proc_mmc_t;
proc_mmc_t proc_mmc;
/*=============================================================================
* proc "dbg Command" file operation functions
*=============================================================================
*/
static int nvt_mmc_proc_show(struct seq_file *sfile, void *v)
{
seq_printf(sfile, "%d\n", debug_on);
return 0;
}
static int nvt_mmc_proc_dbg_open(struct inode *inode, struct file *file)
{
return single_open(file, nvt_mmc_proc_show, NULL);
}
static int nvt_mmc_parse(unsigned char argc, char **pargv)
{
unsigned long config = 0x0;
if (argc != 1) {
pr_err("wrong argument:%d", argc);
return -EINVAL;
}
if (kstrtoul(pargv[0], 0, &config)) {
pr_err("invalid config:%s\n", pargv[0]);
return -EINVAL;
}
if (config)
debug_on = 1;
else
debug_on = 0;
return 0;
}
static ssize_t nvt_mmc_proc_dbg_write(struct file *file, const char __user *buf,
size_t size, loff_t *off)
{
int len = size;
char cmd_line[MAX_CMD_LENGTH];
char *cmdstr = cmd_line;
const char delimiters[] = {' ', 0x0A, 0x0D, '\0'};
char *argv[MAX_ARG_NUM] = {0};
unsigned char ucargc = 0;
/*check command length*/
if ((!len) || (len > (MAX_CMD_LENGTH - 1))) {
pr_err("Command length is too long or 0!\n");
goto ERR_OUT;
}
/*copy command string from user space*/
if (copy_from_user(cmd_line, buf, len)) {
goto ERR_OUT;
}
cmd_line[len - 1] = '\0';
/*parse command string*/
for (ucargc = 0; ucargc < MAX_ARG_NUM; ucargc++) {
argv[ucargc] = strsep(&cmdstr, delimiters);
if (argv[ucargc] == NULL) {
break;
}
}
if (nvt_mmc_parse(ucargc, &argv[0])) {
goto ERR_OUT;
} else {
return size;
}
ERR_OUT:
return -1;
}
static struct file_operations proc_dbg_fops = {
.owner = THIS_MODULE,
.open = nvt_mmc_proc_dbg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = nvt_mmc_proc_dbg_write
};
struct proc_dir_entry *pmodule_root = NULL;
static int nvt_mmc_proc_init(void)
{
int ret = 0;
struct proc_dir_entry *pentry = NULL;
static int reentry = 0;
if (reentry)
return 0;
pmodule_root = proc_mkdir("nvt_info/nvt_mmc", NULL);
if (pmodule_root == NULL) {
pr_err("failed to create Module root\n");
ret = -EINVAL;
goto remove_proc;
}
proc_mmc.pproc_module_root = pmodule_root;
pentry = proc_create("debug", S_IRUGO | S_IXUGO, pmodule_root, &proc_dbg_fops);
if (pentry == NULL) {
pr_err("failed to create proc cmd!\n");
ret = -EINVAL;
goto remove_proc;
}
proc_mmc.pproc_dbg_entry = pentry;
if (!ret)
reentry++;
remove_proc:
return ret;
}
static void nvt_mmc_proc_remove(void)
{
static int reentry = 0;
if (reentry)
return;
proc_remove(pmodule_root);
reentry++;
}
#if 1 // for Brcm drv
static struct mmc_nvt_host* nvt_host[] = {NULL, NULL, NULL, NULL};
void nvt_rescan_card(unsigned id, unsigned insert)
{
struct mmc_nvt_host *host = NULL;
printk("%s Enter: in \n", __FUNCTION__);
BUG_ON(id > 3);
// BUG_ON(nvt_host[id] == NULL);
if (nvt_host[id] == NULL){
printk("%s nvt_host[%d] is null \n", __FUNCTION__, id);
return;
}
host = nvt_host[id];
host->mmc->rescan_entered = 0;
mmc_detect_change(host->mmc, 0);
return;
}
EXPORT_SYMBOL_GPL(nvt_rescan_card);
#endif
static int fastboot_determination(void)
{
u32 m_fastboot = 0x0;
struct device_node* of_node = of_find_node_by_path("/fastboot");
if (of_node) {
of_property_read_u32(of_node, "enable", &m_fastboot);
}
return m_fastboot;
}
static void nvt_mmc_check_fastboot(void)
{
u32 m_fastboot = 0x0, preload = 0x0;
struct device_node* of_node = of_find_node_by_path("/fastboot");
int ret = 0;
if (of_node) {
of_property_read_u32(of_node, "enable", &m_fastboot);
}
of_node = of_find_node_by_path("/fastboot/emmc");
if (of_node) {
of_property_read_u32(of_node, "preload", &preload);
}
if (m_fastboot && preload) {
ret = nvt_mmc_check_preload_finish();
if (ret != 1)
pr_err("error with waiting preload %d\n", ret);
}
}
/* PIO only */
static void nvt_mmc_sg_to_buf(struct mmc_nvt_host *host)
{
host->buffer_bytes_left = sg_dma_len(host->sg);
host->buffer = sg_virt(host->sg);
if (host->buffer_bytes_left > host->bytes_left)
host->buffer_bytes_left = host->bytes_left;
}
static void nvt_mmc_fifo_data_trans(struct mmc_nvt_host *host, unsigned int n)
{
if (host->buffer) {
SDIO_DATA("flush buffer, data_dir(%d) buffer(0x%lx) buffer_bytes_left(0x%x), bytes_left(0x%x)\n",
host->data_dir, (uintptr_t)host->buffer, host->buffer_bytes_left, host->bytes_left);
if (host->data_dir == SDIO_HOST_WRITE_DATA) {
sdiohost_writeblock(host, host->id, (uint8_t *)host->buffer,
host->buffer_bytes_left);
host->bytes_left -= host->buffer_bytes_left;
} else {
sdiohost_readblock(host, host->id, (uint8_t *)host->buffer,
host->buffer_bytes_left);
host->bytes_left -= host->buffer_bytes_left;
}
} else {
/* Should not be here */
WARN_ON(1);
printk("[WARN]SDIO%d: buffer is null, data_dir(%d) buffer(0x%lx) buffer_bytes_left(0x%x), bytes_left(0x%x)\n",
host->id, host->data_dir, (uintptr_t)host->buffer, host->buffer_bytes_left, host->bytes_left);
}
}
static void nvt_mmc_start_command(struct mmc_nvt_host *host, struct mmc_command *cmd)
{
u32 cmd_reg = 0;
unsigned long flags;
char *s;
switch (mmc_resp_type(cmd)) {
case MMC_RSP_R1:
s = ", R1";
break;
case MMC_RSP_R1B:
s = ", R1b";
break;
case MMC_RSP_R2:
s = ", R2";
break;
case MMC_RSP_R3:
s = ", R3/R4";
break;
default:
s = ", Rx";
break;
};
SDIO_TR("CMD%d, arg 0x%08x %s\n", cmd->opcode, cmd->arg, s);
host->cmd = cmd;
switch (mmc_resp_type(cmd)) {
case MMC_RSP_R1B:
/* There's some spec confusion about when R1B is
* allowed, but if the card doesn't issue a BUSY
* then it's harmless for us to allow it.
*/
/*need to check card busy CARD_BUSY2READY bit or
*send _SDIO_SD_SEND_STATUS to check
*/
cmd_reg |= SDIO_CMD_REG_NEED_RSP;
/* FALLTHROUGH */
case MMC_RSP_R1: /* 48 bits, CRC */
cmd_reg |= SDIO_CMD_REG_NEED_RSP;
break;
case MMC_RSP_R2: /* 136 bits, CRC */
cmd_reg |= SDIO_CMD_REG_LONG_RSP;
break;
case MMC_RSP_R3: /* 48 bits, no CRC */
cmd_reg |= SDIO_CMD_REG_NEED_RSP;
break;
default:
cmd_reg |= 0;
SDIO_TR("unknown resp_type %04x\n",
mmc_resp_type(cmd));
spin_lock_irqsave(&host->lock, flags);
sdiohost_setinten(host, host->id, SDIO_STATUS_REG_CMD_SEND);
spin_unlock_irqrestore(&host->lock, flags);
break;
}
/* Set command index */
cmd_reg |= cmd->opcode;
sdiohost_sendsdcmd(host, host->id, cmd_reg, cmd->arg);
}
/*----------------------------------------------------------------------*/
static void nvt_mmc_prepare_data(struct mmc_nvt_host *host, struct mmc_request *req)
{
/*int fifo_lev = (rw_threshold == 32) ? MMCFIFOCTL_FIFOLEV : 0;*/
unsigned int timeout;
struct mmc_data *data = req->data;
int sg_len;
host->data = data;
host->data_early = 0;
if (data == NULL)
return;
timeout = data->timeout_clks +
(data->timeout_ns / host->ns_in_one_cycle);
sdiohost_setdatatimeout(host, host->id, timeout);
sdiohost_setblksize(host, host->id, data->blksz);
host->buffer = NULL;
host->bytes_left = data->blocks * data->blksz;
host->sg_len = data->sg_len;
host->sg = host->data->sg;
nvt_mmc_sg_to_buf(host);
host->data_dir = ((data->flags & MMC_DATA_WRITE) ?
SDIO_HOST_WRITE_DATA : SDIO_HOST_READ_DATA);
SDIO_DATA("%s bytes_left(0x%x) blk(%d) blksz(%d), tout(0x%x) tout_clks(%d) tout_ns(%d) cycle_ns(%d)\n",
(data->flags & MMC_DATA_WRITE) ? "write" : "read", host->bytes_left, data->blocks, data->blksz,
timeout, data->timeout_clks, data->timeout_ns, host->ns_in_one_cycle);
if ((host->mmc->ios.clock >= 50000000) && host->use_dma) {
if (host->data_dir == SDIO_HOST_WRITE_DATA) {
SDIO_DATA("over 50MHz and cmd write, blkfifoen = 0\n");
sdiohost_setblkfifoen(host, false);
} else {
SDIO_DATA("blkfifoen = 1\n");
sdiohost_setblkfifoen(host, true);
}
}
if (host->use_dma) {
#ifdef SDIO_SCATTER_DMA
int i = 0;
size_t dma_align = L1_CACHE_BYTES;
//if (data->sg_len != 1)
// printk("sg_len=%d\r\n", data->sg_len);
/*Check if sg address/length is cache alignment, do pio process if unalignment*/
for (i = 0; i < host->sg_len; i++) {
if ((!IS_ALIGNED((size_t)sg_virt(&host->sg[i]), dma_align)) ||
(!IS_ALIGNED((size_t)sg_dma_len(&data->sg[i]), dma_align))) {
SDIO_DATA("sg unalignment, sg(0x%x) sg_dma_len(0x%x) dma_align(0x%x)\n",
(size_t)sg_virt(&host->sg[i]), (size_t)sg_dma_len(&data->sg[i]), dma_align);
host->do_dma = 0;
SDIO_DATA("pio buffer(0x%lx) buffer_bytes_left(0x%x)\r\n", (uintptr_t)host->buffer, host->buffer_bytes_left);
sdiohost_setupdatatransferpio(host, host->id, \
(uint32_t)host->buffer, \
host->buffer_bytes_left, host->data_dir);
return;
}
}
memset((void *)&host->tmpdestable[0], 0,
sizeof(struct STRG_SEG_DES) * SDIO_DES_TABLE_NUM);
if (host->data_dir)
sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, DMA_FROM_DEVICE);
else
sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, DMA_TO_DEVICE);
for (i = 0; i < sg_len; i++) {
host->tmpdestable[i].uisegaddr = sg_dma_address(&data->sg[i]);
host->tmpdestable[i].uisegsize = sg_dma_len(&data->sg[i]);
SDIO_DATA("segaddr(0x%x) segsize(0x%x)\n", host->tmpdestable[i].uisegaddr, host->tmpdestable[i].uisegsize);
}
/*if (host->data_dir)
dma_sync_sg_for_device(mmc_dev(host->mmc), data->sg,
sg_len, DMA_FROM_DEVICE);
else
dma_sync_sg_for_device(mmc_dev(host->mmc), data->sg,
sg_len, DMA_TO_DEVICE);*/
/*for (i = 0; i < data->sg_len; i++) {
tmpdestable[i].uisegaddr =
(uint32_t)sg_virt(&data->sg[i]);
tmpdestable[i].uisegsize = sg_dma_len(&data->sg[i]);
}*/
sdiohost_setdestab(host->id, (uint32_t)host->tmpdestable, sg_len,
(uint32_t *)host->vuisdio_destab);
sdiohost_setdesen(host->id, 1);
SDIO_DATA("destab(0x%lx) sg_len(%d)\n", (uintptr_t)host->vuisdio_destab, sg_len);
//printk("tab addr 0x%x\r\n", (uint32_t)host->vuisdio_destab);
//printk("tab addr 0x%x, size %d\r\n", tmpdestable[0].uisegaddr, tmpdestable[0].uisegsize);
#endif
host->do_dma = 1;
SDIO_DATA("dma buffer(0x%lx) buffer_bytes_left(0x%x)\r\n", (uintptr_t)host->buffer, host->buffer_bytes_left);
//printk("dirty byte 0x%x\r\n", host->buffer[0x41]);
sdiohost_setupdatatransferdma(host, host->id,
(uint32_t)host->buffer, data->blocks * data->blksz,
host->data_dir, (uint32_t *)host->vuisdio_destab);
/* zero this to ensure we take no PIO paths */
host->bytes_left = 0;
} else {
host->do_dma = 0;
SDIO_DATA("pio buffer(0x%lx) buffer_bytes_left(0x%x)\r\n", (uintptr_t)host->buffer, host->buffer_bytes_left);
//printk("dirty byte 0x%x\r\n", host->buffer[0x41]);
sdiohost_setupdatatransferpio(host, host->id,
(uint32_t)host->buffer,
host->buffer_bytes_left, host->data_dir);
}
}
static int nvt_mmc_get_cd(struct mmc_host *mmc);
static void nvt_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
unsigned long timeout;
u32 mmcst1 = 0;
if (nvt_mmc_get_cd(mmc) == 0) {
mmc_detect_change(host->mmc, 0);
req->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, req);
return;
}
if ((clk_get_phase(host->clk)) && (host->id == SDIO_HOST_ID_1) && \
(req->cmd->opcode == SD_SWITCH_VOLTAGE))
clk_set_phase(host->clk, 0);
if (req->cmd->opcode == MMC_SEND_STATUS)
timeout = jiffies + msecs_to_jiffies(10);
else
timeout = jiffies + msecs_to_jiffies(500);
/* Card may still be sending BUSY after a previous operation,
* typically some kind of write. If so, we can't proceed yet.
*/
if ((req->data) || (req->cmd->opcode == MMC_SEND_STATUS)) {
while (time_before(jiffies, timeout)) {
mmcst1 = sdiohost_getrdy(host, host->id);
if (mmcst1 == true)
break;
cpu_relax();
}
if (mmcst1 == false) {
dev_err(mmc_dev(host->mmc), "still BUSY...%d\n", req->cmd->opcode);
req->cmd->error = -ETIMEDOUT;
mmc_request_done(mmc, req);
return;
}
}
nvt_mmc_prepare_data(host, req);
nvt_mmc_start_command(host, req->cmd);
}
static void nvt_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
SDIO_FLOW("clock(%dHz) busmode(%d) powermode(%d) Vdd(%04x) width(%u) timing(%u)\n",
ios->clock, ios->bus_mode, ios->power_mode,
ios->vdd, 1 << ios->bus_width, ios->timing);
if (ios->clock > max(400000u, host->mmc->f_min)) {
host->need_power_cycle = true;
}
if (host->need_power_cycle && (ios->clock <= max(400000u, host->mmc->f_min))) {
if (host->cd_state) {
/*A power cycle is needed to restart card identification*/
SDIO_FLOW("power cycle to restart card identification, clock(%dHz) <= f_min(%dHz)\n", ios->clock, max(400000u, host->mmc->f_min));
sdiohost_power_cycle(host, 500);
}
host->need_power_cycle = false;
}
/*The power state should be controlled by NVT instead of MMC flows*/
if (ios->clock == 0)
return;
if ((host->voltage_switch == 0) && (ios->clock >= SDIO_MODE_DS) && \
(ios->clock != mmc->f_max))
ios->clock = mmc->f_max;
switch (ios->bus_width) {
case MMC_BUS_WIDTH_8:
SDIO_FLOW("Enabling 8 bit mode\n");
sdiohost_setbuswidth(host, host->id, SDIO_BUS_WIDTH8);
break;
case MMC_BUS_WIDTH_4:
SDIO_FLOW("Enabling 4 bit mode\n");
sdiohost_setbuswidth(host, host->id, SDIO_BUS_WIDTH4);
break;
case MMC_BUS_WIDTH_1:
SDIO_FLOW("Enabling 1 bit mode\n");
sdiohost_setbuswidth(host, host->id, SDIO_BUS_WIDTH1);
break;
}
/*Set host sampling strategy according to different speeds and modes*/
if (ios->clock >= 192000000) {
//sdiohost_setphyclkindly(host, 0x9);
sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
sdiohost_setblkfifoen(host, true);
} else if (ios->clock >= 160000000) {
//sdiohost_setphyclkindly(host, 0x3);
sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
sdiohost_setblkfifoen(host, true);
} else if (ios->clock >= 120000000) {
//sdiohost_setphyclkindly(host, 0x1f);
sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
sdiohost_setblkfifoen(host, true);
} else if (ios->clock >= 96000000) {
//sdiohost_setphyclkindly(host, 0x1b);
sdiohost_setpaddriving(host, SDIO_MODE_SDR50);
sdiohost_setblkfifoen(host, true);
} else if (ios->clock >= 50000000) {
//sdiohost_setphyclkindly(host, 0x0);
sdiohost_setpaddriving(host, SDIO_MODE_SDR50);
sdiohost_setblkfifoen(host, true);
} else if (ios->clock >= 25000000) {
//sdiohost_setphyclkindly(host, 0x0);
sdiohost_setpaddriving(host, SDIO_MODE_HS);
sdiohost_setblkfifoen(host, false);
} else {
//sdiohost_setphyclkindly(host, 0x0);
sdiohost_setpaddriving(host, SDIO_MODE_DS);
sdiohost_setblkfifoen(host, false);
}
if (host->indly_sel >= 0) {
sdiohost_setphyclkindly(host, host->indly_sel);
SDIO_FLOW("use dts indly(0x%x)\n", host->indly_sel);
}
if (host->outdly_sel >= 0) {
sdiohost_setphyclkoutdly(host, host->outdly_sel);
SDIO_FLOW("use dts outdly(0x%x)\n", host->outdly_sel);
}
sdiohost_setinvoutdly(host, false, false);
/*Reset phy to take effect*/
sdiohost_setphyrst(host);
host->timing = ios->timing;
host->bus_mode = ios->bus_mode;
sdiohost_setbusclk(host, host->id, ios->clock,
(uint32_t *)&host->ns_in_one_cycle);
}
static void nvt_mmc_xfer_done(struct mmc_nvt_host *host, struct mmc_data *data)
{
host->data = NULL;
if (host->do_dma) {
if (host->data_dir)
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
DMA_FROM_DEVICE);
else
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
DMA_TO_DEVICE);
}
if (!data->stop) {
SDIO_ISR("%s: !data->stop, call mmc_request_done\r\n", __func__);
mmc_request_done(host->mmc, data->mrq);
} else if (host->cmd) {
if (host->cmd->error) {
SDIO_ISR("%s: host->cmd->error, call mmc_request_done\r\n", __func__);
mmc_request_done(host->mmc, data->mrq);
}
} else {
SDIO_ISR("%s: data->stop, call nvt_mmc_start_command\r\n", __func__);
nvt_mmc_start_command(host, data->stop);
}
}
static void nvt_mmc_cmd_done(struct mmc_nvt_host *host, struct mmc_command *cmd)
{
struct mmc_data *data = host->data;
host->cmd = NULL;
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
/* response type 2 */
sdiohost_getlongrsp(host, host->id,
(uint32_t *)&cmd->resp[0], (uint32_t *)&cmd->resp[1],
(uint32_t *)&cmd->resp[2], (uint32_t *)&cmd->resp[3]);
SDIO_TR("LRsp %x %x %x %x\r\n", cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
} else {
/* response types 1, 1b, 3, 4, 5, 6 */
sdiohost_getshortrsp(host, host->id,
(uint32_t *)&cmd->resp[0]);
SDIO_TR("SRsp %x \r\n", cmd->resp[0]);
}
}
if (data && host->data_early) {
SDIO_ISR("%s: hoat->data, call nvt_mmc_xfer_done\r\n", __func__);
nvt_mmc_xfer_done(host, data);
}
if (data == NULL) {
SDIO_ISR("%s: cmd done, call mmc_request_done\r\n", __func__);
mmc_request_done(host->mmc, cmd->mrq);
} else if (cmd->error) {
if (cmd->error == -ETIMEDOUT) {
cmd->mrq->cmd->retries = 0;
mmc_request_done(host->mmc, cmd->mrq);
SDIO_ISR("%s: cmd timeout, call mmc_request_done\r\n", __func__);
} else if (cmd->error == -EILSEQ) {
cmd->mrq->cmd->retries = 0;
if (data == NULL) {
mmc_request_done(host->mmc, cmd->mrq);
SDIO_ISR("%s: cmd crc, call mmc_request_done\r\n", __func__);
}
}
}
}
static void nvt_mmc_abort_data(struct mmc_nvt_host *host, struct mmc_data *data)
{
if (host->cmd) {
pr_info("SDIO%d nvt_mmc_abort_data %c sts 0x%x (CMD%d) \r\n", host->id, \
host->data_dir ? 'R':'W', host->status, host->cmd->opcode);
} else {
pr_info("SDIO%d nvt_mmc_abort_data %c sts 0x%x\r\n", host->id, \
host->data_dir ? 'R':'W', host->status);
}
sdiohost_resetdata(host, host->id);
}
static irqreturn_t mmc_irq_cd(int irq, void *dev_id)
{
struct mmc_nvt_host *host = (struct mmc_nvt_host *)dev_id;
mmc_detect_change(host->mmc, msecs_to_jiffies(200));
return IRQ_HANDLED;
}
static irqreturn_t nvt_mmc_irq(int irq, void *dev_id)
{
struct mmc_nvt_host *host = (struct mmc_nvt_host *)dev_id;
unsigned int qstatus;
int end_command = 0;
int end_transfer = 0;
struct mmc_data *data = host->data;
u32 tuning_cmd = sdiohost_getcmd(host);
if (!host) {
SDIO_ISR("mmc_nvt_host is null\n");
return IRQ_HANDLED;
} else if (!host->mmc) {
SDIO_ISR("mmc_host is null\n");
return IRQ_HANDLED;
}
sdiohost_disinten(host, host->id, SDIO_STATUS_REG_CMD_SEND);
qstatus = sdiohost_getstatus(host, host->id);
host->status = qstatus;
SDIO_ISR("isr status(0x%x)\n", qstatus);
if (sdiohost_getiointen(host, host->id) && (qstatus & MMCST_SDIO_INT)) {
sdiohost_setstatus(host, host->id, MMCST_SDIO_INT);
mmc_signal_sdio_irq(host->mmc);
return IRQ_HANDLED;
}
if (((qstatus & MMCST_RSP_CRC_OK) || (qstatus & MMCST_CMD_SENT)) && !(qstatus & MMCST_RSP_TIMEOUT)) {
/* End of command phase */
if (data == NULL) {
end_command = (int) host->cmd;
} else {
if ((host->do_dma == 0) && (host->bytes_left > 0)) {
/* if datasize < rw_threshold, no RX ints are generated */
nvt_mmc_fifo_data_trans(host, host->bytes_left);
/* The data status may change and needs to be updated here */
qstatus = sdiohost_getstatus(host, host->id);
host->status = qstatus;
SDIO_ISR("flush buffer done, update status(0x%x)\n", qstatus);
}
}
if (qstatus & MMCST_RSP_CRC_OK)
sdiohost_setstatus(host, host->id, MMCST_RSP_CRC_OK);
if (qstatus & MMCST_CMD_SENT)
sdiohost_setstatus(host, host->id, MMCST_CMD_SENT);
}
if (qstatus & MMCST_RSP_TIMEOUT) {
/* Command timeout */
if (host->cmd) {
host->cmd->error = -ETIMEDOUT;
if (data) {
end_transfer = 1;
end_command = 1;
nvt_mmc_abort_data(host, data);
data->bytes_xfered = 0;
data->error = -ETIMEDOUT;
SDIO_ISR("cmd with data rsp timeout (CMD%d), status(0x%x)\n", host->cmd->opcode, qstatus);
} else {
end_command = 1;
SDIO_ISR("cmd rsp timeout (CMD%d), status(0x%x)\n", host->cmd->opcode, qstatus);
}
}
sdiohost_setstatus(host, host->id, MMCST_RSP_TIMEOUT);
}
if (qstatus & MMCST_RSP_CRC_FAIL) {
/* Command CRC error */
if (host->cmd) {
host->cmd->error = -EILSEQ;
end_command = 1;
SDIO_ISR("cmd CRC error (CMD%d), status(0x%x)\n", host->cmd->opcode, qstatus);
}
sdiohost_setstatus(host, host->id, MMCST_RSP_CRC_FAIL);
}
if (qstatus & MMCST_DATA_TIMEOUT) {
/* Data timeout */
if (data) {
data->error = -ETIMEDOUT;
end_transfer = 1;
nvt_mmc_abort_data(host, data);
SDIO_ISR("data timeout, status(0x%x)\n", qstatus);
}
sdiohost_setstatus(host, host->id, MMCST_DATA_TIMEOUT);
}
if (qstatus & MMCST_DATA_CRC_FAIL) {
/* Data CRC error */
end_transfer = 1;
nvt_mmc_abort_data(host, data);
SDIO_ISR("%s data %s error, status(0x%x)\n",
(data->flags & MMC_DATA_WRITE) ? "write" : "read",
(data->error == -ETIMEDOUT) ? "timeout" : "CRC", qstatus);
sdiohost_setstatus(host, host->id, MMCST_DATA_CRC_FAIL);
}
if (qstatus & MMCST_RSP_CRC_OK) {
end_command = (int) host->cmd;
}
if ((qstatus & MMCST_DATA_END) || (qstatus & MMCST_DATA_CRC_OK)) {
end_transfer = 1;
if (tuning_cmd != MMC_SEND_TUNING_BLOCK) {
if (host->do_dma)
data->bytes_xfered = data->blocks * data->blksz;
else
data->bytes_xfered = host->buffer_bytes_left;
}
if (qstatus & MMCST_DATA_CRC_FAIL)
data->bytes_xfered = 0;
if (qstatus & MMCST_DATA_END) {
sdiohost_setstatus(host, host->id, MMCST_DATA_END);
if (tuning_cmd == MMC_SEND_TUNING_BLOCK)
complete(&host->tuning_data_end);
}
if (qstatus & MMCST_DATA_CRC_OK)
sdiohost_setstatus(host, host->id, MMCST_DATA_CRC_OK);
}
if (qstatus & (MMCST_VOL_SWITCH_END | MMCST_VOL_SWITCH_TIMEOUT)) {
if (qstatus & MMCST_VOL_SWITCH_TIMEOUT) {
host->voltage_switch_timeout = 1;
sdiohost_setstatus(host, host->id, \
MMCST_VOL_SWITCH_TIMEOUT);
}
if (qstatus & MMCST_VOL_SWITCH_END) {
sdiohost_setstatus(host, host->id, \
MMCST_VOL_SWITCH_END);
}
complete(&host->voltage_switch_complete);
}
if (end_command) {
nvt_mmc_cmd_done(host, host->cmd);
}
if (end_transfer) {
if (host->cmd) {
/*
* Data managed to finish before the
* command completed. Make sure we do
* things in the proper order.
*/
host->data_early = 1;
SDIO_ISR("data early, end_command(%d), end_transfer(%d)\r\n",
end_command, end_transfer);
} else if (data) {
if (tuning_cmd != MMC_SEND_TUNING_BLOCK)
nvt_mmc_xfer_done(host, data);
} else {
int i = 0;
/* Should not be here */
WARN_ON(1);
printk("[WARN]SDIO%d: data is null, data(%lx) host->data(%lx) sg(%lx) do_dma(%d) data_dir(%d), buffer(0x%lx) buffer_bytes_left(0x%x), bytes_left(0x%x)\n",
host->id, (uintptr_t)data, (uintptr_t)host->data, (uintptr_t)host->sg, host->do_dma, host->data_dir, (uintptr_t)host->buffer, host->buffer_bytes_left, host->bytes_left);
for (i = 0; i < host->sg_len; i++) {
printk("[WARN]SDIO%d: segaddr(0x%x) segsize(0x%x)\n", host->id, host->tmpdestable[i].uisegaddr, host->tmpdestable[i].uisegsize);
}
printk("[WARN]SDIO%d: destab(0x%lx) sg_len(%d)\n", host->id, (uintptr_t)host->vuisdio_destab, host->sg_len);
}
}
return IRQ_HANDLED;
}
static int nvt_mmc_get_cd(struct mmc_host *mmc)
{
struct platform_device *pdev = to_platform_device(mmc->parent);
struct nvt_mmc_config *config = pdev->dev.platform_data;
if (!config || !config->get_cd)
return -ENOSYS;
return config->get_cd(mmc);
}
static int nvt_mmc_get_ro(struct mmc_host *mmc)
{
struct platform_device *pdev = to_platform_device(mmc->parent);
struct nvt_mmc_config *config = pdev->dev.platform_data;
if (!config || !config->get_ro)
return -ENOSYS;
return config->get_ro(mmc);
}
static void nvt_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
if (enable) {
unsigned int status = sdiohost_getstatus(host, host->id);
/*pr_info("%x\r\n",status);*/
if (status & MMCST_SDIO_INT) {
sdiohost_setstatus(host, host->id, MMCST_SDIO_INT);
status = sdiohost_getstatus(host, host->id);
sdiohost_setstatus(host, host->id,
status & MMCST_SDIO_INT);
mmc_signal_sdio_irq(host->mmc);
} else {
host->sdio_int = true;
sdiohost_setiointen(host, host->id, TRUE);
sdiohost_getiointen(host, host->id);
}
} else {
host->sdio_int = false;
sdiohost_setiointen(host, host->id, FALSE);
sdiohost_getiointen(host, host->id);
}
}
static int nvt_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
int ret;
switch (ios->signal_voltage) {
case MMC_SIGNAL_VOLTAGE_330:
if (sdiohost_getpower(host) == SDIO_HOST_SETPOWER_VOL_3P3)
return 0;
sdiohost_setpower(host, SDIO_HOST_SETPOWER_VOL_3P3);
return 0;
case MMC_SIGNAL_VOLTAGE_180:
if (sdiohost_getpower(host) == SDIO_HOST_SETPOWER_VOL_1P8)
return 0;
sdiohost_disclockout(host, host->id);
sdiohost_setpower(host, SDIO_HOST_SETPOWER_VOL_1P8);
mdelay(400);
sdiohost_enclockout(host, host->id);
mdelay(20);
ret = sdiohost_set_voltage_switch(host);
if (!(clk_get_phase(host->clk)) && (host->id == SDIO_HOST_ID_1))
clk_set_phase(host->clk, 1);
return ret;
default:
/* No signal voltage switch required */
return 0;
}
}
static int nvt_card_busy(struct mmc_host *mmc)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
if (nvt_mmc_get_cd(mmc) == 0) return 0;
/* Check whether DAT[0] is 0 */
return !(sdiohost_getrdy(host, host->id));
}
static void nvt_mmc_check_edge(u32 ch, u32 *zeropos, u32 *zerolength)
{
u32 mask = 2;
u32 max_length = 0;
u32 max_pos = 0;
u32 this_length = 0;
u32 this_pos = 0;
u32 pos;
for (pos = 1; pos < 16; pos++) {
if ((ch & mask) == 0) {
if (this_length == 0) {
this_pos = pos;
}
this_length++;
} else {
if (this_length > max_length) {
max_length = this_length;
max_pos = this_pos;
}
this_length = 0;
}
mask = mask << 1;
}
if ((max_length == 0) || (this_length > max_length)) {
max_pos = this_pos;
max_length = this_length;
}
*zeropos = max_pos;
*zerolength = max_length;
}
#define INDLY_NUM 64
#define INDLY_OFFSET 5
#define TUNING_CYCLE_LIMIT 5
static int nvt_mmc_tuning(struct mmc_host *mmc, u32 opcode)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
int result;
u32 detout, phase_sel, length, pos;
u32 indly_result[INDLY_NUM] = {0}, tuning_cycle = 0;
int indly_t = -1;
bool find_flag = false;
u32 max_len = 0, tmp_max_len = 0;
u32 start_idx = 0, tmp_start_idx = 0;
sdiohost_setphyclrdetval(host);
init_completion(&host->tuning_data_end);
if (scan_indly_engineering_mode) {
for (indly_t = 0; indly_t < INDLY_NUM; indly_t++) {
sdiohost_setphyclkindly(host, indly_t);
result = sdiohost_tuning_cmd(host, opcode, false);
if (result) {
indly_result[indly_t] = 0;
} else {
indly_result[indly_t] = 1;
}
}
for (indly_t = 0; indly_t < INDLY_NUM; indly_t++) {
pr_err("indly = %3d, result = %d\n", indly_t, indly_result[indly_t]);
if (indly_result[indly_t] == 1) {
if (!find_flag) {
find_flag = true;
tmp_max_len = 0;
tmp_start_idx = indly_t;
}
tmp_max_len++;
if (tmp_max_len > max_len) {
max_len = tmp_max_len;
start_idx = tmp_start_idx;
}
} else {
find_flag = false;
}
}
pr_err("select indly = %3d (start_idx = %d, max_len = %d)\n", start_idx + (max_len / 2), start_idx, max_len);
sdiohost_setphyclkindly(host, start_idx + (max_len / 2));
result = sdiohost_tuning_cmd(host, opcode, true);
if (result) {
pr_err("SDIO%d Auto Tuning Cmd Fail, indly = %3d\r\n", host->id, start_idx + (max_len / 2));
}
pr_err("SDIO%d Scan indly engineering mode\r\n", host->id);
} else {
if (indly_debug >= 0) {
sdiohost_setphyclkindly(host, indly_debug);
indly_t = indly_debug;
} else if (host->indly_sel >= 0) {
sdiohost_setphyclkindly(host, host->indly_sel);
indly_t = host->indly_sel;
}
result = sdiohost_tuning_cmd(host, opcode, false);
if (result) {
pr_err("SDIO%d Tuning Cmd Fail, indly = %3d\r\n", host->id, indly_t);
if (indly_t >= 0) {
/* The tuning process should not exceed 150 ms */
while (tuning_cycle < TUNING_CYCLE_LIMIT) {
/* Do not update host->indly_sel to match the abnormal card */
indly_t += INDLY_OFFSET;
sdiohost_setphyclkindly(host, indly_t);
if (!sdiohost_tuning_cmd(host, opcode, false)) {
pr_err("reselect indly = %3d\n", indly_t);
find_flag = true;
break;
}
}
}
if (!find_flag) {
pr_err("SDIO%d Re-Tuning Cmd Fail\r\n", host->id);
return result;
}
}
}
detout = sdiohost_getphydetout(host) & 0xFFFF;
nvt_mmc_check_edge(detout, &pos, &length);
if (length < 5) {
clk_set_rate(host->clk, clk_get_rate(host->clk) >> 1);
sdiohost_setphyclrdetval(host);
result = sdiohost_tuning_cmd(host, opcode, false);
if (result) {
pr_err("SDIO%d Tuning Cmd Fail\r\n", host->id);
return result;
}
detout = sdiohost_getphydetout(host) & 0xFFFF;
nvt_mmc_check_edge(detout, &pos, &length);
if (length < 5) {
pr_warn("SDIO%d detect out zero count %d < 5\n",
host->id, length);
return E_OK;
}
}
phase_sel = pos + (length >> 1);
sdiohost_setdlyphase_sel(host, phase_sel);
sdiohost_setphyphase_cmpen(host, (1 << phase_sel));
return 0;
}
static struct mmc_host_ops nvt_mmc_ops = {
.request = nvt_mmc_request,
.set_ios = nvt_mmc_set_ios,
.get_cd = nvt_mmc_get_cd,
.get_ro = nvt_mmc_get_ro,
.enable_sdio_irq = nvt_mmc_enable_sdio_irq,
.start_signal_voltage_switch = nvt_start_signal_voltage_switch,
.execute_tuning = nvt_mmc_tuning,
.card_busy = nvt_card_busy,
};
static struct mmc_host_ops nvt_fixed_mmc_ops = {
.request = nvt_mmc_request,
.set_ios = nvt_mmc_set_ios,
.get_cd = nvt_mmc_get_cd,
.get_ro = nvt_mmc_get_ro,
.enable_sdio_irq = nvt_mmc_enable_sdio_irq,
.card_busy = nvt_card_busy,
};
/*----------------------------------------------------------------------*/
static void __init init_mmcsd_host(struct mmc_nvt_host *host, int voltage_switch)
{
sdiohost_open(host, host->id, voltage_switch);
}
static int nvt_mmc_get_cd_polled(struct mmc_host *mmc)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
SDIO_ROUTINE("nvt_mmc_get_cd! 1, cd_gpio(0x%x) cd_val(%d)\n", host->cd_gpio, gpio_get_value(host->cd_gpio));
if (host->cd_gpio) {
if (host->power_en) {
if (gpio_get_value(host->power_en) != host->power_detect_edge) {
sdiohost_power_switch(host, 0);
return 0;
} else
sdiohost_power_switch(host, 1);
}
if (gpio_get_value(host->cd_gpio) == host->cd_detect_edge) {
if (!host->cd_state)
sdiohost_power_up(host);
host->cd_state = 1;
return 1;
} else {
if (host->cd_state)
sdiohost_power_down(host);
host->cd_state = 0;
return 0;
}
} else
return 1;
}
static int nvt_mmc_get_ro_polled(struct mmc_host *mmc)
{
struct mmc_nvt_host *host = mmc_priv(mmc);
SDIO_ROUTINE("nvt_mmc_get_ro! 0\n");
if (host->ro_gpio)
return (gpio_get_value(host->ro_gpio) == host->ro_detect_edge ? 1 : 0);
else
return 0;
}
/* rescan property */
static ssize_t nvtmmc_sysfs_set_rescan(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
int is_rescan = 0;
struct mmc_nvt_host *host = NULL;
host = dev_get_drvdata(dev);
if (!host)
return -EINVAL;
if (kstrtoint(buf, 10, &is_rescan) < 0)
return -EINVAL;
if (is_rescan < 2)
nvt_rescan_card(host->id, is_rescan);
else
return -EINVAL;
return count;
}
static DEVICE_ATTR(rescan, S_IWUSR | S_IRUGO,
NULL, nvtmmc_sysfs_set_rescan);
static struct attribute *nvtmmc_sysfs_attributes[] = {
&dev_attr_rescan.attr,
NULL,
};
static const struct attribute_group nvtmmc_sysfs_attr_group = {
.attrs = nvtmmc_sysfs_attributes,
};
static int nvt_mmc_sysfs_init(struct device *dev)
{
return sysfs_create_group(&dev->kobj, &nvtmmc_sysfs_attr_group);
}
static void nvt_mmc_sysfs_remove(struct device *dev)
{
sysfs_remove_group(&dev->kobj, &nvtmmc_sysfs_attr_group);
}
static struct nvt_mmc_config nvt_mmc_config_data = {
.get_cd = nvt_mmc_get_cd_polled,
.get_ro = nvt_mmc_get_ro_polled,
.wires = 4,
.max_freq = 96000000,
.caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED,
};
static struct nvt_mmc_config nvt_mmc2_config_data = {
.get_cd = nvt_mmc_get_cd_polled,
.get_ro = nvt_mmc_get_ro_polled,
.wires = 4,
.max_freq = 192000000,
.caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED,
};
static struct nvt_mmc_config nvt_mmc3_config_data = {
.get_cd = nvt_mmc_get_cd_polled,
.get_ro = nvt_mmc_get_ro_polled,
.wires = 4,
.max_freq = 48000000,
.caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED,
};
static const struct of_device_id nvt_mmcsd_of_dt_ids[] = {
{ .compatible = "nvt,nvt_mmc", .data = &nvt_mmc_config_data },
{ .compatible = "nvt,nvt_mmc2", .data = &nvt_mmc2_config_data },
{ .compatible = "nvt,nvt_mmc3", .data = &nvt_mmc3_config_data },
{},
};
MODULE_DEVICE_TABLE(of, nvt_mmcsd_of_dt_ids);
static int __init nvt_mmcsd_probe(struct platform_device *pdev)
{
struct mmc_nvt_host *host = NULL;
struct mmc_host *mmc = NULL;
struct resource *r, *mem = NULL;
int ret = 0, irq = 0, i;
u32 cd_gpio[3] = {0}, ro_gpio[3] = {0}, power_en[2] = {0};
u32 card_power_gpio[2] = {0};
u32 force_power_cycle[2] = {0};
u32 power_down_delay_ms = 0;
u32 power_up_delay_ms = 0;
u32 pad_driving[SDIO_MAX_MODE_DRIVING] = {0};
u32 ss_clk[2] = {0};
size_t mem_size;
u32 voltage_switch = 0, max_voltage = 0, sample_edge = 0;
int indly_sel = -1, outdly_sel = -1;
int idx = -1;
const char *clk_parent = NULL;
#ifndef CONFIG_OF
struct nvt_mmc_config *pdata = pdev->dev.platform_data;
#else
struct nvt_mmc_config *pdata = NULL;
const struct of_device_id *of_id;
PIN_GROUP_CONFIG pinmux_config;
nvt_mmc_check_fastboot();
/* REVISIT: when we're fully converted, fail if pdata is NULL */
of_id = of_match_device(nvt_mmcsd_of_dt_ids, &pdev->dev);
if (!of_id) {
dev_err(&pdev->dev, "[NVT MMC] OF not found\n");
return -EINVAL;
}
pdata = (struct nvt_mmc_config *) of_id->data;
pdev->dev.platform_data = pdata;
of_property_read_u32(pdev->dev.of_node, "voltage-switch",
&voltage_switch);
of_property_read_u32(pdev->dev.of_node, "max-voltage",
&max_voltage);
of_property_read_u32(pdev->dev.of_node, "neg-sample-edge",
&sample_edge);
of_property_read_u32(pdev->dev.of_node, "indly_sel",
&indly_sel);
of_property_read_u32(pdev->dev.of_node, "outdly_sel",
&outdly_sel);
of_property_read_u32(pdev->dev.of_node, "power_down_delay_ms",
&power_down_delay_ms);
of_property_read_u32(pdev->dev.of_node, "power_up_delay_ms",
&power_up_delay_ms);
of_property_read_u32_array(pdev->dev.of_node, "cd_gpio", cd_gpio, 3);
of_property_read_u32_array(pdev->dev.of_node, "ro_gpio", ro_gpio, 3);
of_property_read_u32_array(pdev->dev.of_node, "power_en", power_en, 2);
of_property_read_u32_array(pdev->dev.of_node, \
"card_power_gpio", card_power_gpio, 2);
of_property_read_u32_array(pdev->dev.of_node, "force_power_cycle", force_power_cycle, 2);
of_property_read_u32_array(pdev->dev.of_node, "driving", pad_driving, 12);
of_property_read_u32_array(pdev->dev.of_node, "ss_clk", ss_clk, 2);
of_property_read_string(pdev->dev.of_node, "clk_parent", &clk_parent);
idx = of_alias_get_id(pdev->dev.of_node, "mmc");
#endif
ret = -ENODEV;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!r || irq == NO_IRQ)
goto out;
ret = -EBUSY;
mem_size = resource_size(r);
mem = request_mem_region(r->start, mem_size, pdev->name);
if (!mem)
goto out;
ret = -ENOMEM;
mmc = mmc_alloc_host(sizeof(struct mmc_nvt_host), &pdev->dev);
if (!mmc)
goto out;
if (idx >= 0)
mmc->index = idx;
host = mmc_priv(mmc);
host->mmc = mmc; /* Important */
host->mem_res = mem;
host->base = ioremap(mem->start, mem_size);/*mem->start;*/
if (!host->base)
goto out;
if (host->base == NVT_SDIO1_BASE_VIRT) {
host->id = SDIO_HOST_ID_1;
} else if (host->base == NVT_SDIO2_BASE_VIRT) {
host->id = SDIO_HOST_ID_2;
} else {
host->id = SDIO_HOST_ID_3;
nvt_disable_sram_shutdown(SDIO3_SD);
}
spin_lock_init(&host->lock);
/*printk("host id = %d\r\n", host->id);*/
ret = -ENXIO;
for (i = 0; i < SDIO_MAX_MODE_DRIVING; i++) {
if (pad_driving[i])
host->pad_driving[i] = pad_driving[i];
else {
if (nvt_get_chip_id() == CHIP_NA51055) {
if (host->id == SDIO_HOST_ID_1)
host->pad_driving[i] = default_pad_driving[i];
else if (host->id == SDIO_HOST_ID_2)
host->pad_driving[i] = default_pad_io_driving[i];
else
host->pad_driving[i] = default_pad_emmc_driving[i];
} else {
if (host->id == SDIO_HOST_ID_1)
host->pad_driving[i] = default_pad_528_driving[i];
else if (host->id == SDIO_HOST_ID_2)
host->pad_driving[i] = default_pad_528_io_driving[i];
else
host->pad_driving[i] = default_pad_528_emmc_driving[i];
}
}
}
if (host->id == SDIO_HOST_ID_1) {
pinmux_config.pin_function = PIN_FUNC_SDIO;
} else if (host->id == SDIO_HOST_ID_2) {
pinmux_config.pin_function = PIN_FUNC_SDIO2;
} else {
pinmux_config.pin_function = PIN_FUNC_SDIO3;
}
nvt_pinmux_capture(&pinmux_config, 1);
host->pinmux_value = pinmux_config.config;
host->use_dma = use_dma;
host->mmc_irq = irq;
host->mmc_cd_irq = gpio_to_irq(cd_gpio[0]);
host->cd_gpio = cd_gpio[0];
host->cd_detect_edge = cd_gpio[1];
host->ro_gpio = ro_gpio[0];
host->ro_detect_edge = ro_gpio[1];
host->power_en = power_en[0];
host->power_detect_edge = power_en[1];
host->cp_gpio = card_power_gpio[0];
host->cp_gpio_value = card_power_gpio[1];
host->force_power_cycle = force_power_cycle[0];
host->force_power_cycle_period = force_power_cycle[1];
host->voltage_switch = voltage_switch;
host->neg_sample_edge = sample_edge;
host->indly_sel = indly_sel;
host->outdly_sel = outdly_sel;
host->power_down_delay_ms = power_down_delay_ms;
host->power_up_delay_ms = power_up_delay_ms;
if (host->force_power_cycle) {
/* In general, the power cycle takes 500ms to 1s */
dev_info(mmc_dev(host->mmc), "%s: force power cycle, delay %d ms\n", __func__, host->force_power_cycle_period);
sdiohost_power_cycle(host, host->force_power_cycle_period);
}
if (max_voltage < VOLTAGE_3300)
sdiohost_setpower(host, SDIO_HOST_SETPOWER_VOL_1P8);
else
sdiohost_setpower(host, SDIO_HOST_SETPOWER_VOL_3P3);
if (host->cd_gpio) {
host->cd_state = gpio_get_value(host->cd_gpio) == host->cd_detect_edge ? 1 : 0;
if (!host->cd_state) {
sdiohost_power_down(host);
/*Disable card power if card power was defined*/
if (host->cp_gpio)
gpio_direction_output(host->cp_gpio, !host->cp_gpio_value);
} else {
/*Enable card power if card power was defined*/
if (host->cp_gpio)
gpio_direction_output(host->cp_gpio, host->cp_gpio_value);
}
} else {
host->cd_state = 1;
/*Enable card power if card power was defined*/
if (host->cp_gpio)
gpio_direction_output(host->cp_gpio, host->cp_gpio_value);
}
host->clk = clk_get(&pdev->dev, dev_name(&pdev->dev));
if (!IS_ERR(host->clk)) {
/* Set parent by dts */
if (clk_parent) {
if (clk_set_parent(host->clk, clk_get(NULL, clk_parent))) {
dev_err(mmc_dev(host->mmc), "%s: dts set clk_parent failed\n", __func__);
} else {
dev_info(mmc_dev(host->mmc), "%s: dts set clk_parent to %s\n", __func__, clk_parent);
}
}
/*sdio required asyn reset before operation.
In fastboot mode, the hardware sync mechanism did
prepare_enable first, and we need to eliminate it here*/
if (fastboot_determination() && __clk_is_enabled(host->clk)) {
clk_disable_unprepare(host->clk);
}
/* Setup Spread Spectrum Clock*/
if (ss_clk[0]) {
if (nvt_get_chip_id() == CHIP_NA51089) {
/* 56x sdio spread spectrum */
struct clk* pll9_clk = clk_get(NULL, "pll9");
struct clk* pll9ss_clk;
if (!IS_ERR(pll9_clk)) {
clk_set_rate(pll9_clk, ss_clk[0]);
clk_set_parent(host->clk, pll9_clk);
} else {
dev_err(mmc_dev(host->mmc), "%s: pll9 not found\n", __func__);
goto out;
}
pll9ss_clk = clk_get(NULL, "pll9_ss");
if (!IS_ERR(pll9ss_clk))
clk_set_phase(pll9ss_clk, ss_clk[1]);
else {
dev_err(mmc_dev(host->mmc), "%s: pll9ss not found\n", __func__);
goto out;
}
} else {
/* 52x sdio spread spectrum */
struct clk* pll4_clk = clk_get(NULL, "pll4");
struct clk* pll4ss_clk;
if (!IS_ERR(pll4_clk)) {
clk_set_rate(pll4_clk, ss_clk[0]);
clk_set_parent(host->clk, pll4_clk);
} else {
dev_err(mmc_dev(host->mmc), "%s: pll4 not found\n", __func__);
goto out;
}
if (nvt_get_chip_id() == CHIP_NA51055)
pll4ss_clk = clk_get(NULL, "pll4_ss");
else
pll4ss_clk = clk_get(NULL, "pll4_ss_528");
if (!IS_ERR(pll4ss_clk))
clk_set_phase(pll4ss_clk, ss_clk[1]);
else {
dev_err(mmc_dev(host->mmc), "%s: pll4ss not found\n", __func__);
goto out;
}
}
}
clk_prepare(host->clk);
clk_enable(host->clk);
} else {
dev_err(mmc_dev(host->mmc), "%s: %s not found\n", __func__, dev_name(&pdev->dev));
goto out;
}
init_mmcsd_host(host , voltage_switch);
sdiohost_set_pads(host, PAD_PULLUP);
mmc->f_min = 312500;
mmc->f_max = 24000000;
#ifndef CONFIG_OF
if (pdata && (pdata->wires == 4 || pdata->wires == 0))
mmc->caps |= MMC_CAP_4_BIT_DATA;
if (pdata && (pdata->wires == 8))
mmc->caps |= (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA);
if (pdata && pdata->max_freq)
mmc->f_max = pdata->max_freq;
#else
mmc_of_parse(host->mmc);
#endif
/* REVISIT: someday, support IRQ-driven card detection. */
/*mmc->caps |= MMC_CAP_NEEDS_POLL;*/
/*mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;*/
mmc->caps |= MMC_CAP_SDIO_IRQ;
host->version = pdata->version;
ret = nvt_mmc_sysfs_init(&pdev->dev);
if (ret < 0) {
pr_err("mmc sysfs can't be created.\n");
}
if (voltage_switch && (max_voltage > VOLTAGE_1800)) {
mmc->ops = &nvt_mmc_ops;
host->max_voltage = SDIO_HOST_SETPOWER_VOL_3P3;
} else {
mmc->ops = &nvt_fixed_mmc_ops;
if (max_voltage > VOLTAGE_1800)
host->max_voltage = SDIO_HOST_SETPOWER_VOL_3P3;
else
host->max_voltage = SDIO_HOST_SETPOWER_VOL_1P8;
}
if (pdata && pdata->caps)
mmc->caps |= pdata->caps;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
mmc->ocr_avail_sdio = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
if(voltage_switch) {
mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | \
MMC_CAP_UHS_SDR50;
if (mmc->f_max > SDIO_MODE_SDR50)
mmc->caps |= MMC_CAP_UHS_SDR104;
}
#if SDIO_SCATTER_DMA
mmc->max_segs = SDIO_DES_TABLE_NUM;/*SDIO_DES_TABLE_NUM;*/
#else
mmc->max_segs = 1 + host->n_link;
#endif
/*mmc->max_seg_size = (32*1024);*/
/* MMC/SD controller limits for multiblock requests */
mmc->max_blk_size = 2048;
mmc->max_blk_count = (32*1024);
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_seg_size = mmc->max_req_size;
dev_dbg(mmc_dev(host->mmc), "max_segs=%d\n", mmc->max_segs);
dev_dbg(mmc_dev(host->mmc), "max_blk_size=%d\n", mmc->max_blk_size);
dev_dbg(mmc_dev(host->mmc), "max_req_size=%d\n", mmc->max_req_size);
dev_dbg(mmc_dev(host->mmc), "max_seg_size=%d\n", mmc->max_seg_size);
#ifdef CONFIG_NVT_WIFI_BRCM_PM
if (host->id == SDIO_HOST_ID_2) {
mmc->pm_caps |= MMC_PM_KEEP_POWER ;
mmc->caps |= MMC_CAP_NONREMOVABLE;
}
#endif
if (cd_gpio[2]) {
if (cd_gpio[0]) {
if(gpio_is_valid(cd_gpio[0])) {
ret = request_irq(host->mmc_cd_irq, mmc_irq_cd,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "mmc_cd", host);
if (ret) {
mmc->caps |= MMC_CAP_NEEDS_POLL;
dev_err(mmc_dev(host->mmc), "cd_gpio request irq failed\n");
}
} else
mmc->caps |= MMC_CAP_NEEDS_POLL;
}
} else {
mmc->caps |= MMC_CAP_NEEDS_POLL;
dev_err(mmc_dev(host->mmc), "cd_gpio is invalid\n");
}
platform_set_drvdata(pdev, host);
#if 1 // for Brcm drv
nvt_host[host->id] = host;
#endif
ret = request_threaded_irq(irq, NULL, nvt_mmc_irq, IRQF_ONESHOT, mmc_hostname(mmc), host);
if (ret)
goto out;
if(voltage_switch)
init_completion(&host->voltage_switch_complete);
rename_region(mem, mmc_hostname(mmc));
sdio_copy_info(host);
ret = nvt_mmc_proc_init();
if (ret)
goto out;
// nvt mmc host has already finish initial, do not need to wait again.
mmc->ios.power_delay_ms = 0;
ret = mmc_add_host(mmc);
if (ret < 0)
goto out;
dev_info(mmc_dev(host->mmc), "Using %s, %d-bit mode sampling %s edge, mmc%d, pinmux(0x%x)\n",
host->use_dma ? "DMA" : "PIO",
(mmc->caps & MMC_CAP_8_BIT_DATA) ? 8 :
(mmc->caps & MMC_CAP_4_BIT_DATA) ? 4 : 1,
host->neg_sample_edge ? "Negtive" : "Positive",
host->id, host->pinmux_value);
return 0;
out:
if (host) {
if (host->clk) {
clk_disable(host->clk);
clk_put(host->clk);
}
if (host->base)
iounmap(host->base);
}
#if 1 // for Brcm drv
nvt_host[host->id] = NULL;
#endif
if (mmc)
mmc_free_host(mmc);
if (mem)
release_mem_region(mem->start, mem_size);
dev_err(mmc_dev(host->mmc), "probe err %d\n", ret);
return ret;
}
static int __exit nvt_mmcsd_remove(struct platform_device *pdev)
{
struct mmc_nvt_host *host = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (host) {
mmc_remove_host(host->mmc);
free_irq(host->mmc_irq, host);
if (gpio_is_valid(host->mmc_cd_irq))
free_irq(gpio_to_irq(host->mmc_cd_irq), host);
clk_disable(host->clk);
clk_put(host->clk);
iounmap(host->base);
release_mem_region(host->mem_res->start, resource_size(host->mem_res));
#if 1 // for Brcm drv
nvt_host[host->id] = NULL;
#endif
mmc_free_host(host->mmc);
nvt_mmc_sysfs_remove(&pdev->dev);
nvt_mmc_proc_remove();
}
return 0;
}
#ifdef CONFIG_PM
static int nvt_mmcsd_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct mmc_nvt_host *host = platform_get_drvdata(pdev);
sdiohost_resetdata(host, host->id);
clk_disable(host->clk);
disable_irq(host->mmc_irq);
sdiohost_setpower(host, SDIO_HOST_SETPOWER_VOL_0);
return 0;
}
static int nvt_mmcsd_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct mmc_nvt_host *host = platform_get_drvdata(pdev);
clk_enable(host->clk);
msleep(50);
sdiohost_setpower(host, host->max_voltage);
init_mmcsd_host(host, host->voltage_switch);
enable_irq(host->mmc_irq);
return 0;
}
static const struct dev_pm_ops nvt_mmcsd_pm = {
.suspend = nvt_mmcsd_suspend,
.resume = nvt_mmcsd_resume,
};
#define nvt_mmcsd_pm_ops (&nvt_mmcsd_pm)
#else
#define nvt_mmcsd_pm_ops NULL
#endif
static struct platform_driver nvt_mmcsd_driver = {
.probe = nvt_mmcsd_probe,
.remove = __exit_p(nvt_mmcsd_remove),
.driver = {
.name = "nvt_mmc",
.owner = THIS_MODULE,
.pm = nvt_mmcsd_pm_ops,
#ifdef CONFIG_OF
.of_match_table = nvt_mmcsd_of_dt_ids,
#endif
},
};
static int __init nvt_mmcsd_init(void)
{
return platform_driver_register(&nvt_mmcsd_driver);
}
module_init(nvt_mmcsd_init);
static void __exit nvt_mmcsd_exit(void)
{
platform_driver_unregister(&nvt_mmcsd_driver);
}
module_exit(nvt_mmcsd_exit);
MODULE_AUTHOR("Novatek");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MMC/SD driver for NOVATEK MMC controller");
MODULE_ALIAS("nvt_mmc");
MODULE_VERSION("1.07.124");