Commit 06f438dd authored by Simon Horman's avatar Simon Horman Committed by Ulf Hansson

mmc: sh_mobile_sdhi: Add tuning support

Add tuning support for use with SDR104 mode
This includes adding support for the sampling clock controller (SCC).

Based on work by Ai Kyuse.

Cc: Ai Kyuse <ai.kyuse.uw@renesas.com>
Signed-off-by: default avatarSimon Horman <horms+renesas@verge.net.au>
Acked-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent 4f119977
...@@ -47,6 +47,11 @@ ...@@ -47,6 +47,11 @@
#define host_to_priv(host) container_of((host)->pdata, struct sh_mobile_sdhi, mmc_data) #define host_to_priv(host) container_of((host)->pdata, struct sh_mobile_sdhi, mmc_data)
struct sh_mobile_sdhi_scc {
unsigned long clk_rate; /* clock rate for SDR104 */
u32 tap; /* sampling clock position for SDR104 */
};
struct sh_mobile_sdhi_of_data { struct sh_mobile_sdhi_of_data {
unsigned long tmio_flags; unsigned long tmio_flags;
u32 tmio_ocr_mask; u32 tmio_ocr_mask;
...@@ -55,6 +60,9 @@ struct sh_mobile_sdhi_of_data { ...@@ -55,6 +60,9 @@ struct sh_mobile_sdhi_of_data {
enum dma_slave_buswidth dma_buswidth; enum dma_slave_buswidth dma_buswidth;
dma_addr_t dma_rx_offset; dma_addr_t dma_rx_offset;
unsigned bus_shift; unsigned bus_shift;
int scc_offset;
struct sh_mobile_sdhi_scc *taps;
int taps_num;
}; };
static const struct sh_mobile_sdhi_of_data of_default_cfg = { static const struct sh_mobile_sdhi_of_data of_default_cfg = {
...@@ -73,12 +81,35 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = { ...@@ -73,12 +81,35 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = {
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
}; };
/* Definitions for sampling clocks */
static struct sh_mobile_sdhi_scc rcar_gen2_scc_taps[] = {
{
.clk_rate = 156000000,
.tap = 0x00000703,
},
{
.clk_rate = 0,
.tap = 0x00000300,
},
};
static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = { static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = {
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE | .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE |
TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2, TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2,
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
.dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES, .dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dma_rx_offset = 0x2000, .dma_rx_offset = 0x2000,
.scc_offset = 0x0300,
.taps = rcar_gen2_scc_taps,
.taps_num = ARRAY_SIZE(rcar_gen2_scc_taps),
};
/* Definitions for sampling clocks */
static struct sh_mobile_sdhi_scc rcar_gen3_scc_taps[] = {
{
.clk_rate = 0,
.tap = 0x00000300,
},
}; };
static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = { static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
...@@ -86,6 +117,9 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = { ...@@ -86,6 +117,9 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2, TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2,
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
.bus_shift = 2, .bus_shift = 2,
.scc_offset = 0x1000,
.taps = rcar_gen3_scc_taps,
.taps_num = ARRAY_SIZE(rcar_gen3_scc_taps),
}; };
static const struct of_device_id sh_mobile_sdhi_of_match[] = { static const struct of_device_id sh_mobile_sdhi_of_match[] = {
...@@ -113,6 +147,7 @@ struct sh_mobile_sdhi { ...@@ -113,6 +147,7 @@ struct sh_mobile_sdhi {
struct tmio_mmc_dma dma_priv; struct tmio_mmc_dma dma_priv;
struct pinctrl *pinctrl; struct pinctrl *pinctrl;
struct pinctrl_state *pins_default, *pins_uhs; struct pinctrl_state *pins_default, *pins_uhs;
void __iomem *scc_ctl;
}; };
static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width) static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width)
...@@ -263,6 +298,201 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc, ...@@ -263,6 +298,201 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
return pinctrl_select_state(priv->pinctrl, pin_state); return pinctrl_select_state(priv->pinctrl, pin_state);
} }
/* SCC registers */
#define SH_MOBILE_SDHI_SCC_DTCNTL 0x000
#define SH_MOBILE_SDHI_SCC_TAPSET 0x002
#define SH_MOBILE_SDHI_SCC_DT2FF 0x004
#define SH_MOBILE_SDHI_SCC_CKSEL 0x006
#define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008
#define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A
/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */
#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN BIT(0)
#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT 16
#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK 0xff
/* Definitions for values the SH_MOBILE_SDHI_SCC_CKSEL register */
#define SH_MOBILE_SDHI_SCC_CKSEL_DTSEL BIT(0)
/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSCNTL register */
#define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN BIT(0)
/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */
#define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR BIT(2)
static inline u32 sd_scc_read32(struct tmio_mmc_host *host,
struct sh_mobile_sdhi *priv, int addr)
{
return readl(priv->scc_ctl + (addr << host->bus_shift));
}
static inline void sd_scc_write32(struct tmio_mmc_host *host,
struct sh_mobile_sdhi *priv,
int addr, u32 val)
{
writel(val, priv->scc_ctl + (addr << host->bus_shift));
}
static unsigned int sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
return 0;
priv = host_to_priv(host);
/* set sampling clock selection range */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
0x8 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT);
/* Initialize SCC */
sd_ctrl_write32_as_16_and_16(host, CTL_STATUS, 0x0);
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL,
SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL));
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
SH_MOBILE_SDHI_SCC_CKSEL_DTSEL |
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DT2FF, host->scc_tappos);
/* Read TAPNUM */
return (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL) >>
SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) &
SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK;
}
static void sh_mobile_sdhi_prepare_tuning(struct tmio_mmc_host *host,
unsigned long tap)
{
struct sh_mobile_sdhi *priv = host_to_priv(host);
/* Set sampling clock position */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap);
}
#define SH_MOBILE_SDHI_MAX_TAP 3
static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv = host_to_priv(host);
unsigned long tap_cnt; /* counter of tuning success */
unsigned long tap_set; /* tap position */
unsigned long tap_start;/* start position of tuning success */
unsigned long tap_end; /* end position of tuning success */
unsigned long ntap; /* temporary counter of tuning success */
unsigned long i;
/* Clear SCC_RVSREQ */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
/*
* Find the longest consecutive run of successful probes. If that
* is more than SH_MOBILE_SDHI_MAX_TAP probes long then use the
* center index as the tap.
*/
tap_cnt = 0;
ntap = 0;
tap_start = 0;
tap_end = 0;
for (i = 0; i < host->tap_num * 2; i++) {
if (test_bit(i, host->taps))
ntap++;
else {
if (ntap > tap_cnt) {
tap_start = i - ntap;
tap_end = i - 1;
tap_cnt = ntap;
}
ntap = 0;
}
}
if (ntap > tap_cnt) {
tap_start = i - ntap;
tap_end = i - 1;
tap_cnt = ntap;
}
if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP)
tap_set = (tap_start + tap_end) / 2 % host->tap_num;
else
return -EIO;
/* Set SCC */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap_set);
/* Enable auto re-tuning */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN |
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
return 0;
}
static bool sh_mobile_sdhi_check_scc_error(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
return 0;
priv = host_to_priv(host);
/* Check SCC error */
if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) &
SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &&
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ) &
SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) {
/* Clear SCC error */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
return true;
}
return false;
}
static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
return;
priv = host_to_priv(host);
/* Reset SCC */
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL,
~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL,
~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL));
}
static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host) static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host)
{ {
int timeout = 1000; int timeout = 1000;
...@@ -333,7 +563,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) ...@@ -333,7 +563,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
struct tmio_mmc_data *mmd = pdev->dev.platform_data; struct tmio_mmc_data *mmd = pdev->dev.platform_data;
struct tmio_mmc_host *host; struct tmio_mmc_host *host;
struct resource *res; struct resource *res;
int irq, ret, i = 0; int irq, ret, i;
struct tmio_mmc_dma *dma_priv; struct tmio_mmc_dma *dma_priv;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
...@@ -393,6 +623,11 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) ...@@ -393,6 +623,11 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
host->card_busy = sh_mobile_sdhi_card_busy; host->card_busy = sh_mobile_sdhi_card_busy;
host->start_signal_voltage_switch = host->start_signal_voltage_switch =
sh_mobile_sdhi_start_signal_voltage_switch; sh_mobile_sdhi_start_signal_voltage_switch;
host->init_tuning = sh_mobile_sdhi_init_tuning;
host->prepare_tuning = sh_mobile_sdhi_prepare_tuning;
host->select_tuning = sh_mobile_sdhi_select_tuning;
host->check_scc_error = sh_mobile_sdhi_check_scc_error;
host->hw_reset = sh_mobile_sdhi_hw_reset;
} }
/* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */ /* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */
...@@ -433,6 +668,34 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) ...@@ -433,6 +668,34 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
if (ret < 0) if (ret < 0)
goto efree; goto efree;
if (host->mmc->caps & MMC_CAP_UHS_SDR104) {
host->mmc->caps |= MMC_CAP_HW_RESET;
if (of_id && of_id->data) {
const struct sh_mobile_sdhi_of_data *of_data;
const struct sh_mobile_sdhi_scc *taps;
bool hit = false;
of_data = of_id->data;
taps = of_data->taps;
for (i = 0; i < of_data->taps_num; i++) {
if (taps[i].clk_rate == 0 ||
taps[i].clk_rate == host->mmc->f_max) {
host->scc_tappos = taps->tap;
hit = true;
break;
}
}
if (!hit)
dev_warn(&host->pdev->dev, "Unknown clock rate for SDR104\n");
priv->scc_ctl = host->ctl + of_data->scc_offset;
}
}
i = 0;
while (1) { while (1) {
irq = platform_get_irq(pdev, i); irq = platform_get_irq(pdev, i);
if (irq < 0) if (irq < 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment