2011-06-08 17:41:57 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2010 Marvell International Ltd.
|
|
|
|
* Zhangfei Gao <zhangfei.gao@marvell.com>
|
|
|
|
* Kevin Wang <dwang4@marvell.com>
|
|
|
|
* Mingwei Wang <mwwang@marvell.com>
|
|
|
|
* Philip Rakity <prakity@marvell.com>
|
|
|
|
* Mark Brown <markb@marvell.com>
|
|
|
|
*
|
|
|
|
* This software is licensed under the terms of the GNU General Public
|
|
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
|
|
* may be copied, distributed, and modified under those terms.
|
|
|
|
*
|
|
|
|
* 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/err.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/mmc/card.h>
|
|
|
|
#include <linux/mmc/host.h>
|
2012-09-19 16:29:12 +08:00
|
|
|
#include <linux/mmc/slot-gpio.h>
|
2011-06-20 22:11:52 +08:00
|
|
|
#include <linux/platform_data/pxa_sdhci.h>
|
2011-06-08 17:41:57 +08:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/delay.h>
|
2011-07-04 03:15:51 +08:00
|
|
|
#include <linux/module.h>
|
2012-04-11 10:34:33 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_device.h>
|
2012-09-19 16:29:12 +08:00
|
|
|
#include <linux/of_gpio.h>
|
2013-02-01 17:48:30 +08:00
|
|
|
#include <linux/pm.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
2014-02-18 23:08:29 +08:00
|
|
|
#include <linux/mbus.h>
|
2012-04-11 10:34:33 +08:00
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
#include "sdhci.h"
|
|
|
|
#include "sdhci-pltfm.h"
|
|
|
|
|
2013-02-01 17:48:30 +08:00
|
|
|
#define PXAV3_RPM_DELAY_MS 50
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
#define SD_CLOCK_BURST_SIZE_SETUP 0x10A
|
|
|
|
#define SDCLK_SEL 0x100
|
|
|
|
#define SDCLK_DELAY_SHIFT 9
|
|
|
|
#define SDCLK_DELAY_MASK 0x1f
|
|
|
|
|
|
|
|
#define SD_CFG_FIFO_PARAM 0x100
|
|
|
|
#define SDCFG_GEN_PAD_CLK_ON (1<<6)
|
|
|
|
#define SDCFG_GEN_PAD_CLK_CNT_MASK 0xFF
|
|
|
|
#define SDCFG_GEN_PAD_CLK_CNT_SHIFT 24
|
|
|
|
|
|
|
|
#define SD_SPI_MODE 0x108
|
|
|
|
#define SD_CE_ATA_1 0x10C
|
|
|
|
|
|
|
|
#define SD_CE_ATA_2 0x10E
|
|
|
|
#define SDCE_MISC_INT (1<<2)
|
|
|
|
#define SDCE_MISC_INT_EN (1<<1)
|
|
|
|
|
2014-10-21 17:22:35 +08:00
|
|
|
struct sdhci_pxa {
|
2014-10-21 17:22:40 +08:00
|
|
|
struct clk *clk_core;
|
2014-10-21 17:22:38 +08:00
|
|
|
struct clk *clk_io;
|
2014-10-21 17:22:35 +08:00
|
|
|
u8 power_mode;
|
2015-01-29 19:36:27 +08:00
|
|
|
void __iomem *sdio3_conf_reg;
|
2014-10-21 17:22:35 +08:00
|
|
|
};
|
|
|
|
|
2014-02-18 23:08:29 +08:00
|
|
|
/*
|
|
|
|
* These registers are relative to the second register region, for the
|
|
|
|
* MBus bridge.
|
|
|
|
*/
|
|
|
|
#define SDHCI_WINDOW_CTRL(i) (0x80 + ((i) << 3))
|
|
|
|
#define SDHCI_WINDOW_BASE(i) (0x84 + ((i) << 3))
|
|
|
|
#define SDHCI_MAX_WIN_NUM 8
|
|
|
|
|
2015-01-29 19:36:27 +08:00
|
|
|
/*
|
|
|
|
* Fields below belong to SDIO3 Configuration Register (third register
|
|
|
|
* region for the Armada 38x flavor)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define SDIO3_CONF_CLK_INV BIT(0)
|
|
|
|
#define SDIO3_CONF_SD_FB_CLK BIT(2)
|
|
|
|
|
2014-02-18 23:08:29 +08:00
|
|
|
static int mv_conf_mbus_windows(struct platform_device *pdev,
|
|
|
|
const struct mbus_dram_target_info *dram)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
void __iomem *regs;
|
|
|
|
struct resource *res;
|
|
|
|
|
|
|
|
if (!dram) {
|
|
|
|
dev_err(&pdev->dev, "no mbus dram info\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
|
|
if (!res) {
|
|
|
|
dev_err(&pdev->dev, "cannot get mbus registers\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
regs = ioremap(res->start, resource_size(res));
|
|
|
|
if (!regs) {
|
|
|
|
dev_err(&pdev->dev, "cannot map mbus registers\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < SDHCI_MAX_WIN_NUM; i++) {
|
|
|
|
writel(0, regs + SDHCI_WINDOW_CTRL(i));
|
|
|
|
writel(0, regs + SDHCI_WINDOW_BASE(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < dram->num_cs; i++) {
|
|
|
|
const struct mbus_dram_window *cs = dram->cs + i;
|
|
|
|
|
|
|
|
/* Write size, attributes and target id to control register */
|
|
|
|
writel(((cs->size - 1) & 0xffff0000) |
|
|
|
|
(cs->mbus_attr << 8) |
|
|
|
|
(dram->mbus_dram_target_id << 4) | 1,
|
|
|
|
regs + SDHCI_WINDOW_CTRL(i));
|
|
|
|
/* Write base address to base register */
|
|
|
|
writel(cs->base, regs + SDHCI_WINDOW_BASE(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
iounmap(regs);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-01-29 19:36:25 +08:00
|
|
|
static int armada_38x_quirks(struct platform_device *pdev,
|
|
|
|
struct sdhci_host *host)
|
2015-01-29 19:36:24 +08:00
|
|
|
{
|
2015-01-29 19:36:25 +08:00
|
|
|
struct device_node *np = pdev->dev.of_node;
|
2015-01-29 19:36:27 +08:00
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2015-01-29 19:36:27 +08:00
|
|
|
struct resource *res;
|
2015-01-29 19:36:25 +08:00
|
|
|
|
2015-10-06 09:22:35 +08:00
|
|
|
host->quirks &= ~SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
|
2015-01-29 19:36:24 +08:00
|
|
|
host->quirks |= SDHCI_QUIRK_MISSING_CAPS;
|
2016-01-26 21:40:47 +08:00
|
|
|
|
|
|
|
host->caps = sdhci_readl(host, SDHCI_CAPABILITIES);
|
|
|
|
host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1);
|
|
|
|
|
2015-01-29 19:36:27 +08:00
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
|
|
|
"conf-sdio3");
|
|
|
|
if (res) {
|
|
|
|
pxa->sdio3_conf_reg = devm_ioremap_resource(&pdev->dev, res);
|
|
|
|
if (IS_ERR(pxa->sdio3_conf_reg))
|
|
|
|
return PTR_ERR(pxa->sdio3_conf_reg);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* According to erratum 'FE-2946959' both SDR50 and DDR50
|
|
|
|
* modes require specific clock adjustments in SDIO3
|
|
|
|
* Configuration register, if the adjustment is not done,
|
|
|
|
* remove them from the capabilities.
|
|
|
|
*/
|
|
|
|
host->caps1 &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
|
|
|
|
|
|
|
|
dev_warn(&pdev->dev, "conf-sdio3 register not found: disabling SDR50 and DDR50 modes.\nConsider updating your dtb\n");
|
|
|
|
}
|
2015-01-29 19:36:25 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* According to erratum 'ERR-7878951' Armada 38x SDHCI
|
|
|
|
* controller has different capabilities than the ones shown
|
|
|
|
* in its registers
|
|
|
|
*/
|
|
|
|
if (of_property_read_bool(np, "no-1-8-v")) {
|
|
|
|
host->caps &= ~SDHCI_CAN_VDD_180;
|
|
|
|
host->mmc->caps &= ~MMC_CAP_1_8V_DDR;
|
|
|
|
} else {
|
|
|
|
host->caps &= ~SDHCI_CAN_VDD_330;
|
|
|
|
}
|
|
|
|
host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_USE_SDR50_TUNING);
|
|
|
|
|
2015-01-29 19:36:24 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-25 19:57:12 +08:00
|
|
|
static void pxav3_reset(struct sdhci_host *host, u8 mask)
|
2011-06-08 17:41:57 +08:00
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
|
|
|
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
|
|
|
|
2014-04-25 19:57:12 +08:00
|
|
|
sdhci_reset(host, mask);
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
if (mask == SDHCI_RESET_ALL) {
|
|
|
|
/*
|
|
|
|
* tune timing of read data/command when crc error happen
|
|
|
|
* no performance impact
|
|
|
|
*/
|
|
|
|
if (pdata && 0 != pdata->clk_delay_cycles) {
|
|
|
|
u16 tmp;
|
|
|
|
|
|
|
|
tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
|
|
|
|
tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK)
|
|
|
|
<< SDCLK_DELAY_SHIFT;
|
|
|
|
tmp |= SDCLK_SEL;
|
|
|
|
writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_WAIT_COUNT 5
|
|
|
|
static void pxav3_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode)
|
|
|
|
{
|
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2011-06-08 17:41:57 +08:00
|
|
|
u16 tmp;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
if (pxa->power_mode == MMC_POWER_UP
|
|
|
|
&& power_mode == MMC_POWER_ON) {
|
|
|
|
|
|
|
|
dev_dbg(mmc_dev(host->mmc),
|
|
|
|
"%s: slot->power_mode = %d,"
|
|
|
|
"ios->power_mode = %d\n",
|
|
|
|
__func__,
|
|
|
|
pxa->power_mode,
|
|
|
|
power_mode);
|
|
|
|
|
|
|
|
/* set we want notice of when 74 clocks are sent */
|
|
|
|
tmp = readw(host->ioaddr + SD_CE_ATA_2);
|
|
|
|
tmp |= SDCE_MISC_INT_EN;
|
|
|
|
writew(tmp, host->ioaddr + SD_CE_ATA_2);
|
|
|
|
|
|
|
|
/* start sending the 74 clocks */
|
|
|
|
tmp = readw(host->ioaddr + SD_CFG_FIFO_PARAM);
|
|
|
|
tmp |= SDCFG_GEN_PAD_CLK_ON;
|
|
|
|
writew(tmp, host->ioaddr + SD_CFG_FIFO_PARAM);
|
|
|
|
|
|
|
|
/* slowest speed is about 100KHz or 10usec per clock */
|
|
|
|
udelay(740);
|
|
|
|
count = 0;
|
|
|
|
|
|
|
|
while (count++ < MAX_WAIT_COUNT) {
|
|
|
|
if ((readw(host->ioaddr + SD_CE_ATA_2)
|
|
|
|
& SDCE_MISC_INT) == 0)
|
|
|
|
break;
|
|
|
|
udelay(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count == MAX_WAIT_COUNT)
|
|
|
|
dev_warn(mmc_dev(host->mmc), "74 clock interrupt not cleared\n");
|
|
|
|
|
|
|
|
/* clear the interrupt bit if posted */
|
|
|
|
tmp = readw(host->ioaddr + SD_CE_ATA_2);
|
|
|
|
tmp |= SDCE_MISC_INT;
|
|
|
|
writew(tmp, host->ioaddr + SD_CE_ATA_2);
|
|
|
|
}
|
|
|
|
pxa->power_mode = power_mode;
|
|
|
|
}
|
|
|
|
|
2014-04-25 19:59:20 +08:00
|
|
|
static void pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
|
2011-06-08 17:41:57 +08:00
|
|
|
{
|
2015-01-29 19:36:27 +08:00
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2011-06-08 17:41:57 +08:00
|
|
|
u16 ctrl_2;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set V18_EN -- UHS modes do not work without this.
|
|
|
|
* does not change signaling voltage
|
|
|
|
*/
|
|
|
|
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 | SDHCI_CTRL_VDD_180;
|
|
|
|
break;
|
|
|
|
case MMC_TIMING_UHS_SDR104:
|
|
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180;
|
|
|
|
break;
|
2014-10-21 17:22:34 +08:00
|
|
|
case MMC_TIMING_MMC_DDR52:
|
2011-06-08 17:41:57 +08:00
|
|
|
case MMC_TIMING_UHS_DDR50:
|
|
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-01-29 19:36:27 +08:00
|
|
|
/*
|
|
|
|
* Update SDIO3 Configuration register according to erratum
|
|
|
|
* FE-2946959
|
|
|
|
*/
|
|
|
|
if (pxa->sdio3_conf_reg) {
|
|
|
|
u8 reg_val = readb(pxa->sdio3_conf_reg);
|
|
|
|
|
|
|
|
if (uhs == MMC_TIMING_UHS_SDR50 ||
|
|
|
|
uhs == MMC_TIMING_UHS_DDR50) {
|
|
|
|
reg_val &= ~SDIO3_CONF_CLK_INV;
|
|
|
|
reg_val |= SDIO3_CONF_SD_FB_CLK;
|
2015-10-06 09:22:36 +08:00
|
|
|
} else if (uhs == MMC_TIMING_MMC_HS) {
|
|
|
|
reg_val &= ~SDIO3_CONF_CLK_INV;
|
|
|
|
reg_val &= ~SDIO3_CONF_SD_FB_CLK;
|
2015-01-29 19:36:27 +08:00
|
|
|
} else {
|
|
|
|
reg_val |= SDIO3_CONF_CLK_INV;
|
|
|
|
reg_val &= ~SDIO3_CONF_SD_FB_CLK;
|
|
|
|
}
|
|
|
|
writeb(reg_val, pxa->sdio3_conf_reg);
|
|
|
|
}
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
|
|
dev_dbg(mmc_dev(host->mmc),
|
|
|
|
"%s uhs = %d, ctrl_2 = %04X\n",
|
|
|
|
__func__, uhs, ctrl_2);
|
|
|
|
}
|
|
|
|
|
2016-03-29 17:45:43 +08:00
|
|
|
static void pxav3_set_power(struct sdhci_host *host, unsigned char mode,
|
|
|
|
unsigned short vdd)
|
|
|
|
{
|
|
|
|
struct mmc_host *mmc = host->mmc;
|
|
|
|
u8 pwr = host->pwr;
|
|
|
|
|
2016-10-05 17:11:22 +08:00
|
|
|
sdhci_set_power_noreg(host, mode, vdd);
|
2016-03-29 17:45:43 +08:00
|
|
|
|
|
|
|
if (host->pwr == pwr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (host->pwr == 0)
|
|
|
|
vdd = 0;
|
|
|
|
|
2017-03-21 01:50:37 +08:00
|
|
|
if (!IS_ERR(mmc->supply.vmmc))
|
2016-03-29 17:45:43 +08:00
|
|
|
mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
|
|
|
|
}
|
|
|
|
|
2013-03-14 02:26:05 +08:00
|
|
|
static const struct sdhci_ops pxav3_sdhci_ops = {
|
2014-04-25 19:58:55 +08:00
|
|
|
.set_clock = sdhci_set_clock,
|
2016-03-29 17:45:43 +08:00
|
|
|
.set_power = pxav3_set_power,
|
2011-06-08 17:41:57 +08:00
|
|
|
.platform_send_init_74_clocks = pxav3_gen_init_74_clocks,
|
2013-01-29 02:27:12 +08:00
|
|
|
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
2014-04-25 19:57:07 +08:00
|
|
|
.set_bus_width = sdhci_set_bus_width,
|
2014-04-25 19:57:12 +08:00
|
|
|
.reset = pxav3_reset,
|
2014-08-15 21:02:15 +08:00
|
|
|
.set_uhs_signaling = pxav3_set_uhs_signaling,
|
2011-06-08 17:41:57 +08:00
|
|
|
};
|
|
|
|
|
2017-08-07 17:50:40 +08:00
|
|
|
static const struct sdhci_pltfm_data sdhci_pxav3_pdata = {
|
2013-03-25 17:42:59 +08:00
|
|
|
.quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK
|
2013-03-25 17:42:56 +08:00
|
|
|
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
|
|
|
|
| SDHCI_QUIRK_32BIT_ADMA_SIZE
|
|
|
|
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
|
|
.ops = &pxav3_sdhci_ops,
|
|
|
|
};
|
|
|
|
|
2012-04-11 10:34:33 +08:00
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static const struct of_device_id sdhci_pxav3_of_match[] = {
|
|
|
|
{
|
|
|
|
.compatible = "mrvl,pxav3-mmc",
|
|
|
|
},
|
2014-02-18 23:08:29 +08:00
|
|
|
{
|
|
|
|
.compatible = "marvell,armada-380-sdhci",
|
|
|
|
},
|
2012-04-11 10:34:33 +08:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
|
|
|
|
|
|
|
|
static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sdhci_pxa_platdata *pdata;
|
|
|
|
struct device_node *np = dev->of_node;
|
|
|
|
u32 clk_delay_cycles;
|
|
|
|
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
|
|
if (!pdata)
|
|
|
|
return NULL;
|
|
|
|
|
2015-01-28 19:54:12 +08:00
|
|
|
if (!of_property_read_u32(np, "mrvl,clk-delay-cycles",
|
|
|
|
&clk_delay_cycles))
|
2012-04-11 10:34:33 +08:00
|
|
|
pdata->clk_delay_cycles = clk_delay_cycles;
|
|
|
|
|
|
|
|
return pdata;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-11-20 02:23:06 +08:00
|
|
|
static int sdhci_pxav3_probe(struct platform_device *pdev)
|
2011-06-08 17:41:57 +08:00
|
|
|
{
|
|
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
|
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
|
|
|
struct device *dev = &pdev->dev;
|
2014-02-18 23:08:29 +08:00
|
|
|
struct device_node *np = pdev->dev.of_node;
|
2011-06-08 17:41:57 +08:00
|
|
|
struct sdhci_host *host = NULL;
|
|
|
|
struct sdhci_pxa *pxa = NULL;
|
2012-04-11 10:34:33 +08:00
|
|
|
const struct of_device_id *match;
|
2011-06-08 17:41:57 +08:00
|
|
|
int ret;
|
|
|
|
|
2016-02-16 21:08:27 +08:00
|
|
|
host = sdhci_pltfm_init(pdev, &sdhci_pxav3_pdata, sizeof(*pxa));
|
2014-07-16 17:53:42 +08:00
|
|
|
if (IS_ERR(host))
|
2011-06-08 17:41:57 +08:00
|
|
|
return PTR_ERR(host);
|
2014-02-18 23:08:29 +08:00
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
pxa = sdhci_pltfm_priv(pltfm_host);
|
2011-06-08 17:41:57 +08:00
|
|
|
|
2014-10-21 17:22:39 +08:00
|
|
|
pxa->clk_io = devm_clk_get(dev, "io");
|
|
|
|
if (IS_ERR(pxa->clk_io))
|
|
|
|
pxa->clk_io = devm_clk_get(dev, NULL);
|
2014-10-21 17:22:38 +08:00
|
|
|
if (IS_ERR(pxa->clk_io)) {
|
2011-06-08 17:41:57 +08:00
|
|
|
dev_err(dev, "failed to get io clock\n");
|
2014-10-21 17:22:38 +08:00
|
|
|
ret = PTR_ERR(pxa->clk_io);
|
2011-06-08 17:41:57 +08:00
|
|
|
goto err_clk_get;
|
|
|
|
}
|
2014-10-21 17:22:38 +08:00
|
|
|
pltfm_host->clk = pxa->clk_io;
|
|
|
|
clk_prepare_enable(pxa->clk_io);
|
2011-06-08 17:41:57 +08:00
|
|
|
|
2014-10-21 17:22:40 +08:00
|
|
|
pxa->clk_core = devm_clk_get(dev, "core");
|
|
|
|
if (!IS_ERR(pxa->clk_core))
|
|
|
|
clk_prepare_enable(pxa->clk_core);
|
|
|
|
|
2015-01-29 19:36:25 +08:00
|
|
|
/* enable 1/8V DDR capable */
|
|
|
|
host->mmc->caps |= MMC_CAP_1_8V_DDR;
|
|
|
|
|
mmc: sdhci-pxav3: do the mbus window configuration after enabling clocks
In commit 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada
38x SDHCI controller"), the sdhci-pxav3 driver was extended to include
support for the SDHCI controller found in the Armada 38x
processor. This mainly involved adding some MBus window related
configuration.
However, this configuration is currently done too early in ->probe():
it is done before clocks are enabled, while this configuration
involves touching the registers of the controller, which will hang the
SoC if the clock is disabled. It wasn't noticed until now because the
bootloader typically leaves gatable clocks enabled, but in situations
where we have a deferred probe (due to a CD GPIO that cannot be taken,
for example), then the probe will be re-tried later, after a clock
disable has been done in the exit path of the failed probe attempt of
the device. This second probe() will hang the system due to the clock
being disabled.
This can for example be produced on Armada 385 GP, which has a CD GPIO
connected to an I2C PCA9555. If the driver for the PCA9555 is not
compiled into the kernel, then we will have the following sequence of
events:
1. The SDHCI probes
2. It does the MBus configuration (which works, because the clock is
left enabled by the bootloader)
3. It enables the clock
4. It tries to get the CD GPIO, which fails due to the driver being
missing, so -EPROBE_DEFER is returned.
5. Before returning -EPROBE_DEFER, the driver cleans up what was
done, which includes disabling the clock.
6. Later on, the SDHCI probe is tried again.
7. It does the MBus configuration, which hangs because the clock is
no longer enabled.
This commit does the obvious fix of doing the MBus configuration after
the clock has been enabled by the driver.
Fixes: 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada 38x SDHCI controller")
Cc: <stable@vger.kernel.org> # v3.15+
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
2014-12-31 18:54:10 +08:00
|
|
|
if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) {
|
2015-01-29 19:36:25 +08:00
|
|
|
ret = armada_38x_quirks(pdev, host);
|
2015-01-29 19:36:24 +08:00
|
|
|
if (ret < 0)
|
2015-10-06 09:22:37 +08:00
|
|
|
goto err_mbus_win;
|
mmc: sdhci-pxav3: do the mbus window configuration after enabling clocks
In commit 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada
38x SDHCI controller"), the sdhci-pxav3 driver was extended to include
support for the SDHCI controller found in the Armada 38x
processor. This mainly involved adding some MBus window related
configuration.
However, this configuration is currently done too early in ->probe():
it is done before clocks are enabled, while this configuration
involves touching the registers of the controller, which will hang the
SoC if the clock is disabled. It wasn't noticed until now because the
bootloader typically leaves gatable clocks enabled, but in situations
where we have a deferred probe (due to a CD GPIO that cannot be taken,
for example), then the probe will be re-tried later, after a clock
disable has been done in the exit path of the failed probe attempt of
the device. This second probe() will hang the system due to the clock
being disabled.
This can for example be produced on Armada 385 GP, which has a CD GPIO
connected to an I2C PCA9555. If the driver for the PCA9555 is not
compiled into the kernel, then we will have the following sequence of
events:
1. The SDHCI probes
2. It does the MBus configuration (which works, because the clock is
left enabled by the bootloader)
3. It enables the clock
4. It tries to get the CD GPIO, which fails due to the driver being
missing, so -EPROBE_DEFER is returned.
5. Before returning -EPROBE_DEFER, the driver cleans up what was
done, which includes disabling the clock.
6. Later on, the SDHCI probe is tried again.
7. It does the MBus configuration, which hangs because the clock is
no longer enabled.
This commit does the obvious fix of doing the MBus configuration after
the clock has been enabled by the driver.
Fixes: 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada 38x SDHCI controller")
Cc: <stable@vger.kernel.org> # v3.15+
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
2014-12-31 18:54:10 +08:00
|
|
|
ret = mv_conf_mbus_windows(pdev, mv_mbus_dram_info());
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_mbus_win;
|
|
|
|
}
|
|
|
|
|
2012-04-11 10:34:33 +08:00
|
|
|
match = of_match_device(of_match_ptr(sdhci_pxav3_of_match), &pdev->dev);
|
2013-03-25 17:42:58 +08:00
|
|
|
if (match) {
|
2013-06-10 04:14:15 +08:00
|
|
|
ret = mmc_of_parse(host->mmc);
|
|
|
|
if (ret)
|
|
|
|
goto err_of_parse;
|
2013-03-25 17:42:58 +08:00
|
|
|
sdhci_get_of_property(pdev);
|
2012-04-11 10:34:33 +08:00
|
|
|
pdata = pxav3_get_mmc_pdata(dev);
|
2015-07-23 17:56:23 +08:00
|
|
|
pdev->dev.platform_data = pdata;
|
2013-03-25 17:42:58 +08:00
|
|
|
} else if (pdata) {
|
2013-03-25 17:42:57 +08:00
|
|
|
/* on-chip device */
|
|
|
|
if (pdata->flags & PXA_FLAG_CARD_PERMANENT)
|
2011-06-08 17:41:57 +08:00
|
|
|
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
|
|
|
|
|
|
|
|
/* If slot design supports 8 bit data, indicate this to MMC. */
|
|
|
|
if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT)
|
|
|
|
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
|
|
|
|
|
|
|
|
if (pdata->quirks)
|
|
|
|
host->quirks |= pdata->quirks;
|
2012-10-17 19:04:48 +08:00
|
|
|
if (pdata->quirks2)
|
|
|
|
host->quirks2 |= pdata->quirks2;
|
2011-06-08 17:41:57 +08:00
|
|
|
if (pdata->host_caps)
|
|
|
|
host->mmc->caps |= pdata->host_caps;
|
2012-09-19 16:29:12 +08:00
|
|
|
if (pdata->host_caps2)
|
|
|
|
host->mmc->caps2 |= pdata->host_caps2;
|
2011-06-08 17:41:57 +08:00
|
|
|
if (pdata->pm_caps)
|
|
|
|
host->mmc->pm_caps |= pdata->pm_caps;
|
2012-09-19 16:29:12 +08:00
|
|
|
|
|
|
|
if (gpio_is_valid(pdata->ext_cd_gpio)) {
|
2013-08-08 18:38:31 +08:00
|
|
|
ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio,
|
|
|
|
0);
|
2012-09-19 16:29:12 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_err(mmc_dev(host->mmc),
|
|
|
|
"failed to allocate card detect gpio\n");
|
|
|
|
goto err_cd_req;
|
|
|
|
}
|
|
|
|
}
|
2011-06-08 17:41:57 +08:00
|
|
|
}
|
|
|
|
|
2015-01-04 23:15:47 +08:00
|
|
|
pm_runtime_get_noresume(&pdev->dev);
|
|
|
|
pm_runtime_set_active(&pdev->dev);
|
2013-02-01 17:48:30 +08:00
|
|
|
pm_runtime_set_autosuspend_delay(&pdev->dev, PXAV3_RPM_DELAY_MS);
|
|
|
|
pm_runtime_use_autosuspend(&pdev->dev);
|
2015-01-04 23:15:47 +08:00
|
|
|
pm_runtime_enable(&pdev->dev);
|
2013-02-01 17:48:30 +08:00
|
|
|
pm_suspend_ignore_children(&pdev->dev, 1);
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
ret = sdhci_add_host(host);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev, "failed to add host\n");
|
|
|
|
goto err_add_host;
|
|
|
|
}
|
|
|
|
|
2015-06-02 18:38:35 +08:00
|
|
|
if (host->mmc->pm_caps & MMC_PM_WAKE_SDIO_IRQ)
|
2013-01-15 03:38:53 +08:00
|
|
|
device_init_wakeup(&pdev->dev, 1);
|
|
|
|
|
2013-02-01 17:48:30 +08:00
|
|
|
pm_runtime_put_autosuspend(&pdev->dev);
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_add_host:
|
2013-06-27 23:46:29 +08:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
2015-01-04 23:15:47 +08:00
|
|
|
pm_runtime_put_noidle(&pdev->dev);
|
2014-07-16 15:50:09 +08:00
|
|
|
err_of_parse:
|
|
|
|
err_cd_req:
|
mmc: sdhci-pxav3: do the mbus window configuration after enabling clocks
In commit 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada
38x SDHCI controller"), the sdhci-pxav3 driver was extended to include
support for the SDHCI controller found in the Armada 38x
processor. This mainly involved adding some MBus window related
configuration.
However, this configuration is currently done too early in ->probe():
it is done before clocks are enabled, while this configuration
involves touching the registers of the controller, which will hang the
SoC if the clock is disabled. It wasn't noticed until now because the
bootloader typically leaves gatable clocks enabled, but in situations
where we have a deferred probe (due to a CD GPIO that cannot be taken,
for example), then the probe will be re-tried later, after a clock
disable has been done in the exit path of the failed probe attempt of
the device. This second probe() will hang the system due to the clock
being disabled.
This can for example be produced on Armada 385 GP, which has a CD GPIO
connected to an I2C PCA9555. If the driver for the PCA9555 is not
compiled into the kernel, then we will have the following sequence of
events:
1. The SDHCI probes
2. It does the MBus configuration (which works, because the clock is
left enabled by the bootloader)
3. It enables the clock
4. It tries to get the CD GPIO, which fails due to the driver being
missing, so -EPROBE_DEFER is returned.
5. Before returning -EPROBE_DEFER, the driver cleans up what was
done, which includes disabling the clock.
6. Later on, the SDHCI probe is tried again.
7. It does the MBus configuration, which hangs because the clock is
no longer enabled.
This commit does the obvious fix of doing the MBus configuration after
the clock has been enabled by the driver.
Fixes: 5491ce3f79ee ("mmc: sdhci-pxav3: add support for the Armada 38x SDHCI controller")
Cc: <stable@vger.kernel.org> # v3.15+
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
2014-12-31 18:54:10 +08:00
|
|
|
err_mbus_win:
|
2014-10-21 17:22:38 +08:00
|
|
|
clk_disable_unprepare(pxa->clk_io);
|
2015-01-05 15:59:19 +08:00
|
|
|
clk_disable_unprepare(pxa->clk_core);
|
2011-06-08 17:41:57 +08:00
|
|
|
err_clk_get:
|
|
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-11-20 02:26:03 +08:00
|
|
|
static int sdhci_pxav3_remove(struct platform_device *pdev)
|
2011-06-08 17:41:57 +08:00
|
|
|
{
|
|
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2011-06-08 17:41:57 +08:00
|
|
|
|
2013-02-01 17:48:30 +08:00
|
|
|
pm_runtime_get_sync(&pdev->dev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
2015-01-04 23:15:48 +08:00
|
|
|
pm_runtime_put_noidle(&pdev->dev);
|
|
|
|
|
|
|
|
sdhci_remove_host(host, 1);
|
2011-06-08 17:41:57 +08:00
|
|
|
|
2014-10-21 17:22:38 +08:00
|
|
|
clk_disable_unprepare(pxa->clk_io);
|
2015-01-05 15:59:19 +08:00
|
|
|
clk_disable_unprepare(pxa->clk_core);
|
2012-09-19 16:29:12 +08:00
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-01 17:48:30 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
static int sdhci_pxav3_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
pm_runtime_get_sync(dev);
|
2017-03-21 01:50:32 +08:00
|
|
|
if (host->tuning_mode != SDHCI_TUNING_MODE_3)
|
|
|
|
mmc_retune_needed(host->mmc);
|
2013-02-01 17:48:30 +08:00
|
|
|
ret = sdhci_suspend_host(host);
|
|
|
|
pm_runtime_mark_last_busy(dev);
|
|
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdhci_pxav3_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
pm_runtime_get_sync(dev);
|
|
|
|
ret = sdhci_resume_host(host);
|
|
|
|
pm_runtime_mark_last_busy(dev);
|
|
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-12-05 10:05:33 +08:00
|
|
|
#ifdef CONFIG_PM
|
2013-02-01 17:48:30 +08:00
|
|
|
static int sdhci_pxav3_runtime_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2015-01-23 18:08:21 +08:00
|
|
|
int ret;
|
2013-02-01 17:48:30 +08:00
|
|
|
|
2015-01-23 18:08:21 +08:00
|
|
|
ret = sdhci_runtime_suspend_host(host);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2013-02-01 17:48:30 +08:00
|
|
|
|
2017-03-21 01:50:32 +08:00
|
|
|
if (host->tuning_mode != SDHCI_TUNING_MODE_3)
|
|
|
|
mmc_retune_needed(host->mmc);
|
|
|
|
|
2014-10-21 17:22:38 +08:00
|
|
|
clk_disable_unprepare(pxa->clk_io);
|
2014-10-21 17:22:40 +08:00
|
|
|
if (!IS_ERR(pxa->clk_core))
|
|
|
|
clk_disable_unprepare(pxa->clk_core);
|
2013-02-01 17:48:30 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sdhci_pxav3_runtime_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
2016-02-16 21:08:27 +08:00
|
|
|
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
|
2013-02-01 17:48:30 +08:00
|
|
|
|
2014-10-21 17:22:38 +08:00
|
|
|
clk_prepare_enable(pxa->clk_io);
|
2014-10-21 17:22:40 +08:00
|
|
|
if (!IS_ERR(pxa->clk_core))
|
|
|
|
clk_prepare_enable(pxa->clk_core);
|
2013-02-01 17:48:30 +08:00
|
|
|
|
2015-01-23 18:08:21 +08:00
|
|
|
return sdhci_runtime_resume_host(host);
|
2013-02-01 17:48:30 +08:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct dev_pm_ops sdhci_pxav3_pmops = {
|
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(sdhci_pxav3_suspend, sdhci_pxav3_resume)
|
|
|
|
SET_RUNTIME_PM_OPS(sdhci_pxav3_runtime_suspend,
|
|
|
|
sdhci_pxav3_runtime_resume, NULL)
|
|
|
|
};
|
|
|
|
|
2011-06-08 17:41:57 +08:00
|
|
|
static struct platform_driver sdhci_pxav3_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "sdhci-pxav3",
|
2015-05-05 17:11:54 +08:00
|
|
|
.of_match_table = of_match_ptr(sdhci_pxav3_of_match),
|
2016-07-27 17:08:41 +08:00
|
|
|
.pm = &sdhci_pxav3_pmops,
|
2011-06-08 17:41:57 +08:00
|
|
|
},
|
|
|
|
.probe = sdhci_pxav3_probe,
|
2012-11-20 02:20:26 +08:00
|
|
|
.remove = sdhci_pxav3_remove,
|
2011-06-08 17:41:57 +08:00
|
|
|
};
|
|
|
|
|
2011-11-26 12:55:43 +08:00
|
|
|
module_platform_driver(sdhci_pxav3_driver);
|
2011-06-08 17:41:57 +08:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("SDHCI driver for pxav3");
|
|
|
|
MODULE_AUTHOR("Marvell International Ltd.");
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
|