mirror of https://github.com/OpenIPC/firmware.git
718 lines
18 KiB
Diff
718 lines
18 KiB
Diff
--- linux-4.9.37/drivers/mmc/host/sdhci-goke.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ linux-4.9.y/drivers/mmc/host/sdhci-goke.c 2021-06-07 13:01:33.000000000 +0300
|
|
@@ -0,0 +1,714 @@
|
|
+/*
|
|
+ * Copyright (c) Hunan Goke,Chengdu Goke,Shandong Goke. 2021. All rights reserved.
|
|
+ */
|
|
+
|
|
+static unsigned int sdhci_bsp_get_max_clk(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+
|
|
+ return bsp_priv->f_max;
|
|
+}
|
|
+
|
|
+static int sdhci_bsp_parse_dt(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ struct device_node *np = host->mmc->parent->of_node;
|
|
+ int ret, len;
|
|
+
|
|
+ ret = mmc_of_parse(host->mmc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (of_property_read_u32(np, "max-frequency", &bsp_priv->f_max))
|
|
+ bsp_priv->f_max = MAX_FREQ;
|
|
+
|
|
+ if (of_find_property(np, "mmc-cmd-queue", &len))
|
|
+ host->mmc->caps2 |= MMC_CAP2_CMD_QUEUE;
|
|
+
|
|
+ if (of_find_property(np, "mmc-broken-cmd23", &len) ||
|
|
+ (host->mmc->caps2 & MMC_CAP2_CMD_QUEUE))
|
|
+ host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void bsp_mmc_crg_init(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_pltfm_priv(pltfm_host);
|
|
+
|
|
+ clk_prepare_enable(pltfm_host->clk);
|
|
+ reset_control_assert(bsp_priv->crg_rst);
|
|
+ reset_control_assert(bsp_priv->dll_rst);
|
|
+ if (bsp_priv->sampl_rst)
|
|
+ reset_control_assert(bsp_priv->sampl_rst);
|
|
+
|
|
+ udelay(25); /* delay 25us */
|
|
+ reset_control_deassert(bsp_priv->crg_rst);
|
|
+ udelay(10); /* delay 10us */
|
|
+}
|
|
+
|
|
+static void bsp_set_drv_phase(struct sdhci_host *host, unsigned int phase)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int devid = bsp_priv->devid;
|
|
+ unsigned int offset[] = {
|
|
+ REG_EMMC_DRV_DLL_CTRL,
|
|
+ REG_SDIO0_DRV_DLL_CTRL,
|
|
+ REG_SDIO1_DRV_DLL_CTRL,
|
|
+ REG_SDIO2_DRV_DLL_CTRL
|
|
+ };
|
|
+
|
|
+ regmap_write_bits(bsp_priv->crg_regmap, offset[devid],
|
|
+ SDIO_DRV_PHASE_SEL_MASK, sdio_drv_sel(phase));
|
|
+}
|
|
+
|
|
+static void bsp_set_sampl_phase(struct sdhci_host *host, unsigned int phase)
|
|
+{
|
|
+ unsigned int reg;
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_AT_STAT);
|
|
+ reg &= ~SDHCI_PHASE_SEL_MASK;
|
|
+ reg |= phase;
|
|
+ sdhci_writel(host, reg, SDHCI_AT_STAT);
|
|
+}
|
|
+
|
|
+static void bsp_disable_card_clk(struct sdhci_host *host)
|
|
+{
|
|
+ u16 clk;
|
|
+
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ clk &= ~SDHCI_CLOCK_CARD_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+}
|
|
+
|
|
+static void bsp_enable_card_clk(struct sdhci_host *host)
|
|
+{
|
|
+ u16 clk;
|
|
+
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ clk |= SDHCI_CLOCK_CARD_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+}
|
|
+
|
|
+static void bsp_disable_inter_clk(struct sdhci_host *host)
|
|
+{
|
|
+ u16 clk;
|
|
+
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ clk &= ~SDHCI_CLOCK_INT_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+}
|
|
+
|
|
+static void bsp_enable_sampl_dll_slave(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int devid = bsp_priv->devid;
|
|
+ unsigned int offset[] = {
|
|
+ REG_EMMC_SAMPL_DLL_CTRL,
|
|
+ REG_SDIO0_SAMPL_DLL_CTRL,
|
|
+ REG_SDIO1_SAMPL_DLL_CTRL,
|
|
+ REG_SDIO2_SAMPL_DLL_CTRL
|
|
+ };
|
|
+
|
|
+ regmap_write_bits(bsp_priv->crg_regmap, offset[devid],
|
|
+ SDIO_SAMPL_DLL_SLAVE_EN, SDIO_SAMPL_DLL_SLAVE_EN);
|
|
+}
|
|
+
|
|
+static void bsp_wait_drv_dll_lock(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int devid = bsp_priv->devid;
|
|
+ unsigned int reg;
|
|
+ unsigned int timeout = 20;
|
|
+ unsigned int offset[] = {
|
|
+ REG_EMMC_DRV_DLL_STATUS,
|
|
+ REG_SDIO0_DRV_DLL_STATUS,
|
|
+ REG_SDIO1_DRV_DLL_STATUS,
|
|
+ REG_SDIO2_DRV_DLL_STATUS
|
|
+ };
|
|
+
|
|
+ do {
|
|
+ reg = 0;
|
|
+ regmap_read(bsp_priv->crg_regmap, offset[devid], ®);
|
|
+ if (reg & SDIO_DRV_DLL_LOCK)
|
|
+ return;
|
|
+
|
|
+ mdelay(1);
|
|
+ timeout--;
|
|
+ } while (timeout > 0);
|
|
+
|
|
+ pr_err("%s: DRV DLL master not locked.\n", mmc_hostname(host->mmc));
|
|
+}
|
|
+
|
|
+static void bsp_wait_sampl_dll_slave_ready(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int devid = bsp_priv->devid;
|
|
+ unsigned int reg;
|
|
+ unsigned int timeout = 20;
|
|
+ unsigned int offset[] = {
|
|
+ REG_EMMC_SAMPL_DLL_STATUS,
|
|
+ REG_SDIO0_SAMPL_DLL_STATUS,
|
|
+ REG_SDIO1_SAMPL_DLL_STATUS,
|
|
+ REG_SDIO2_SAMPL_DLL_STATUS
|
|
+ };
|
|
+
|
|
+ do {
|
|
+ reg = 0;
|
|
+ regmap_read(bsp_priv->crg_regmap, offset[devid], ®);
|
|
+ if (reg & SDIO_SAMPL_DLL_SLAVE_READY)
|
|
+ return;
|
|
+
|
|
+ mdelay(1);
|
|
+ timeout--;
|
|
+ } while (timeout > 0);
|
|
+
|
|
+ pr_err("%s: SAMPL DLL slave not ready.\n", mmc_hostname(host->mmc));
|
|
+}
|
|
+
|
|
+static void bsp_enable_sample(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int reg;
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_AT_CTRL);
|
|
+ reg |= SDHCI_SAMPLE_EN;
|
|
+ sdhci_writel(host, reg, SDHCI_AT_CTRL);
|
|
+}
|
|
+
|
|
+static void sdhci_bsp_set_clock(struct sdhci_host *host, unsigned int clock)
|
|
+{
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_pltfm_priv(pltfm_host);
|
|
+ unsigned long timeout;
|
|
+ u16 clk;
|
|
+
|
|
+ host->mmc->actual_clock = 0;
|
|
+ bsp_disable_card_clk(host);
|
|
+ udelay(25); /* delay 25us */
|
|
+ bsp_disable_inter_clk(host);
|
|
+ if (clock == 0)
|
|
+ return;
|
|
+
|
|
+ reset_control_assert(bsp_priv->dll_rst);
|
|
+ if (bsp_priv->sampl_rst)
|
|
+ reset_control_assert(bsp_priv->sampl_rst);
|
|
+ udelay(25); /* delay 25us */
|
|
+
|
|
+ clk_set_rate(pltfm_host->clk, clock);
|
|
+ host->mmc->actual_clock = clk_get_rate(pltfm_host->clk);
|
|
+
|
|
+ bsp_get_phase(host);
|
|
+ bsp_set_drv_phase(host, bsp_priv->drv_phase);
|
|
+ bsp_enable_sample(host);
|
|
+ bsp_set_sampl_phase(host, bsp_priv->sampl_phase);
|
|
+ udelay(25); /* delay 25us */
|
|
+
|
|
+ if (host->mmc->actual_clock > MMC_HIGH_52_MAX_DTR) {
|
|
+ bsp_enable_sampl_dll_slave(host);
|
|
+ reset_control_deassert(bsp_priv->dll_rst);
|
|
+ if (bsp_priv->sampl_rst)
|
|
+ reset_control_deassert(bsp_priv->sampl_rst);
|
|
+ }
|
|
+
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ clk |= SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_PLL_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+ timeout = 20; /* default timeout 20ms */
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ while (!(clk & SDHCI_CLOCK_INT_STABLE)) {
|
|
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
|
+ if (timeout == 0) {
|
|
+ pr_err("%s: Internal clock never stabilised.\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ return;
|
|
+ }
|
|
+ timeout--;
|
|
+ mdelay(1);
|
|
+ }
|
|
+
|
|
+ if (host->mmc->actual_clock > MMC_HIGH_52_MAX_DTR) {
|
|
+ bsp_wait_drv_dll_lock(host);
|
|
+ bsp_wait_sampl_dll_slave_ready(host);
|
|
+ }
|
|
+
|
|
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400)
|
|
+ bsp_wait_ds_180_dll_ready(host);
|
|
+
|
|
+ clk |= SDHCI_CLOCK_CARD_EN;
|
|
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
|
+ udelay(100); /* delay 100us */
|
|
+
|
|
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
|
|
+ bsp_wait_ds_dll_lock(host);
|
|
+ bsp_set_ds_dll_delay(host);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void bsp_select_sampl_phase(struct sdhci_host *host, unsigned int phase)
|
|
+{
|
|
+ bsp_disable_card_clk(host);
|
|
+ bsp_set_sampl_phase(host, phase);
|
|
+ bsp_wait_sampl_dll_slave_ready(host);
|
|
+ bsp_enable_card_clk(host);
|
|
+ udelay(1);
|
|
+}
|
|
+
|
|
+static int bsp_send_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ int count, err;
|
|
+
|
|
+ count = 0;
|
|
+ do {
|
|
+ err = mmc_send_tuning(host->mmc, opcode, NULL);
|
|
+ if (err)
|
|
+ break;
|
|
+
|
|
+ count++;
|
|
+ } while (count < MAX_TUNING_NUM);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void bsp_pre_tuning(struct sdhci_host *host)
|
|
+{
|
|
+ sdhci_writel(host, host->ier | SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE);
|
|
+ sdhci_writel(host, host->ier | SDHCI_INT_DATA_AVAIL,
|
|
+ SDHCI_SIGNAL_ENABLE);
|
|
+
|
|
+ bsp_wait_drv_dll_lock(host);
|
|
+ bsp_enable_sampl_dll_slave(host);
|
|
+ bsp_enable_sample(host);
|
|
+ host->is_tuning = 1;
|
|
+}
|
|
+
|
|
+static void bsp_post_tuning(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned short ctrl;
|
|
+
|
|
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
+ ctrl |= SDHCI_CTRL_TUNED_CLK;
|
|
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
|
|
+
|
|
+ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
|
|
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
|
|
+ host->is_tuning = 0;
|
|
+}
|
|
+
|
|
+#ifndef SDHCI_GOKE_EDGE_TUNING
|
|
+static int bsp_get_best_sampl(u32 candidates)
|
|
+{
|
|
+ int rise = NOT_FOUND;
|
|
+ int fall = NOT_FOUND;
|
|
+ int win_max_r = NOT_FOUND;
|
|
+ int win_max_f = NOT_FOUND;
|
|
+ int end_fall = NOT_FOUND;
|
|
+ int found = NOT_FOUND;
|
|
+ int win_max = 0;
|
|
+ int i, win;
|
|
+
|
|
+ for (i = 0; i < PHASE_SCALE; i++) {
|
|
+ if ((candidates & 0x3) == 0x2)
|
|
+ rise = (i + 1) % PHASE_SCALE;
|
|
+
|
|
+ if ((candidates & 0x3) == 0x1) {
|
|
+ fall = i;
|
|
+ if (rise != NOT_FOUND) {
|
|
+ win = fall - rise + 1;
|
|
+ if (win > win_max) {
|
|
+ win_max = win;
|
|
+ found = (fall + rise) / 2; /* Get window center by devide 2 */
|
|
+ win_max_r = rise;
|
|
+ win_max_f = fall;
|
|
+ rise = NOT_FOUND;
|
|
+ fall = NOT_FOUND;
|
|
+ }
|
|
+ } else {
|
|
+ end_fall = fall;
|
|
+ }
|
|
+ }
|
|
+ candidates = ror32(candidates, 1);
|
|
+ }
|
|
+
|
|
+ if (end_fall != NOT_FOUND && rise != NOT_FOUND) {
|
|
+ fall = end_fall;
|
|
+ if (end_fall < rise)
|
|
+ end_fall += PHASE_SCALE;
|
|
+
|
|
+ win = end_fall - rise + 1;
|
|
+ if (win > win_max) {
|
|
+ found = (rise + (win / 2)) % PHASE_SCALE; /* Get window center by devide 2 */
|
|
+ win_max_r = rise;
|
|
+ win_max_f = fall;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (found != NOT_FOUND)
|
|
+ pr_info("valid phase shift [%d, %d] Final Phase:%d\n",
|
|
+ win_max_r, win_max_f, found);
|
|
+
|
|
+ return found;
|
|
+}
|
|
+
|
|
+static int sdhci_bsp_exec_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int sampl;
|
|
+ unsigned int candidates = 0;
|
|
+ int phase, err;
|
|
+
|
|
+ bsp_pre_tuning(host);
|
|
+
|
|
+ for (sampl = 0; sampl < PHASE_SCALE; sampl++) {
|
|
+ bsp_select_sampl_phase(host, sampl);
|
|
+
|
|
+ err = bsp_send_tuning(host, opcode);
|
|
+ if (err)
|
|
+ pr_debug("send tuning CMD%u fail! phase:%d err:%d\n",
|
|
+ opcode, sampl, err);
|
|
+ else
|
|
+ candidates |= (0x1 << sampl);
|
|
+ }
|
|
+
|
|
+ pr_info("%s: tuning done! candidates 0x%X: ",
|
|
+ mmc_hostname(host->mmc), candidates);
|
|
+
|
|
+ phase = bsp_get_best_sampl(candidates);
|
|
+ if (phase == NOT_FOUND) {
|
|
+ phase = bsp_priv->sampl_phase;
|
|
+ pr_err("no valid phase shift! use default %d\n", phase);
|
|
+ }
|
|
+
|
|
+ bsp_priv->tuning_phase = phase;
|
|
+ bsp_select_sampl_phase(host, phase);
|
|
+ bsp_post_tuning(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#else
|
|
+static void bsp_enable_edge_tuning(struct sdhci_host *host)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int devid = bsp_priv->devid;
|
|
+ unsigned int samplb_offset[] = {
|
|
+ REG_EMMC_SAMPLB_DLL_CTRL,
|
|
+ REG_SDIO0_SAMPLB_DLL_CTRL,
|
|
+ REG_SDIO1_SAMPLB_DLL_CTRL,
|
|
+ REG_SDIO2_SAMPLB_DLL_CTRL
|
|
+ };
|
|
+ unsigned int reg;
|
|
+
|
|
+ regmap_write_bits(bsp_priv->crg_regmap, samplb_offset[devid],
|
|
+ SDIO_SAMPLB_DLL_CLK_MASK, sdio_samplb_sel(8)); /* 8 for 180 degree */
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_MULTI_CYCLE);
|
|
+ reg |= SDHCI_EDGE_DETECT_EN;
|
|
+ sdhci_writel(host, reg, SDHCI_MULTI_CYCLE);
|
|
+}
|
|
+
|
|
+static void bsp_disable_edge_tuning(struct sdhci_host *host)
|
|
+{
|
|
+ unsigned int reg;
|
|
+
|
|
+ reg = sdhci_readl(host, SDHCI_MULTI_CYCLE);
|
|
+ reg &= ~SDHCI_EDGE_DETECT_EN;
|
|
+ sdhci_writel(host, reg, SDHCI_MULTI_CYCLE);
|
|
+}
|
|
+
|
|
+static int sdhci_bsp_exec_edge_tuning(struct sdhci_host *host, u32 opcode)
|
|
+{
|
|
+ struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host);
|
|
+ unsigned int index, val;
|
|
+ unsigned int edge_p2f, edge_f2p, start, end, phase;
|
|
+ unsigned int fall, rise, fall_updat_flag;
|
|
+ unsigned int found = 0;
|
|
+ unsigned int prev_found = 0;
|
|
+ int prev_err = 0;
|
|
+ int err;
|
|
+
|
|
+ bsp_pre_tuning(host);
|
|
+ bsp_enable_edge_tuning(host);
|
|
+
|
|
+ start = 0;
|
|
+ end = PHASE_SCALE / EDGE_TUNING_PHASE_STEP;
|
|
+
|
|
+ edge_p2f = start;
|
|
+ edge_f2p = end;
|
|
+ for (index = 0; index <= end; index++) {
|
|
+ bsp_select_sampl_phase(host, index * EDGE_TUNING_PHASE_STEP);
|
|
+
|
|
+ err = mmc_send_tuning(host->mmc, opcode, NULL);
|
|
+ if (!err) {
|
|
+ val = sdhci_readl(host, SDHCI_MULTI_CYCLE);
|
|
+ found = val & SDHCI_FOUND_EDGE;
|
|
+ } else {
|
|
+ found = 1;
|
|
+ }
|
|
+
|
|
+ if (prev_found && !found)
|
|
+ edge_f2p = index;
|
|
+ else if (!prev_found && found)
|
|
+ edge_p2f = index;
|
|
+
|
|
+ if ((edge_p2f != start) && (edge_f2p != end))
|
|
+ break;
|
|
+
|
|
+ prev_found = found;
|
|
+ found = 0;
|
|
+ }
|
|
+
|
|
+ if ((edge_p2f == start) && (edge_f2p == end)) {
|
|
+ pr_err("%s: tuning failed! can not found edge!\n",
|
|
+ mmc_hostname(host->mmc));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ bsp_disable_edge_tuning(host);
|
|
+
|
|
+ start = edge_p2f * EDGE_TUNING_PHASE_STEP;
|
|
+ end = edge_f2p * EDGE_TUNING_PHASE_STEP;
|
|
+ if (end <= start)
|
|
+ end += PHASE_SCALE;
|
|
+
|
|
+ fall = start;
|
|
+ rise = end;
|
|
+ fall_updat_flag = 0;
|
|
+ for (index = start; index <= end; index++) {
|
|
+ bsp_select_sampl_phase(host, index % PHASE_SCALE);
|
|
+
|
|
+ err = bsp_send_tuning(host, opcode);
|
|
+ if (err)
|
|
+ pr_debug("send tuning CMD%u fail! phase:%d err:%d\n",
|
|
+ opcode, index, err);
|
|
+
|
|
+ if (err && index == start) {
|
|
+ if (!fall_updat_flag) {
|
|
+ fall_updat_flag = 1;
|
|
+ fall = start;
|
|
+ }
|
|
+ } else {
|
|
+ if (!prev_err && err) {
|
|
+ if (!fall_updat_flag) {
|
|
+ fall_updat_flag = 1;
|
|
+ fall = index;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ if (prev_err && !err)
|
|
+ rise = index;
|
|
+
|
|
+ if (err && index == end)
|
|
+ rise = end;
|
|
+
|
|
+
|
|
+ prev_err = err;
|
|
+ }
|
|
+
|
|
+ phase = ((fall + rise) / 2 + PHASE_SCALE / 2) % PHASE_SCALE; /* Get window center by divide 2 */
|
|
+
|
|
+ pr_info("%s: tuning done! valid phase shift [%d, %d] Final Phase:%d\n",
|
|
+ mmc_hostname(host->mmc), rise % PHASE_SCALE,
|
|
+ fall % PHASE_SCALE, phase);
|
|
+
|
|
+ bsp_priv->tuning_phase = phase;
|
|
+ bsp_select_sampl_phase(host, phase);
|
|
+ bsp_post_tuning(host);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void sdhci_bsp_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
|
|
+{
|
|
+ sdhci_set_uhs_signaling(host, timing);
|
|
+ host->timing = timing;
|
|
+
|
|
+ /* Goke add set io config here to set pin drv strength */
|
|
+ bsp_set_io_config(host);
|
|
+}
|
|
+
|
|
+static void sdhci_bsp_hw_reset(struct sdhci_host *host)
|
|
+{
|
|
+ sdhci_writel(host, 0x0, SDHCI_EMMC_HW_RESET);
|
|
+ udelay(10); /* delay 10us */
|
|
+ sdhci_writel(host, 0x1, SDHCI_EMMC_HW_RESET);
|
|
+ udelay(200); /* delay 200us */
|
|
+}
|
|
+
|
|
+/*
|
|
+ * This api is for wifi driver rescan the sdio device,
|
|
+ * ugly but it is needed
|
|
+ */
|
|
+int bsp_sdio_rescan(int slot)
|
|
+{
|
|
+ struct mmc_host *mmc = mci_host[slot];
|
|
+
|
|
+ if (mmc == NULL) {
|
|
+ pr_err("invalid mmc, please check the argument\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ mmc_detect_change(mmc, 0);
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(bsp_sdio_rescan);
|
|
+
|
|
+static const struct sdhci_ops sdhci_bsp_ops = {
|
|
+ .get_max_clock = sdhci_bsp_get_max_clk,
|
|
+ .set_clock = sdhci_bsp_set_clock,
|
|
+ .set_bus_width = sdhci_set_bus_width,
|
|
+ .reset = sdhci_reset,
|
|
+ .set_uhs_signaling = sdhci_bsp_set_uhs_signaling,
|
|
+ .hw_reset = sdhci_bsp_hw_reset,
|
|
+
|
|
+#ifdef SDHCI_GOKE_EDGE_TUNING
|
|
+ .platform_execute_tuning = sdhci_bsp_exec_edge_tuning,
|
|
+#else
|
|
+ .platform_execute_tuning = sdhci_bsp_exec_tuning,
|
|
+#endif
|
|
+ .pre_init = bsp_mmc_crg_init,
|
|
+ .extra_init = bsp_host_extra_init,
|
|
+};
|
|
+
|
|
+static const struct sdhci_pltfm_data sdhci_bsp_pdata = {
|
|
+ .ops = &sdhci_bsp_ops,
|
|
+ .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
|
|
+ SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
|
|
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
|
|
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
|
|
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
|
+};
|
|
+static int sdhci_bsp_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct sdhci_host *host;
|
|
+ struct sdhci_pltfm_host *pltfm_host = NULL;
|
|
+ int ret;
|
|
+
|
|
+ host = sdhci_pltfm_init(pdev, &sdhci_bsp_pdata,
|
|
+ sizeof(struct sdhci_bsp_priv));
|
|
+ if (IS_ERR(host))
|
|
+ return PTR_ERR(host);
|
|
+
|
|
+ ret = sdhci_bsp_pltfm_init(pdev, host);
|
|
+ if (ret)
|
|
+ goto err_sdhci_add;
|
|
+
|
|
+ if (bsp_support_runtime_pm(host)) {
|
|
+ pm_runtime_get_noresume(&pdev->dev);
|
|
+ pm_runtime_set_autosuspend_delay(&pdev->dev,
|
|
+ GOKE_MMC_AUTOSUSPEND_DELAY_MS);
|
|
+ pm_runtime_use_autosuspend(&pdev->dev);
|
|
+ pm_runtime_set_active(&pdev->dev);
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+ }
|
|
+
|
|
+ ret = sdhci_add_host(host);
|
|
+ if (ret)
|
|
+ goto pm_runtime_disable;
|
|
+
|
|
+ if (bsp_support_runtime_pm(host)) {
|
|
+ pm_runtime_mark_last_busy(&pdev->dev);
|
|
+ pm_runtime_put_autosuspend(&pdev->dev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+pm_runtime_disable:
|
|
+ if (bsp_support_runtime_pm(host)) {
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ pm_runtime_set_suspended(&pdev->dev);
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+ }
|
|
+
|
|
+err_sdhci_add:
|
|
+ pltfm_host = sdhci_priv(host);
|
|
+ clk_disable_unprepare(pltfm_host->clk);
|
|
+ sdhci_pltfm_free(pdev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sdhci_bsp_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (bsp_support_runtime_pm(host)) {
|
|
+ pm_runtime_get_sync(&pdev->dev);
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+ pm_runtime_put_noidle(&pdev->dev);
|
|
+ }
|
|
+ return sdhci_pltfm_unregister(pdev);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int sdhci_bsp_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+
|
|
+ bsp_disable_card_clk(host);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sdhci_bsp_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct sdhci_host *host = dev_get_drvdata(dev);
|
|
+
|
|
+ bsp_enable_card_clk(host);
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static const struct of_device_id sdhci_bsp_match[] = {
|
|
+ { .compatible = "goke,sdhci" },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sdhci_bsp_match);
|
|
+
|
|
+static const struct dev_pm_ops sdhci_bsp_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend,
|
|
+ sdhci_pltfm_resume)
|
|
+
|
|
+ SET_RUNTIME_PM_OPS(sdhci_bsp_runtime_suspend,
|
|
+ sdhci_bsp_runtime_resume,
|
|
+ NULL)
|
|
+};
|
|
+
|
|
+static struct platform_driver sdhci_bsp_driver = {
|
|
+ .driver = {
|
|
+ .name = "sdhci-goke",
|
|
+ .of_match_table = sdhci_bsp_match,
|
|
+ .pm = &sdhci_bsp_pm_ops,
|
|
+ },
|
|
+ .probe = sdhci_bsp_probe,
|
|
+ .remove = sdhci_bsp_remove,
|
|
+};
|
|
+
|
|
+static int __init sdhci_bsp_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = platform_driver_register(&sdhci_bsp_driver);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = mci_proc_init();
|
|
+ if (ret)
|
|
+ platform_driver_unregister(&sdhci_bsp_driver);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __exit sdhci_bsp_exit(void)
|
|
+{
|
|
+ mci_proc_shutdown();
|
|
+
|
|
+ platform_driver_unregister(&sdhci_bsp_driver);
|
|
+}
|
|
+
|
|
+module_init(sdhci_bsp_init);
|
|
+module_exit(sdhci_bsp_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("SDHCI driver for goke");
|
|
+MODULE_LICENSE("GPL v2");
|