1. update cv182x/cv183x configuration file 2. update cv181x/cv180x configuration file 3. update clk driver for cvitek 4. update dma driver for cvitek 5. update soc driver for cvitek 6. porting cvitek ion driver from kernel-4.19 7. compatible with riscv Change-Id: Icff9fafe0ebe7d6bab824bbadb952e08bdc66c19
1468 lines
46 KiB
C
1468 lines
46 KiB
C
/*
|
|
* drivers/mmc/host/sdhci-cv.c - CVITEK SDHCI Platform driver
|
|
*
|
|
* Copyright (c) 2013-2014, The Linux Foundation. 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 and
|
|
* only 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/mmc/slot-gpio.h>
|
|
|
|
#include "../../core/card.h"
|
|
#include "../sdhci-pltfm.h"
|
|
#include "sdhci-cv183x.h"
|
|
|
|
#define DRIVER_NAME "cvi"
|
|
#define SDHCI_DUMP(f, x...) \
|
|
pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
|
|
|
|
#define MAX_CARD_TYPE 4
|
|
#define MAX_SPEED_MODE 5
|
|
|
|
#define CVI_PARENT "cvi"
|
|
#define CVI_STATS_PROC "cvi_info"
|
|
#define MAX_CLOCK_SCALE (4)
|
|
|
|
#define UNSTUFF_BITS(resp, start, size) \
|
|
({ \
|
|
const int __size = size; \
|
|
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
|
const int __off = 3 - ((start) / 32); \
|
|
const int __shft = (start) & 31; \
|
|
u32 __res; \
|
|
__res = resp[__off] >> __shft; \
|
|
if (__size + __shft > 32) \
|
|
__res |= resp[__off - 1] << ((32 - __shft) % 32); \
|
|
__res & __mask; \
|
|
})
|
|
|
|
static struct proc_dir_entry *proc_cvi_dir;
|
|
|
|
static char *card_type[MAX_CARD_TYPE + 1] = {
|
|
"MMC card", "SD card", "SDIO card", "SD combo (IO+mem) card", "unknown"
|
|
};
|
|
|
|
static char *cvi_get_card_type(unsigned int sd_type)
|
|
{
|
|
if (sd_type >= MAX_CARD_TYPE)
|
|
return card_type[MAX_CARD_TYPE];
|
|
else
|
|
return card_type[sd_type];
|
|
}
|
|
|
|
static inline int is_card_uhs(unsigned char timing)
|
|
{
|
|
return timing >= MMC_TIMING_UHS_SDR12 && timing <= MMC_TIMING_UHS_DDR50;
|
|
};
|
|
|
|
static inline int is_card_hs(unsigned char timing)
|
|
{
|
|
return timing == MMC_TIMING_SD_HS || timing == MMC_TIMING_MMC_HS;
|
|
};
|
|
|
|
static void cvi_stats_seq_printout(struct seq_file *s)
|
|
{
|
|
const char *type = NULL;
|
|
unsigned int present;
|
|
unsigned char timing;
|
|
struct sdhci_cvi_host *cvi_host = NULL;
|
|
struct mmc_host *mmc = NULL;
|
|
struct mmc_card *card = NULL;
|
|
const char *uhs_bus_speed_mode = "";
|
|
u32 speed_class, grade_speed_uhs;
|
|
static const char *const uhs_speeds[] = {
|
|
[UHS_SDR12_BUS_SPEED] = "SDR12 ", [UHS_SDR25_BUS_SPEED] = "SDR25 ",
|
|
[UHS_SDR50_BUS_SPEED] = "SDR50 ", [UHS_SDR104_BUS_SPEED] = "SDR104 ",
|
|
[UHS_DDR50_BUS_SPEED] = "DDR50 ",
|
|
};
|
|
cvi_host = (struct sdhci_cvi_host *)s->private;
|
|
|
|
if (!cvi_host || !cvi_host->mmc) {
|
|
seq_printf(s, "cvi s : %p: s->private %p\n", s, s->private);
|
|
return;
|
|
}
|
|
|
|
seq_printf(s, "cvi.%d", cvi_host->mmc->index);
|
|
|
|
mmc = cvi_host->mmc;
|
|
present = mmc->ops->get_cd(mmc);
|
|
|
|
if (present) {
|
|
seq_puts(s, ": plugged");
|
|
} else {
|
|
seq_puts(s, ": unplugged");
|
|
}
|
|
|
|
card = mmc->card;
|
|
|
|
if (!card) {
|
|
seq_puts(s, "_disconnected\n");
|
|
} else {
|
|
seq_puts(s, "_connected\n");
|
|
seq_printf(s, "\tType: %s", cvi_get_card_type(card->type));
|
|
|
|
//if (card->state & MMC_STATE_BLOCKADDR) {
|
|
if (mmc_card_blockaddr(card)) {
|
|
if (card->state & MMC_CARD_SDXC)
|
|
type = "SDXC";
|
|
else
|
|
type = "SDHC";
|
|
seq_printf(s, "(%s)\n", type);
|
|
}
|
|
|
|
timing = mmc->ios.timing;
|
|
|
|
if (is_card_uhs(mmc->ios.timing) &&
|
|
card->sd_bus_speed < ARRAY_SIZE(uhs_speeds))
|
|
uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];
|
|
|
|
seq_printf(s, "\tMode: %s%s%s%s\n",
|
|
is_card_uhs(timing) ? "UHS "
|
|
: (is_card_hs(timing) ? "HS " : ""),
|
|
timing == MMC_TIMING_MMC_HS400
|
|
? "HS400 "
|
|
: (timing == MMC_TIMING_MMC_HS200 ? "HS200 " : ""),
|
|
timing == MMC_TIMING_MMC_DDR52 ? "DDR " : "",
|
|
uhs_bus_speed_mode);
|
|
|
|
speed_class = UNSTUFF_BITS(card->raw_ssr, 440 - 384, 8);
|
|
grade_speed_uhs = UNSTUFF_BITS(card->raw_ssr, 396 - 384, 4);
|
|
seq_printf(s, "\tSpeed Class: Class %s\n",
|
|
(speed_class == 0x00)
|
|
? "0"
|
|
: (speed_class == 0x01)
|
|
? "2"
|
|
: (speed_class == 0x02)
|
|
? "4"
|
|
: (speed_class == 0x03)
|
|
? "6"
|
|
: (speed_class == 0x04)
|
|
? "10"
|
|
: "Reserved");
|
|
seq_printf(s, "\tUhs Speed Grade: %s\n",
|
|
(grade_speed_uhs == 0x00)
|
|
? "Less than 10MB/sec(0h)"
|
|
: (grade_speed_uhs == 0x01)
|
|
? "10MB/sec and above(1h)"
|
|
: "Reserved");
|
|
}
|
|
}
|
|
|
|
/* proc interface setup */
|
|
static void *cvi_seq_start(struct seq_file *s, loff_t *pos)
|
|
{
|
|
/* counter is used to tracking multi proc interfaces
|
|
* We have only one interface so return zero
|
|
* pointer to start the sequence.
|
|
*/
|
|
static unsigned long counter;
|
|
|
|
if (*pos == 0) {
|
|
return &counter;
|
|
}
|
|
|
|
*pos = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/* define parameters where showed in proc file */
|
|
static int cvi_stats_seq_show(struct seq_file *s, void *v)
|
|
{
|
|
cvi_stats_seq_printout(s);
|
|
return 0;
|
|
}
|
|
|
|
/* proc interface stop */
|
|
static void cvi_seq_stop(struct seq_file *s, void *v) {}
|
|
|
|
/* proc interface operation */
|
|
static const struct seq_operations cvi_stats_seq_ops = {
|
|
.start = cvi_seq_start,
|
|
.stop = cvi_seq_stop,
|
|
.show = cvi_stats_seq_show
|
|
};
|
|
|
|
/* proc file open*/
|
|
static int cvi_stats_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, cvi_stats_seq_show, PDE_DATA(inode));
|
|
};
|
|
|
|
/* proc file operation */
|
|
static const struct proc_ops cvi_stats_proc_ops = {
|
|
.proc_open = cvi_stats_proc_open,
|
|
.proc_read = seq_read,
|
|
.proc_release = single_release
|
|
};
|
|
|
|
int cvi_proc_init(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
struct proc_dir_entry *proc_stats_entry;
|
|
|
|
pr_info("%s cvi_host 0x%p\n", __func__, cvi_host);
|
|
|
|
proc_cvi_dir = proc_mkdir(CVI_PARENT, NULL);
|
|
|
|
if (!proc_cvi_dir) {
|
|
pr_err("%s: failed to create proc file %s\n", __func__, CVI_PARENT);
|
|
return 1;
|
|
}
|
|
|
|
proc_stats_entry = proc_create_data(CVI_STATS_PROC, 0400, proc_cvi_dir, &cvi_stats_proc_ops,
|
|
(void *)cvi_host);
|
|
|
|
if (!proc_stats_entry) {
|
|
pr_err("%s: failed to create proc file %s\n", __func__, CVI_STATS_PROC);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cvi_proc_shutdown(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
|
|
if (proc_cvi_dir) {
|
|
remove_proc_entry(CVI_STATS_PROC, proc_cvi_dir);
|
|
remove_proc_entry(CVI_PARENT, NULL);
|
|
proc_cvi_dir = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sdhci_cv183x_emmc_setup_pad(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
/* Name Offset
|
|
* PAD_EMMC_RSTN 0x24
|
|
* PAD_EMMC_CLK 0x20
|
|
* PAD_EMMC_CMD 0x28
|
|
* PAD_EMMC_DAT0 0x30
|
|
* PAD_EMMC_DAT1 0x2C
|
|
* PAD_EMMC_DAT2 0x34
|
|
* PAD_EMMC_DAT3 0x38
|
|
|
|
*/
|
|
|
|
u8 val = 0x0;
|
|
|
|
writeb(val, cvi_host->pinmuxbase + 0x24);
|
|
writeb(val, cvi_host->pinmuxbase + 0x20);
|
|
writeb(val, cvi_host->pinmuxbase + 0x28);
|
|
writeb(val, cvi_host->pinmuxbase + 0x30);
|
|
writeb(val, cvi_host->pinmuxbase + 0x2C);
|
|
writeb(val, cvi_host->pinmuxbase + 0x34);
|
|
writeb(val, cvi_host->pinmuxbase + 0x38);
|
|
}
|
|
|
|
static void sdhci_cv1835_sd_setup_pad(struct sdhci_host *host, bool bunplug)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
/* Name Offset unplug plug
|
|
* PAD_SDIO0_CD 0x04 SDIO0 SDIO0
|
|
* PAD_SDIO0_PWR_EN 0x14 SDIO0 SDIO0
|
|
* PAD_SDIO0_CLK 0x40 XGPIO SDIO0
|
|
* PAD_SDIO0_CMD 0x3C XGPIO SDIO0
|
|
* PAD_SDIO0_D0 0x44 XGPIO SDIO0
|
|
* PAD_SDIO0_D1 0x48 XGPIO SDIO0
|
|
* PAD_SDIO0_D2 0x4C XGPIO SDIO0
|
|
* PAD_SDIO0_D3 0x50 XGPIO SDIO0
|
|
* 0x0: SDIO0 function
|
|
* 0x3: XGPIO function
|
|
*/
|
|
|
|
u8 val = (bunplug) ? 0x3 : 0x0;
|
|
|
|
if (cvi_host->cvi_gpio && cvi_host->cvi_gpio->cd_gpio)
|
|
writeb(0x3, cvi_host->pinmuxbase + 0x04);
|
|
else
|
|
writeb(0x0, cvi_host->pinmuxbase + 0x04);
|
|
|
|
writeb(0x0, cvi_host->pinmuxbase + 0x14);
|
|
writeb(val, cvi_host->pinmuxbase + 0x40);
|
|
writeb(val, cvi_host->pinmuxbase + 0x3C);
|
|
writeb(val, cvi_host->pinmuxbase + 0x44);
|
|
writeb(val, cvi_host->pinmuxbase + 0x48);
|
|
writeb(val, cvi_host->pinmuxbase + 0x4C);
|
|
writeb(val, cvi_host->pinmuxbase + 0x50);
|
|
}
|
|
|
|
static void sdhci_cv1835_sd_setup_io(struct sdhci_host *host, bool reset)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
/*
|
|
* Name Offset reset sd0
|
|
* REG_SDIO0_CD 0xC04 PU PU
|
|
* REG_SDIO0_CLK 0xB04 PD PD
|
|
* REG_SDIO0_CMD 0xB00 PD PU
|
|
* REG_SDIO0_D0 0xB08 PD PU
|
|
* REG_SDIO0_D1 0xB0C PD PU
|
|
* REG_SDIO0_D2 0xB10 PD PU
|
|
* REG_SDIO0_D3 0xB14 PD PU
|
|
* BIT(2) : PU enable(1)/disable(0)
|
|
* BIT(3) : PD enable(1)/disable(0)
|
|
*/
|
|
|
|
u8 raise_bit = (reset) ? BIT(3) : BIT(2);
|
|
u8 down_bit = (reset) ? BIT(2) : BIT(3);
|
|
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xC04) | BIT(2)) & ~(BIT(3))),
|
|
cvi_host->pinmuxbase + 0xC04);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB04) | BIT(3)) & ~(BIT(2))),
|
|
cvi_host->pinmuxbase + 0xB04);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB00) | raise_bit) & ~(down_bit)),
|
|
cvi_host->pinmuxbase + 0xB00);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB08) | raise_bit) & ~(down_bit)),
|
|
cvi_host->pinmuxbase + 0xB08);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB0C) | raise_bit) & ~(down_bit)),
|
|
cvi_host->pinmuxbase + 0xB0C);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB10) | raise_bit) & ~(down_bit)),
|
|
cvi_host->pinmuxbase + 0xB10);
|
|
writeb(((readb(cvi_host->pinmuxbase + 0xB14) | raise_bit) & ~(down_bit)),
|
|
cvi_host->pinmuxbase + 0xB14);
|
|
}
|
|
|
|
static void sdhci_cvi_set_tap(struct sdhci_host *host, unsigned int tap)
|
|
{
|
|
pr_debug("%s %d\n", __func__, tap);
|
|
sdhci_writel(host, 0x0, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
sdhci_writel(host, 0x18, CVI_183X_SDHCI_VENDOR_A_CTRL_R);
|
|
sdhci_writel(host, tap, CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
}
|
|
|
|
static void sdhci_cvi_reset_helper(struct sdhci_host *host, u8 mask)
|
|
{
|
|
// disable Intr before reset
|
|
sdhci_writel(host, 0, SDHCI_INT_ENABLE);
|
|
sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
|
|
|
|
sdhci_reset(host, mask);
|
|
|
|
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
|
|
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
|
|
}
|
|
|
|
static void sdhci_cvi_cv1835_set_tap(struct sdhci_host *host, unsigned int tap)
|
|
{
|
|
pr_debug("%s %d\n", __func__, tap);
|
|
// Set sd_clk_en(0x2c[2]) to 0
|
|
sdhci_writew(host, sdhci_readw(host, SDHCI_CLOCK_CONTROL) & (~(0x1 << 2)), SDHCI_CLOCK_CONTROL);
|
|
sdhci_writew(host, 0, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
sdhci_writew(host, 0x18, CVI_183X_SDHCI_VENDOR_A_CTRL_R);
|
|
sdhci_writel(host, sdhci_readl(host, SDHCI_RX_DELAY_LINE) | 0x300000, SDHCI_RX_DELAY_LINE);
|
|
sdhci_writew(host, tap, CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
// Set sd_clk_en(0x2c[2]) to 1
|
|
sdhci_writew(host, sdhci_readw(host, SDHCI_CLOCK_CONTROL) | (0x1 << 2), SDHCI_CLOCK_CONTROL);
|
|
}
|
|
|
|
static void reset_after_tuning_pass(struct sdhci_host *host)
|
|
{
|
|
pr_debug("tuning pass\n");
|
|
|
|
/* Clear BUF_RD_READY intr */
|
|
sdhci_writew(host, sdhci_readw(host, SDHCI_INT_STATUS) & (~(0x1 << 5)),
|
|
SDHCI_INT_STATUS);
|
|
|
|
/* Set SDHCI_SOFTWARE_RESET.SW_RST_DAT = 1 to clear buffered tuning block */
|
|
sdhci_writeb(host, sdhci_readb(host, SDHCI_SOFTWARE_RESET) | (0x1 << 2), SDHCI_SOFTWARE_RESET);
|
|
|
|
/* Set SDHCI_SOFTWARE_RESET.SW_RST_CMD = 1 */
|
|
sdhci_writeb(host, sdhci_readb(host, SDHCI_SOFTWARE_RESET) | (0x1 << 1), SDHCI_SOFTWARE_RESET);
|
|
|
|
while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & 0x3)
|
|
;
|
|
}
|
|
|
|
static inline uint32_t CHECK_MASK_BIT(void *_mask, uint32_t bit)
|
|
{
|
|
uint32_t w = bit / 8;
|
|
uint32_t off = bit % 8;
|
|
|
|
return ((uint8_t *)_mask)[w] & (1 << off);
|
|
}
|
|
|
|
static inline void SET_MASK_BIT(void *_mask, uint32_t bit)
|
|
{
|
|
uint32_t byte = bit / 8;
|
|
uint32_t offset = bit % 8;
|
|
((uint8_t *)_mask)[byte] |= (1 << offset);
|
|
}
|
|
|
|
int sdhci_cv1835_general_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|
{
|
|
u16 min = 0;
|
|
u32 k = 0;
|
|
s32 ret;
|
|
u32 retry_cnt = 0;
|
|
|
|
u32 tuning_result[4] = {0, 0, 0, 0};
|
|
u32 rx_lead_lag_result[4] = {0, 0, 0, 0};
|
|
char tuning_graph[TUNE_MAX_PHCODE+1];
|
|
char rx_lead_lag_graph[TUNE_MAX_PHCODE+1];
|
|
|
|
u32 reg = 0;
|
|
u32 reg_rx_lead_lag = 0;
|
|
s32 max_lead_lag_idx = -1;
|
|
s32 max_window_idx = -1;
|
|
s32 cur_window_idx = -1;
|
|
u16 max_lead_lag_size = 0;
|
|
u16 max_window_size = 0;
|
|
u16 cur_window_size = 0;
|
|
s32 rx_lead_lag_phase = -1;
|
|
s32 final_tap = -1;
|
|
u32 rate = 0;
|
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
sdhci_writel(host, 0x0, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
|
|
reg = sdhci_readw(host, SDHCI_ERR_INT_STATUS);
|
|
pr_debug("%s : SDHCI_ERR_INT_STATUS 0x%x\n", mmc_hostname(host->mmc),
|
|
reg);
|
|
|
|
reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
pr_debug("%s : host ctrl2 0x%x\n", mmc_hostname(host->mmc), reg);
|
|
/* Set Host_CTRL2_R.SAMPLE_CLK_SEL=0 */
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, SDHCI_HOST_CONTROL2) & (~(0x1 << 7)),
|
|
SDHCI_HOST_CONTROL2);
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, SDHCI_HOST_CONTROL2) & (~(0x3 << 4)),
|
|
SDHCI_HOST_CONTROL2);
|
|
|
|
reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
pr_debug("%s : host ctrl2 0x%x\n", mmc_hostname(host->mmc), reg);
|
|
|
|
/* Set ATR_CTRL_R.SW_TNE_EN=1 */
|
|
reg = sdhci_readl(host, CVI_183X_SDHCI_VENDOR_A_CTRL_R);
|
|
pr_debug("%s : A ctrl 0x%x\n", mmc_hostname(host->mmc), reg);
|
|
sdhci_writel(host,
|
|
sdhci_readl(host, CVI_183X_SDHCI_VENDOR_A_CTRL_R) | (0x1<<4),
|
|
CVI_183X_SDHCI_VENDOR_A_CTRL_R);
|
|
reg = sdhci_readl(host, CVI_183X_SDHCI_VENDOR_A_CTRL_R);
|
|
pr_debug("%s : A ctrl 0x%x\n", mmc_hostname(host->mmc), reg);
|
|
|
|
while (min < TUNE_MAX_PHCODE) {
|
|
retry_cnt = 0;
|
|
sdhci_cvi_cv1835_set_tap(host, min);
|
|
reg_rx_lead_lag = sdhci_readw(host, SDHCI_LEAD_LAG_FLAG) & BIT(1);
|
|
|
|
retry_tuning:
|
|
ret = mmc_send_tuning(host->mmc, opcode, NULL);
|
|
|
|
if (!ret && retry_cnt < MAX_TUNING_CMD_RETRY_COUNT) {
|
|
retry_cnt++;
|
|
goto retry_tuning;
|
|
}
|
|
|
|
if (ret) {
|
|
SET_MASK_BIT(tuning_result, min);
|
|
}
|
|
|
|
if (reg_rx_lead_lag) {
|
|
SET_MASK_BIT(rx_lead_lag_result, min);
|
|
}
|
|
|
|
min++;
|
|
}
|
|
|
|
reset_after_tuning_pass(host);
|
|
|
|
pr_debug("tuning result: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
|
tuning_result[0], tuning_result[1], tuning_result[2], tuning_result[3]);
|
|
pr_debug("rx_lead_lag result: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
|
rx_lead_lag_result[0], rx_lead_lag_result[1], rx_lead_lag_result[2], rx_lead_lag_result[3]);
|
|
for (k = 0; k < TUNE_MAX_PHCODE; k++) {
|
|
if (CHECK_MASK_BIT(tuning_result, k) == 0)
|
|
tuning_graph[k] = '-';
|
|
else
|
|
tuning_graph[k] = 'x';
|
|
if (CHECK_MASK_BIT(rx_lead_lag_result, k) == 0)
|
|
rx_lead_lag_graph[k] = '0';
|
|
else
|
|
rx_lead_lag_graph[k] = '1';
|
|
}
|
|
tuning_graph[TUNE_MAX_PHCODE] = '\0';
|
|
rx_lead_lag_graph[TUNE_MAX_PHCODE] = '\0';
|
|
|
|
pr_debug("tuning graph: %s\n", tuning_graph);
|
|
pr_debug("rx_lead_lag graph: %s\n", rx_lead_lag_graph);
|
|
|
|
// Find a final tap as median of maximum window
|
|
for (k = 0; k < TUNE_MAX_PHCODE; k++) {
|
|
if (CHECK_MASK_BIT(tuning_result, k) == 0) {
|
|
if (-1 == cur_window_idx) {
|
|
cur_window_idx = k;
|
|
}
|
|
cur_window_size++;
|
|
|
|
if (cur_window_size > max_window_size) {
|
|
max_window_size = cur_window_size;
|
|
max_window_idx = cur_window_idx;
|
|
if (max_window_size >= TAP_WINDOW_THLD)
|
|
final_tap = cur_window_idx + (max_window_size/2);
|
|
}
|
|
} else {
|
|
cur_window_idx = -1;
|
|
cur_window_size = 0;
|
|
}
|
|
}
|
|
|
|
cur_window_idx = -1;
|
|
cur_window_size = 0;
|
|
for (k = 0; k < TUNE_MAX_PHCODE; k++) {
|
|
if (CHECK_MASK_BIT(rx_lead_lag_result, k) == 0) {
|
|
//from 1 to 0 and window_size already computed.
|
|
if ((rx_lead_lag_phase == 1) && (cur_window_size > 0)) {
|
|
max_lead_lag_idx = cur_window_idx;
|
|
max_lead_lag_size = cur_window_size;
|
|
break;
|
|
}
|
|
if (cur_window_idx == -1) {
|
|
cur_window_idx = k;
|
|
}
|
|
cur_window_size++;
|
|
rx_lead_lag_phase = 0;
|
|
} else {
|
|
rx_lead_lag_phase = 1;
|
|
if ((cur_window_idx != -1) && (cur_window_size > 0)) {
|
|
cur_window_size++;
|
|
max_lead_lag_idx = cur_window_idx;
|
|
max_lead_lag_size = cur_window_size;
|
|
} else {
|
|
cur_window_size = 0;
|
|
}
|
|
}
|
|
}
|
|
rate = max_window_size * 100 / max_lead_lag_size;
|
|
pr_debug("MaxWindow[Idx, Width]:[%d,%u] Tuning Tap: %d\n", max_window_idx, max_window_size, final_tap);
|
|
pr_debug("RX_LeadLag[Idx, Width]:[%d,%u] rate = %d\n", max_lead_lag_idx, max_lead_lag_size, rate);
|
|
|
|
sdhci_cvi_cv1835_set_tap(host, final_tap);
|
|
cvi_host->final_tap = final_tap;
|
|
pr_debug("%s finished tuning, code:%d\n", __func__, final_tap);
|
|
|
|
return mmc_send_tuning(host->mmc, opcode, NULL);
|
|
}
|
|
|
|
static int sdhci_cv1835_general_select_drive_strength(struct sdhci_host *host,
|
|
struct mmc_card *card, unsigned int max_dtr, int host_drv,
|
|
int card_drv, int *drv_type)
|
|
{
|
|
int driver_type = 0;
|
|
uint32_t reg;
|
|
|
|
pr_debug(" max_dtr %d, host_drv %d, card_drv %d, drv_type %d\n",
|
|
max_dtr, host_drv, card_drv, *drv_type);
|
|
|
|
driver_type = MMC_SET_DRIVER_TYPE_A;
|
|
*drv_type = MMC_SET_DRIVER_TYPE_A;
|
|
reg = (1 << PHY_CNFG_PHY_PWRGOOD) | (0xe << PHY_CNFG_PAD_SP) |
|
|
(0xe << PHY_CNFG_PAD_SN) | (1 << PHY_CNFG_PHY_RSTN);
|
|
sdhci_writel(host, reg, SDHCI_P_PHY_CNFG);
|
|
|
|
return driver_type;
|
|
}
|
|
|
|
static void sdhci_cvi_general_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
|
|
{
|
|
struct mmc_host *mmc = host->mmc;
|
|
u16 ctrl_2;
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
/* Select Bus Speed Mode for host */
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
switch (uhs) {
|
|
case MMC_TIMING_UHS_SDR12:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
break;
|
|
case MMC_TIMING_UHS_SDR25:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
break;
|
|
case MMC_TIMING_UHS_SDR50:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
|
break;
|
|
case MMC_TIMING_MMC_HS200:
|
|
case MMC_TIMING_UHS_SDR104:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
|
break;
|
|
case MMC_TIMING_UHS_DDR50:
|
|
case MMC_TIMING_MMC_DDR52:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* When clock frequency is less than 100MHz, the feedback clock must be
|
|
* provided and DLL must not be used so that tuning can be skipped. To
|
|
* provide feedback clock, the mode selection can be any value less
|
|
* than 3'b011 in bits [2:0] of HOST CONTROL2 register.
|
|
*/
|
|
if (host->clock <= 100000000 &&
|
|
(uhs == MMC_TIMING_MMC_HS400 ||
|
|
uhs == MMC_TIMING_MMC_HS200 ||
|
|
uhs == MMC_TIMING_UHS_SDR104))
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
|
|
dev_dbg(mmc_dev(mmc), "%s: clock=%u uhs=%u ctrl_2=0x%x\n",
|
|
mmc_hostname(host->mmc), host->clock, uhs, ctrl_2);
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
}
|
|
|
|
static int sdhci_cv1835_sdio_get_cd(struct mmc_host *mmc)
|
|
{
|
|
struct sdhci_host *host = mmc_priv(mmc);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
int gpio_on = 0;
|
|
|
|
pr_debug("%s(%d) mmc %p, host %p\n", __func__, __LINE__, mmc, host);
|
|
|
|
if (gpio_is_valid(cvi_host->gpio_card_cd)) {
|
|
pr_debug("host->gpio_card_cd %d\n", cvi_host->gpio_card_cd);
|
|
gpio_on = gpio_get_value(cvi_host->gpio_card_cd);
|
|
|
|
if (gpio_on)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int sdhci_cvi_general_emmc_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
uint32_t clk = 375 * 1000 * 1000;
|
|
|
|
pr_debug(DRIVER_NAME ":%s : %d\n", __func__, clk);
|
|
return clk;
|
|
}
|
|
|
|
static unsigned int sdhci_cv1835_sd_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
uint32_t clk = 375 * 1000 * 1000;
|
|
|
|
pr_debug(DRIVER_NAME ":%s : %d\n", __func__, clk);
|
|
return clk;
|
|
}
|
|
|
|
static unsigned int sdhci_cvi_fpga_emmc_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
uint32_t clk = 25 * 1000 * 1000;
|
|
|
|
pr_debug(DRIVER_NAME ":%s : %d\n", __func__, clk);
|
|
return clk;
|
|
}
|
|
|
|
static unsigned int sdhci_cvi_fpga_sd_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
uint32_t clk = 25 * 1000 * 1000;
|
|
|
|
pr_debug(DRIVER_NAME ":%s : %d\n", __func__, clk);
|
|
return clk;
|
|
}
|
|
|
|
static void sdhci_cv1835_sd_reset(struct sdhci_host *host, u8 mask)
|
|
{
|
|
u16 ctrl_2;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
pr_debug("%s mask = 0x%x\n", __func__, mask);
|
|
sdhci_cvi_reset_helper(host, mask);
|
|
|
|
if (mask & SDHCI_RESET_ALL) {
|
|
// revert tx
|
|
sdhci_writeb(host, 0x10, SDHCI_P_SDCLKDL_DC);
|
|
// revert rx
|
|
sdhci_cvi_set_tap(host, 0x00);
|
|
}
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
ctrl_2 &= SDHCI_CTRL_UHS_MASK;
|
|
if (ctrl_2 == SDHCI_CTRL_UHS_SDR104) {
|
|
//reg_0x508[0] = 0
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R) & (~(BIT(0))),
|
|
CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
//reg_0x30C[21:20] = 3
|
|
sdhci_writel(host,
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE) | (BIT(21) | BIT(20)),
|
|
SDHCI_RX_DELAY_LINE);
|
|
//reg_0x31C[22:16] = 0, reg_0x31C[11:10] = 0
|
|
sdhci_writel(host, sdhci_readl(host, SDHCI_TX_DELAY_LINE) & (~(0x7F0C00)), SDHCI_TX_DELAY_LINE);
|
|
//reg_0x544[6:0] = tap
|
|
sdhci_writew(host, cvi_host->final_tap & 0x7F, CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
} else {
|
|
//Reset as DS/HS setting.
|
|
//reg_0x508[0] = 1
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R) | BIT(0),
|
|
CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
//reg_0x30C[21:20] = 0
|
|
sdhci_writel(host,
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE) & (~(BIT(21) | BIT(20))),
|
|
SDHCI_RX_DELAY_LINE);
|
|
//reg_0x31C[22:16] = 0, reg_0x31C[11:10] = 0
|
|
sdhci_writel(host, sdhci_readl(host, SDHCI_TX_DELAY_LINE) & (~(0x7F0C00)), SDHCI_TX_DELAY_LINE);
|
|
//reg_0x544[6:0] = 0
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_A_STAT_R) & (~(0x7F)),
|
|
CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
}
|
|
}
|
|
|
|
static void sdhci_cv1835_emmc_reset(struct sdhci_host *host, u8 mask)
|
|
{
|
|
u16 ctrl_2;
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
pr_debug("%s mask = 0x%x\n", __func__, mask);
|
|
sdhci_cvi_reset_helper(host, mask);
|
|
|
|
if (mask & SDHCI_RESET_ALL) {
|
|
// revert tx
|
|
sdhci_writeb(host, 0x10, SDHCI_P_SDCLKDL_DC);
|
|
// revert rx
|
|
sdhci_cvi_set_tap(host, 0x10);
|
|
}
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
ctrl_2 &= SDHCI_CTRL_UHS_MASK;
|
|
if (ctrl_2 == SDHCI_CTRL_UHS_SDR104) {
|
|
//Reset as HS200/SDR104 mode
|
|
//reg_0x508[0] = 0
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R) & (~(BIT(0))),
|
|
CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
//reg_0x30C[21:20] = 3
|
|
sdhci_writel(host,
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE) | (BIT(21) | BIT(20)),
|
|
SDHCI_RX_DELAY_LINE);
|
|
//reg_0x31C[22:16] = 0, reg_0x31C[11:10] = 0
|
|
sdhci_writel(host, sdhci_readl(host, SDHCI_TX_DELAY_LINE) & (~(0x7F0C00)), SDHCI_TX_DELAY_LINE);
|
|
//reg_0x544[6:0] = tap
|
|
sdhci_writew(host, cvi_host->final_tap & 0x7F, CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
} else {
|
|
//Reset as DS/HS setting.
|
|
//reg_0x508[0] = 1
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R) | BIT(0),
|
|
CVI_183X_SDHCI_VENDOR_MSHC_CTRL_R);
|
|
//reg_0x30C[21:20] = 0
|
|
sdhci_writel(host,
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE) & (~(BIT(21) | BIT(20))),
|
|
SDHCI_RX_DELAY_LINE);
|
|
//reg_0x31C[22:16] = 0, reg_0x31C[11:10] = 0
|
|
sdhci_writel(host, sdhci_readl(host, SDHCI_TX_DELAY_LINE) & (~(0x7F0C00)), SDHCI_TX_DELAY_LINE);
|
|
//reg_0x544[6:0] = 0
|
|
sdhci_writew(host,
|
|
sdhci_readw(host, CVI_183X_SDHCI_VENDOR_A_STAT_R) & (~(0x7F)),
|
|
CVI_183X_SDHCI_VENDOR_A_STAT_R);
|
|
}
|
|
}
|
|
|
|
|
|
/* Used for wifi driver due if no SD card detect pin implemented */
|
|
static struct mmc_host *wifi_mmc;
|
|
|
|
int cvi_sdio_rescan(void)
|
|
{
|
|
|
|
if (!wifi_mmc) {
|
|
pr_err("invalid wifi mmc, please check the argument\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mmc_detect_change(wifi_mmc, 0);
|
|
|
|
wifi_mmc->rescan_entered = 0;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cvi_sdio_rescan);
|
|
|
|
|
|
void sdhci_cvi_emmc_voltage_switch(struct sdhci_host *host)
|
|
{
|
|
}
|
|
|
|
void sdhci_cv1835_sd_voltage_switch(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
// enable SDIO0_CLK[7:5] to set CLK max strengh
|
|
writeb((readb(cvi_host->pinmuxbase + 0xB04) | BIT(7) | BIT(6) | BIT(5)),
|
|
cvi_host->pinmuxbase + 0xB04);
|
|
|
|
//Voltage switching flow (1.8v)
|
|
//reg_pwrsw_auto=1, reg_pwrsw_disc=0, pwrsw_vsel=1(1.8v), reg_en_pwrsw=1
|
|
writel(0xB | (readl(cvi_host->topbase + OFFSET_SD_PWRSW_CTRL) & 0xFFFFFFF0),
|
|
cvi_host->topbase + OFFSET_SD_PWRSW_CTRL);
|
|
pr_debug("sd PWRSW 0x%x\n", readl(cvi_host->topbase + 0x1F4));
|
|
cvi_host->sdio0_voltage_1_8_v = 1;
|
|
|
|
//set SDIO0 PAD to 1.8V mode
|
|
//=> 0x03000018[5:4] = 2'b11 ,( reg_sd0_ms_ow=1. reg_sd0_ms_sw=0) => MS overwrite to 1 (1.8v mode)
|
|
writel(BIT(4) | BIT(5) | readl(cvi_host->topbase + 0x18), cvi_host->topbase + 0x18);
|
|
pr_debug("SDIO0 PAD 0x%x\n", readl(cvi_host->topbase + 0x18));
|
|
|
|
//wait 1ms
|
|
mdelay(1);
|
|
}
|
|
|
|
void sdhci_cv1835_sd_voltage_restore(struct sdhci_host *host, bool bunplug)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
|
|
if (bunplug) {
|
|
// set SDIO0 PAD to 3.3V mode
|
|
// reg_sd0_ms_ow=1. reg_sd0_ms_sw=0) => MS overwrite to 0 (3.0v mode)
|
|
writel(BIT(4) | (readl(cvi_host->topbase + 0x18) & 0xFFFFFFCF), cvi_host->topbase + 0x18);
|
|
pr_debug("SDIO0 PAD 0x%x\n", readl(cvi_host->topbase + 0x18));
|
|
|
|
//Voltage close flow
|
|
//(reg_pwrsw_auto=1, reg_pwrsw_disc=1, reg_pwrsw_vsel=1(1.8v), reg_en_pwrsw=0)
|
|
writel(0xE | (readl(cvi_host->topbase + OFFSET_SD_PWRSW_CTRL) & 0xFFFFFFF0),
|
|
cvi_host->topbase + OFFSET_SD_PWRSW_CTRL);
|
|
cvi_host->sdio0_voltage_1_8_v = 0;
|
|
} else {
|
|
if (!cvi_host->sdio0_voltage_1_8_v) {
|
|
|
|
// set SDIO0 PAD to 3.3V mode
|
|
// reg_sd0_ms_ow=1. reg_sd0_ms_sw=0) => MS overwrite to 0 (3.0v mode)
|
|
writel(BIT(4) | (readl(cvi_host->topbase + 0x18) & 0xFFFFFFCF), cvi_host->topbase + 0x18);
|
|
pr_debug("SDIO0 PAD 0x%x\n", readl(cvi_host->topbase + 0x18));
|
|
|
|
//Voltage switching flow (3.3)
|
|
//(reg_pwrsw_auto=1, reg_pwrsw_disc=0, reg_pwrsw_vsel=0(3.0v), reg_en_pwrsw=1)
|
|
writel(0x9 | (readl(cvi_host->topbase + OFFSET_SD_PWRSW_CTRL) & 0xFFFFFFF0),
|
|
cvi_host->topbase + OFFSET_SD_PWRSW_CTRL);
|
|
}
|
|
}
|
|
|
|
//wait 1ms
|
|
mdelay(1);
|
|
}
|
|
|
|
static void sdhci_cv1835_sd_set_power(struct sdhci_host *host, unsigned char mode,
|
|
unsigned short vdd)
|
|
{
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
pr_debug("%s:mode %d, vdd %d\n", __func__, mode, vdd);
|
|
|
|
if (mode == MMC_POWER_ON && mmc->ops->get_cd(mmc)) {
|
|
sdhci_set_power_noreg(host, mode, vdd);
|
|
sdhci_cv1835_sd_voltage_restore(host, false);
|
|
sdhci_cv1835_sd_setup_pad(host, false);
|
|
sdhci_cv1835_sd_setup_io(host, false);
|
|
mdelay(5);
|
|
} else if (mode == MMC_POWER_OFF) {
|
|
sdhci_cv1835_sd_setup_pad(host, true);
|
|
sdhci_cv1835_sd_setup_io(host, true);
|
|
sdhci_cv1835_sd_voltage_restore(host, true);
|
|
sdhci_set_power_noreg(host, mode, vdd);
|
|
mdelay(30);
|
|
}
|
|
}
|
|
|
|
static void sdhci_cv1835_emmc_dump_vendor_regs(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
u8 clk_source_select = 0;
|
|
u8 PAD_EMMC_RSTN = 0;
|
|
u8 PAD_EMMC_CLK = 0;
|
|
u8 PAD_EMMC_CMD = 0;
|
|
u8 PAD_EMMC_DAT0 = 0;
|
|
u8 PAD_EMMC_DAT1 = 0;
|
|
u8 PAD_EMMC_DAT2 = 0;
|
|
u8 PAD_EMMC_DAT3 = 0;
|
|
u8 REG_EMMC_RSTN = 0;
|
|
u8 REG_EMMC_CLK = 0;
|
|
u8 REG_EMMC_CMD = 0;
|
|
u8 REG_EMMC_DAT0 = 0;
|
|
u8 REG_EMMC_DAT1 = 0;
|
|
u8 REG_EMMC_DAT2 = 0;
|
|
u8 REG_EMMC_DAT3 = 0;
|
|
|
|
SDHCI_DUMP(": Reg_544: 0x%08x | Reg_30C: 0x%08x\n",
|
|
sdhci_readl(host, CVI_183X_SDHCI_VENDOR_A_STAT_R),
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE));
|
|
SDHCI_DUMP(": Reg_31C: 0x%08x | Reg_312: 0x%08x\n",
|
|
sdhci_readl(host, SDHCI_TX_DELAY_LINE),
|
|
sdhci_readw(host, SDHCI_LEAD_LAG_FLAG));
|
|
|
|
PAD_EMMC_RSTN = readb(cvi_host->pinmuxbase + 0x24) & 0x07;
|
|
PAD_EMMC_CLK = readb(cvi_host->pinmuxbase + 0x20) & 0x07;
|
|
PAD_EMMC_CMD = readb(cvi_host->pinmuxbase + 0x28) & 0x07;
|
|
PAD_EMMC_DAT0 = readb(cvi_host->pinmuxbase + 0x30) & 0x07;
|
|
PAD_EMMC_DAT1 = readb(cvi_host->pinmuxbase + 0x2C) & 0x07;
|
|
PAD_EMMC_DAT2 = readb(cvi_host->pinmuxbase + 0x34) & 0x07;
|
|
PAD_EMMC_DAT3 = readb(cvi_host->pinmuxbase + 0x38) & 0x07;
|
|
REG_EMMC_RSTN = readb(cvi_host->pinmuxbase + 0xC24);
|
|
REG_EMMC_CLK = readb(cvi_host->pinmuxbase + 0xC20);
|
|
REG_EMMC_CMD = readb(cvi_host->pinmuxbase + 0xC28);
|
|
REG_EMMC_DAT0 = readb(cvi_host->pinmuxbase + 0xC30);
|
|
REG_EMMC_DAT1 = readb(cvi_host->pinmuxbase + 0xC2C);
|
|
REG_EMMC_DAT2 = readb(cvi_host->pinmuxbase + 0xC34);
|
|
REG_EMMC_DAT3 = readb(cvi_host->pinmuxbase + 0xC38);
|
|
|
|
SDHCI_DUMP(": PAD_EMMC_RSTN:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_RSTN, (REG_EMMC_RSTN & 0x04)>>2, (REG_EMMC_RSTN & 0x08)>>3,
|
|
(REG_EMMC_RSTN & 0x80)>>7, (REG_EMMC_RSTN & 0x40)>>6, (REG_EMMC_RSTN & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_CLK:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_CLK, (REG_EMMC_CLK & 0x04)>>2, (REG_EMMC_CLK & 0x08)>>3,
|
|
(REG_EMMC_CLK & 0x80)>>7, (REG_EMMC_CLK & 0x40)>>6, (REG_EMMC_CLK & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_CMD:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_CMD, (REG_EMMC_CMD & 0x04)>>2, (REG_EMMC_CMD & 0x08)>>3,
|
|
(REG_EMMC_CMD & 0x80)>>7, (REG_EMMC_CMD & 0x40)>>6, (REG_EMMC_CMD & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_DAT0:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_DAT0, (REG_EMMC_DAT0 & 0x04)>>2, (REG_EMMC_DAT0 & 0x08)>>3,
|
|
(REG_EMMC_DAT0 & 0x80)>>7, (REG_EMMC_DAT0 & 0x40)>>6, (REG_EMMC_DAT0 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_DAT1:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_DAT1, (REG_EMMC_DAT1 & 0x04)>>2, (REG_EMMC_DAT1 & 0x08)>>3,
|
|
(REG_EMMC_DAT1 & 0x80)>>7, (REG_EMMC_DAT1 & 0x40)>>6, (REG_EMMC_DAT1 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_DAT2:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_DAT2, (REG_EMMC_DAT2 & 0x04)>>2, (REG_EMMC_DAT2 & 0x08)>>3,
|
|
(REG_EMMC_DAT2 & 0x80)>>7, (REG_EMMC_DAT2 & 0x40)>>6, (REG_EMMC_DAT2 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_EMMC_DAT3:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_EMMC_DAT3, (REG_EMMC_DAT3 & 0x04)>>2, (REG_EMMC_DAT3 & 0x08)>>3,
|
|
(REG_EMMC_DAT3 & 0x80)>>7, (REG_EMMC_DAT3 & 0x40)>>6, (REG_EMMC_DAT3 & 0x20)>>5);
|
|
|
|
clk_source_select = (readb(cvi_host->clkgenbase + 0x20) & 0x20) >> 5;
|
|
|
|
SDHCI_DUMP(": clk_emmc enable[16]:0x%08x\n", readl(cvi_host->clkgenbase));
|
|
SDHCI_DUMP(": clk_emmc source_select:%u\n", clk_source_select);
|
|
if (clk_source_select == 0) {
|
|
SDHCI_DUMP(": clk_emmc REG:0x03002068 = 0x%08x\n",
|
|
readl(cvi_host->clkgenbase + 0x68));
|
|
if (readl(cvi_host->clkgenbase + 0x68) == 0x00000001)
|
|
SDHCI_DUMP(": clk_emmc %d MHz\n", DISPPLL_MHZ/12);
|
|
} else if (clk_source_select == 1) {
|
|
SDHCI_DUMP(": clk_emmc REG:0x03002064 = 0x%08x\n",
|
|
readl(cvi_host->clkgenbase + 0x64));
|
|
if (readl(cvi_host->clkgenbase + 0x64) == 0x00040009)
|
|
SDHCI_DUMP(": clk_emmc %d MHz\n", FPLL_MHZ/4);
|
|
}
|
|
}
|
|
|
|
static void sdhci_cv1835_sd_dump_vendor_regs(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
u8 clk_source_select = 0;
|
|
u8 PAD_SDIO0_PWR_EN = 0;
|
|
u8 PAD_SDIO0_CD = 0;
|
|
u8 PAD_SDIO0_CLK = 0;
|
|
u8 PAD_SDIO0_CMD = 0;
|
|
u8 PAD_SDIO0_D0 = 0;
|
|
u8 PAD_SDIO0_D1 = 0;
|
|
u8 PAD_SDIO0_D2 = 0;
|
|
u8 PAD_SDIO0_D3 = 0;
|
|
u8 REG_SDIO0_CD = 0;
|
|
u8 REG_SDIO0_CLK = 0;
|
|
u8 REG_SDIO0_CMD = 0;
|
|
u8 REG_SDIO0_D0 = 0;
|
|
u8 REG_SDIO0_D1 = 0;
|
|
u8 REG_SDIO0_D2 = 0;
|
|
u8 REG_SDIO0_D3 = 0;
|
|
|
|
SDHCI_DUMP(": Reg_544: 0x%08x | Reg_30C: 0x%08x\n",
|
|
sdhci_readl(host, CVI_183X_SDHCI_VENDOR_A_STAT_R),
|
|
sdhci_readl(host, SDHCI_RX_DELAY_LINE));
|
|
SDHCI_DUMP(": Reg_31C: 0x%08x | Reg_312: 0x%08x\n",
|
|
sdhci_readl(host, SDHCI_TX_DELAY_LINE),
|
|
sdhci_readw(host, SDHCI_LEAD_LAG_FLAG));
|
|
|
|
PAD_SDIO0_PWR_EN = readb(cvi_host->pinmuxbase + 0x14) & 0x07;
|
|
PAD_SDIO0_CD = readb(cvi_host->pinmuxbase + 0x04) & 0x07;
|
|
PAD_SDIO0_CLK = readb(cvi_host->pinmuxbase + 0x40) & 0x07;
|
|
PAD_SDIO0_CMD = readb(cvi_host->pinmuxbase + 0x3C) & 0x07;
|
|
PAD_SDIO0_D0 = readb(cvi_host->pinmuxbase + 0x44) & 0x07;
|
|
PAD_SDIO0_D1 = readb(cvi_host->pinmuxbase + 0x48) & 0x07;
|
|
PAD_SDIO0_D2 = readb(cvi_host->pinmuxbase + 0x4C) & 0x07;
|
|
PAD_SDIO0_D3 = readb(cvi_host->pinmuxbase + 0x50) & 0x07;
|
|
REG_SDIO0_CD = readb(cvi_host->pinmuxbase + 0xC04);
|
|
REG_SDIO0_CLK = readb(cvi_host->pinmuxbase + 0xB04);
|
|
REG_SDIO0_CMD = readb(cvi_host->pinmuxbase + 0xB00);
|
|
REG_SDIO0_D0 = readb(cvi_host->pinmuxbase + 0xB08);
|
|
REG_SDIO0_D1 = readb(cvi_host->pinmuxbase + 0xB0C);
|
|
REG_SDIO0_D2 = readb(cvi_host->pinmuxbase + 0xB10);
|
|
REG_SDIO0_D3 = readb(cvi_host->pinmuxbase + 0xB14);
|
|
|
|
SDHCI_DUMP(": PAD_SDIO0_PWR_EN:0x%02x\n", PAD_SDIO0_PWR_EN);
|
|
SDHCI_DUMP(": PAD_SDIO0_CD:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_CD, (REG_SDIO0_CD & 0x04)>>2, (REG_SDIO0_CD & 0x08)>>3,
|
|
(REG_SDIO0_CD & 0x80)>>7, (REG_SDIO0_CD & 0x40)>>6, (REG_SDIO0_CD & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_CLK:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_CLK, (REG_SDIO0_CLK & 0x04)>>2, (REG_SDIO0_CLK & 0x08)>>3,
|
|
(REG_SDIO0_CLK & 0x80)>>7, (REG_SDIO0_CLK & 0x40)>>6, (REG_SDIO0_CLK & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_CMD:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_CMD, (REG_SDIO0_CMD & 0x04)>>2, (REG_SDIO0_CMD & 0x08)>>3,
|
|
(REG_SDIO0_CMD & 0x80)>>7, (REG_SDIO0_CMD & 0x40)>>6, (REG_SDIO0_CMD & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_D0:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_D0, (REG_SDIO0_D0 & 0x04)>>2, (REG_SDIO0_D0 & 0x08)>>3,
|
|
(REG_SDIO0_D0 & 0x80)>>7, (REG_SDIO0_D0 & 0x40)>>6, (REG_SDIO0_D0 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_D1:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_D1, (REG_SDIO0_D1 & 0x04)>>2, (REG_SDIO0_D1 & 0x08)>>3,
|
|
(REG_SDIO0_D1 & 0x80)>>7, (REG_SDIO0_D1 & 0x40)>>6, (REG_SDIO0_D1 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_D2:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_D2, (REG_SDIO0_D2 & 0x04)>>2, (REG_SDIO0_D2 & 0x08)>>3,
|
|
(REG_SDIO0_D2 & 0x80)>>7, (REG_SDIO0_D2 & 0x40)>>6, (REG_SDIO0_D2 & 0x20)>>5);
|
|
SDHCI_DUMP(": PAD_SDIO0_D3:0x%02x PU:%u PD:%u DS[2:0]:%u%u%u\n",
|
|
PAD_SDIO0_D3, (REG_SDIO0_D3 & 0x04)>>2, (REG_SDIO0_D3 & 0x08)>>3,
|
|
(REG_SDIO0_D3 & 0x80)>>7, (REG_SDIO0_D3 & 0x40)>>6, (REG_SDIO0_D3 & 0x20)>>5);
|
|
|
|
clk_source_select = (readb(cvi_host->clkgenbase + 0x20) & 0x40) >> 6;
|
|
|
|
SDHCI_DUMP(": clk_sd0 enable[19]:0x%08x\n", readl(cvi_host->clkgenbase));
|
|
SDHCI_DUMP(": clk_sd0 source_select:%u\n", clk_source_select);
|
|
if (clk_source_select == 0) {
|
|
SDHCI_DUMP(": clk_sd0 REG:0x03002074 = 0x%08x\n",
|
|
readl(cvi_host->clkgenbase + 0x74));
|
|
if (readl(cvi_host->clkgenbase + 0x74) == 0x00000001)
|
|
SDHCI_DUMP(": clk_sd0 %d MHz\n", DISPPLL_MHZ/12);
|
|
} else if (clk_source_select == 1) {
|
|
SDHCI_DUMP(": clk_sd0 REG:0x03002070 = 0x%08x\n",
|
|
readl(cvi_host->clkgenbase + 0x70));
|
|
if (readl(cvi_host->clkgenbase + 0x70) == 0x00040009)
|
|
SDHCI_DUMP(": clk_sd0 %d MHz\n", FPLL_MHZ/4);
|
|
}
|
|
}
|
|
|
|
static const struct sdhci_ops sdhci_cv1835_emmc_ops = {
|
|
.reset = sdhci_cv1835_emmc_reset,
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.get_max_clock = sdhci_cvi_general_emmc_get_max_clock,
|
|
.voltage_switch = sdhci_cvi_emmc_voltage_switch,
|
|
.set_uhs_signaling = sdhci_cvi_general_set_uhs_signaling,
|
|
.platform_execute_tuning = sdhci_cv1835_general_execute_tuning,
|
|
.select_drive_strength = sdhci_cv1835_general_select_drive_strength,
|
|
.dump_vendor_regs = sdhci_cv1835_emmc_dump_vendor_regs,
|
|
};
|
|
|
|
static const struct sdhci_ops sdhci_cv1835_sd_ops = {
|
|
.reset = sdhci_cv1835_sd_reset,
|
|
.set_clock = sdhci_set_clock,
|
|
.set_power = sdhci_cv1835_sd_set_power,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.get_max_clock = sdhci_cv1835_sd_get_max_clock,
|
|
.voltage_switch = sdhci_cv1835_sd_voltage_switch,
|
|
.set_uhs_signaling = sdhci_cvi_general_set_uhs_signaling,
|
|
.platform_execute_tuning = sdhci_cv1835_general_execute_tuning,
|
|
.select_drive_strength = sdhci_cv1835_general_select_drive_strength,
|
|
.dump_vendor_regs = sdhci_cv1835_sd_dump_vendor_regs,
|
|
};
|
|
|
|
static const struct sdhci_ops sdhci_cv1835_sdio_ops = {
|
|
.reset = sdhci_cv1835_sd_reset,
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.get_max_clock = sdhci_cv1835_sd_get_max_clock,
|
|
.voltage_switch = sdhci_cv1835_sd_voltage_switch,
|
|
.set_uhs_signaling = sdhci_cvi_general_set_uhs_signaling,
|
|
.select_drive_strength = sdhci_cv1835_general_select_drive_strength,
|
|
};
|
|
|
|
static const struct sdhci_ops sdhci_cv1835_fpga_emmc_ops = {
|
|
.reset = sdhci_cv1835_emmc_reset,
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.get_max_clock = sdhci_cvi_fpga_emmc_get_max_clock,
|
|
.voltage_switch = sdhci_cvi_emmc_voltage_switch,
|
|
.set_uhs_signaling = sdhci_cvi_general_set_uhs_signaling,
|
|
.platform_execute_tuning = sdhci_cv1835_general_execute_tuning,
|
|
.select_drive_strength = sdhci_cv1835_general_select_drive_strength,
|
|
.dump_vendor_regs = sdhci_cv1835_emmc_dump_vendor_regs,
|
|
};
|
|
|
|
static const struct sdhci_ops sdhci_cv1835_fpga_sd_ops = {
|
|
.reset = sdhci_cv1835_sd_reset,
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.get_max_clock = sdhci_cvi_fpga_sd_get_max_clock,
|
|
.voltage_switch = sdhci_cv1835_sd_voltage_switch,
|
|
.set_uhs_signaling = sdhci_cvi_general_set_uhs_signaling,
|
|
.platform_execute_tuning = sdhci_cv1835_general_execute_tuning,
|
|
.select_drive_strength = sdhci_cv1835_general_select_drive_strength,
|
|
.dump_vendor_regs = sdhci_cv1835_sd_dump_vendor_regs,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_cv1835_emmc_pdata = {
|
|
.ops = &sdhci_cv1835_emmc_ops,
|
|
.quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_cv1835_sd_pdata = {
|
|
.ops = &sdhci_cv1835_sd_ops,
|
|
.quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_cv1835_sdio_pdata = {
|
|
.ops = &sdhci_cv1835_sdio_ops,
|
|
.quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_NO_1_8_V,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_cv1835_fpga_emmc_pdata = {
|
|
.ops = &sdhci_cv1835_fpga_emmc_ops,
|
|
.quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_BROKEN_HS200 | SDHCI_QUIRK2_NO_1_8_V,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_cv1835_fpga_sd_pdata = {
|
|
.ops = &sdhci_cv1835_fpga_sd_ops,
|
|
.quirks = SDHCI_QUIRK_INVERTED_WRITE_PROTECT | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_NO_1_8_V,
|
|
};
|
|
|
|
static const struct of_device_id sdhci_cvi_dt_match[] = {
|
|
{.compatible = "cvitek,cv1835-fpga-emmc", .data = &sdhci_cv1835_fpga_emmc_pdata},
|
|
{.compatible = "cvitek,cv1835-fpga-sd", .data = &sdhci_cv1835_fpga_sd_pdata},
|
|
{.compatible = "cvitek,cv1835-emmc", .data = &sdhci_cv1835_emmc_pdata},
|
|
{.compatible = "cvitek,cv1835-sd", .data = &sdhci_cv1835_sd_pdata},
|
|
{.compatible = "cvitek,cv1835-sdio", .data = &sdhci_cv1835_sdio_pdata},
|
|
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static void disable_mmc_clk(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
struct mmc_host *mmc = cvi_host->mmc;
|
|
|
|
pr_debug("%s: disable_mmc_clk\n", mmc_hostname(mmc));
|
|
reset_control_assert(cvi_host->clk_rst_axi_emmc_ctrl);
|
|
reset_control_assert(cvi_host->clk_rst_emmc_ctrl);
|
|
reset_control_assert(cvi_host->clk_rst_100k_emmc_ctrl);
|
|
}
|
|
|
|
static int check_mmc_device_presence(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
u32 pstate_reg = 0;
|
|
struct mmc_host *mmc = cvi_host->mmc;
|
|
int ret = 0;
|
|
|
|
pr_debug("%s: Check_mmc_device_presence:\n", mmc_hostname(mmc));
|
|
|
|
pstate_reg = readl_relaxed(cvi_host->core_mem + SDHCI_PRESENT_STATE);
|
|
|
|
if (!(pstate_reg & SDHCI_CARD_PRESENT)) {
|
|
// card not detected
|
|
pr_debug("%s: device not present\n", mmc_hostname(mmc));
|
|
disable_mmc_clk(cvi_host);
|
|
ret = -1;
|
|
} else {
|
|
pr_debug("%s: device is present\n", mmc_hostname(mmc));
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int get_emmc_clk_control(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
int ret;
|
|
struct mmc_host *mmc = cvi_host->mmc;
|
|
|
|
pr_debug("%s: get_emmc_clk_control\n", mmc_hostname(mmc));
|
|
|
|
cvi_host->clk_rst_axi_emmc_ctrl = devm_reset_control_get(&cvi_host->pdev->dev, "axi_emmc");
|
|
if (IS_ERR(cvi_host->clk_rst_axi_emmc_ctrl)) {
|
|
ret = PTR_ERR(cvi_host->clk_rst_axi_emmc_ctrl);
|
|
dev_err(&cvi_host->pdev->dev, "failed to retrieve axi_emmc clk reset");
|
|
return ret;
|
|
}
|
|
|
|
cvi_host->clk_rst_emmc_ctrl = devm_reset_control_get(&cvi_host->pdev->dev, "emmc");
|
|
if (IS_ERR(cvi_host->clk_rst_emmc_ctrl)) {
|
|
ret = PTR_ERR(cvi_host->clk_rst_emmc_ctrl);
|
|
dev_err(&cvi_host->pdev->dev, "failed to retrieve emmc clk reset");
|
|
return ret;
|
|
}
|
|
|
|
cvi_host->clk_rst_100k_emmc_ctrl = devm_reset_control_get(&cvi_host->pdev->dev, "100k_emmc");
|
|
if (IS_ERR(cvi_host->clk_rst_100k_emmc_ctrl)) {
|
|
ret = PTR_ERR(cvi_host->clk_rst_100k_emmc_ctrl);
|
|
dev_err(&cvi_host->pdev->dev, "failed to retrieve 100k_emmc clk reset");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DEVICE_TABLE(of, sdhci_cvi_dt_match);
|
|
|
|
static irqreturn_t sdhci_cvi_cd_handler(int irq, void *dev_id)
|
|
{
|
|
/* Schedule a card detection after a debounce timeout */
|
|
struct mmc_host *host = dev_id;
|
|
struct mmc_gpio *ctx = host->slot.handler_priv;
|
|
|
|
pr_debug("%s-%d mmc_gpio_get_cd:%d\n",
|
|
__func__, __LINE__, mmc_gpio_get_cd(host));
|
|
host->trigger_card_event = true;
|
|
host->ops->card_event(host);
|
|
mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sdhci_cvi_probe(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host;
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct sdhci_cvi_host *cvi_host;
|
|
const struct of_device_id *match;
|
|
const struct sdhci_pltfm_data *pdata;
|
|
int ret;
|
|
int gpio_cd = -EINVAL;
|
|
|
|
pr_info(DRIVER_NAME ":%s\n", __func__);
|
|
|
|
match = of_match_device(sdhci_cvi_dt_match, &pdev->dev);
|
|
if (!match)
|
|
return -EINVAL;
|
|
|
|
pdata = match->data;
|
|
|
|
host = sdhci_pltfm_init(pdev, pdata, sizeof(*cvi_host));
|
|
if (IS_ERR(host))
|
|
return PTR_ERR(host);
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
cvi_host->host = host;
|
|
cvi_host->mmc = host->mmc;
|
|
cvi_host->pdev = pdev;
|
|
cvi_host->core_mem = host->ioaddr;
|
|
cvi_host->topbase = ioremap(TOP_BASE, 0x2000);
|
|
cvi_host->pinmuxbase = ioremap(PINMUX_BASE, 0x1000);
|
|
cvi_host->clkgenbase = ioremap(CLKGEN_BASE, 0x100);
|
|
|
|
sdhci_cv1835_sd_voltage_restore(host, false);
|
|
|
|
ret = mmc_of_parse(host->mmc);
|
|
if (ret)
|
|
goto pltfm_free;
|
|
|
|
// if device use gpio as card detect pin, change its get_cd function.
|
|
if (host->mmc->caps & MMC_CAP_NEEDS_POLL) {
|
|
enum of_gpio_flags flags;
|
|
|
|
host->mmc_host_ops.get_cd = sdhci_cv1835_sdio_get_cd;
|
|
cvi_host->gpio_card_cd = of_get_named_gpio_flags(pdev->dev.of_node, "gpio-cd-sdio", 0, &flags);
|
|
cvi_host->gpio_card_cd_active = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
|
|
|
|
pr_debug("gpio_card_cd %d gpio_card_cd_active %d\n",
|
|
cvi_host->gpio_card_cd, cvi_host->gpio_card_cd_active);
|
|
}
|
|
|
|
sdhci_get_of_property(pdev);
|
|
|
|
if (host->quirks2 & SDHCI_QUIRK2_SUPPORT_DISABLE_CLK &&
|
|
!(host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
|
|
) {
|
|
get_emmc_clk_control(cvi_host);
|
|
ret = check_mmc_device_presence(cvi_host);
|
|
|
|
if (ret)
|
|
goto err_add_host;
|
|
}
|
|
|
|
if (pdata->ops->hw_reset) {
|
|
cvi_host->reset = devm_reset_control_get(&pdev->dev, "sdio");
|
|
if (IS_ERR(cvi_host->reset)) {
|
|
ret = PTR_ERR(cvi_host->reset);
|
|
goto pltfm_free;
|
|
}
|
|
}
|
|
|
|
if (pdev->dev.of_node) {
|
|
gpio_cd = of_get_named_gpio(pdev->dev.of_node, "cvi-cd-gpios", 0);
|
|
}
|
|
|
|
if (gpio_is_valid(gpio_cd)) {
|
|
cvi_host->cvi_gpio = devm_kzalloc(&cvi_host->pdev->dev,
|
|
sizeof(struct mmc_gpio), GFP_KERNEL);
|
|
if (cvi_host->cvi_gpio) {
|
|
cvi_host->cvi_gpio->cd_gpio_isr = sdhci_cvi_cd_handler;
|
|
cvi_host->cvi_gpio->cd_debounce_delay_ms = SDHCI_GPIO_CD_DEBOUNCE_DELAY_TIME;
|
|
cvi_host->cvi_gpio->cd_label = devm_kzalloc(&cvi_host->pdev->dev,
|
|
strlen("cd-gpio-irq") + 1, GFP_KERNEL);
|
|
strcpy(cvi_host->cvi_gpio->cd_label, "cd-gpio-irq");
|
|
host->mmc->slot.handler_priv = cvi_host->cvi_gpio;
|
|
ret = mmc_gpiod_request_cd(host->mmc, "cvi-cd",
|
|
0, false, SDHCI_GPIO_CD_DEBOUNCE_TIME);
|
|
if (ret) {
|
|
pr_err("card detect request cd failed: %d\n", ret);
|
|
} else {
|
|
writeb(0x3, cvi_host->pinmuxbase + 0x34);
|
|
mmc_gpiod_request_cd_irq(host->mmc);
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = sdhci_add_host(host);
|
|
if (ret)
|
|
goto err_add_host;
|
|
|
|
platform_set_drvdata(pdev, cvi_host);
|
|
|
|
if (strstr(dev_name(mmc_dev(host->mmc)), "wifi-sd"))
|
|
wifi_mmc = host->mmc;
|
|
else
|
|
wifi_mmc = NULL;
|
|
|
|
/* device proc entry */
|
|
if ((!proc_cvi_dir) &&
|
|
(strstr(dev_name(mmc_dev(host->mmc)), "cv-sd"))) {
|
|
ret = cvi_proc_init(cvi_host);
|
|
if (ret)
|
|
pr_err("device proc init is failed!");
|
|
}
|
|
|
|
if (strstr(dev_name(mmc_dev(host->mmc)), "cv-emmc"))
|
|
sdhci_cv183x_emmc_setup_pad(host);
|
|
|
|
return 0;
|
|
|
|
err_add_host:
|
|
pltfm_free:
|
|
sdhci_pltfm_free(pdev);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_cvi_remove(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_cvi_host *cvi_host = sdhci_pltfm_priv(pltfm_host);
|
|
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
|
|
|
|
sdhci_remove_host(host, dead);
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
cvi_proc_shutdown(cvi_host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int save_rtc_reg(struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void restore_rtc_reg(struct sdhci_cvi_host *cvi_host) {}
|
|
|
|
static void save_reg(struct sdhci_host *host, struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
save_rtc_reg(cvi_host);
|
|
cvi_host->reg_ctrl2 = sdhci_readl(host, SDHCI_HOST_CONTROL2);
|
|
cvi_host->reg_clk_ctrl = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
|
|
cvi_host->reg_host_ctrl = sdhci_readl(host, SDHCI_HOST_CONTROL);
|
|
}
|
|
|
|
static void restore_reg(struct sdhci_host *host, struct sdhci_cvi_host *cvi_host)
|
|
{
|
|
restore_rtc_reg(cvi_host);
|
|
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
|
|
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
|
|
sdhci_writel(host, cvi_host->reg_ctrl2, SDHCI_HOST_CONTROL2);
|
|
sdhci_writel(host, cvi_host->reg_clk_ctrl, SDHCI_CLOCK_CONTROL);
|
|
sdhci_writel(host, cvi_host->reg_host_ctrl, SDHCI_HOST_CONTROL);
|
|
}
|
|
|
|
static int sdhci_cvi_suspend(struct device *dev)
|
|
{
|
|
struct sdhci_cvi_host *cvi_host = dev_get_drvdata(dev);
|
|
struct sdhci_host *host = cvi_host->host;
|
|
|
|
if (!host)
|
|
return 0;
|
|
|
|
save_reg(host, cvi_host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdhci_cvi_resume(struct device *dev)
|
|
{
|
|
struct sdhci_cvi_host *cvi_host = dev_get_drvdata(dev);
|
|
struct sdhci_host *host = cvi_host->host;
|
|
|
|
if (!host)
|
|
return 0;
|
|
|
|
restore_reg(host, cvi_host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
static const struct dev_pm_ops sdhci_cvi_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(sdhci_cvi_suspend, sdhci_cvi_resume)
|
|
};
|
|
|
|
static struct platform_driver sdhci_cvi_driver = {
|
|
.probe = sdhci_cvi_probe,
|
|
.remove = sdhci_cvi_remove,
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.pm = &sdhci_cvi_pm_ops,
|
|
.of_match_table = sdhci_cvi_dt_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(sdhci_cvi_driver);
|
|
|
|
MODULE_DESCRIPTION("Cvitek Secure Digital Host Controller Interface driver");
|
|
MODULE_LICENSE("GPL v2");
|