summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sdhci-pxav2.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/sdhci-pxav2.c')
-rw-r--r--drivers/mmc/host/sdhci-pxav2.c154
1 files changed, 141 insertions, 13 deletions
diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c
index f18906b5575f..fc306eb1f845 100644
--- a/drivers/mmc/host/sdhci-pxav2.c
+++ b/drivers/mmc/host/sdhci-pxav2.c
@@ -20,6 +20,9 @@
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/mmc.h>
+#include <linux/pinctrl/consumer.h>
#include "sdhci.h"
#include "sdhci-pltfm.h"
@@ -41,6 +44,13 @@
#define MMC_CARD 0x1000
#define MMC_WIDTH 0x0100
+struct sdhci_pxav2_host {
+ struct mmc_request *sdio_mrq;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_default;
+ struct pinctrl_state *pins_cmd_gpio;
+};
+
static void pxav2_reset(struct sdhci_host *host, u8 mask)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
@@ -80,6 +90,71 @@ static void pxav2_reset(struct sdhci_host *host, u8 mask)
}
}
+static u16 pxav1_readw(struct sdhci_host *host, int reg)
+{
+ /* Workaround for data abort exception on SDH2 and SDH4 on PXA168 */
+ if (reg == SDHCI_HOST_VERSION)
+ return readl(host->ioaddr + SDHCI_HOST_VERSION - 2) >> 16;
+
+ return readw(host->ioaddr + reg);
+}
+
+static u32 pxav1_irq(struct sdhci_host *host, u32 intmask)
+{
+ struct sdhci_pxav2_host *pxav2_host = sdhci_pltfm_priv(sdhci_priv(host));
+ struct mmc_request *sdio_mrq;
+
+ if (pxav2_host->sdio_mrq && (intmask & SDHCI_INT_CMD_MASK)) {
+ /* The dummy CMD0 for the SDIO workaround just completed */
+ sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, SDHCI_INT_STATUS);
+ intmask &= ~SDHCI_INT_CMD_MASK;
+
+ /* Restore MMC function to CMD pin */
+ if (pxav2_host->pinctrl && pxav2_host->pins_default)
+ pinctrl_select_state(pxav2_host->pinctrl, pxav2_host->pins_default);
+
+ sdio_mrq = pxav2_host->sdio_mrq;
+ pxav2_host->sdio_mrq = NULL;
+ mmc_request_done(host->mmc, sdio_mrq);
+ }
+
+ return intmask;
+}
+
+static void pxav1_request_done(struct sdhci_host *host, struct mmc_request *mrq)
+{
+ u16 tmp;
+ struct sdhci_pxav2_host *pxav2_host;
+
+ /* If this is an SDIO command, perform errata workaround for silicon bug */
+ if (mrq->cmd && !mrq->cmd->error &&
+ (mrq->cmd->opcode == SD_IO_RW_DIRECT ||
+ mrq->cmd->opcode == SD_IO_RW_EXTENDED)) {
+ /* Reset data port */
+ tmp = readw(host->ioaddr + SDHCI_TIMEOUT_CONTROL);
+ tmp |= 0x400;
+ writew(tmp, host->ioaddr + SDHCI_TIMEOUT_CONTROL);
+
+ /* Clock is now stopped, so restart it by sending a dummy CMD0 */
+ pxav2_host = sdhci_pltfm_priv(sdhci_priv(host));
+ pxav2_host->sdio_mrq = mrq;
+
+ /* Set CMD as high output rather than MMC function while we do CMD0 */
+ if (pxav2_host->pinctrl && pxav2_host->pins_cmd_gpio)
+ pinctrl_select_state(pxav2_host->pinctrl, pxav2_host->pins_cmd_gpio);
+
+ sdhci_writel(host, 0, SDHCI_ARGUMENT);
+ sdhci_writew(host, 0, SDHCI_TRANSFER_MODE);
+ sdhci_writew(host, SDHCI_MAKE_CMD(MMC_GO_IDLE_STATE, SDHCI_CMD_RESP_NONE),
+ SDHCI_COMMAND);
+
+ /* Don't finish this request until the dummy CMD0 finishes */
+ return;
+ }
+
+ mmc_request_done(host->mmc, mrq);
+}
+
static void pxav2_mmc_set_bus_width(struct sdhci_host *host, int width)
{
u8 ctrl;
@@ -101,6 +176,27 @@ static void pxav2_mmc_set_bus_width(struct sdhci_host *host, int width)
writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL);
}
+struct sdhci_pxa_variant {
+ const struct sdhci_ops *ops;
+ unsigned int extra_quirks;
+};
+
+static const struct sdhci_ops pxav1_sdhci_ops = {
+ .read_w = pxav1_readw,
+ .set_clock = sdhci_set_clock,
+ .irq = pxav1_irq,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+ .set_bus_width = pxav2_mmc_set_bus_width,
+ .reset = pxav2_reset,
+ .set_uhs_signaling = sdhci_set_uhs_signaling,
+ .request_done = pxav1_request_done,
+};
+
+static const struct sdhci_pxa_variant __maybe_unused pxav1_variant = {
+ .ops = &pxav1_sdhci_ops,
+ .extra_quirks = SDHCI_QUIRK_NO_BUSY_IRQ | SDHCI_QUIRK_32BIT_DMA_SIZE,
+};
+
static const struct sdhci_ops pxav2_sdhci_ops = {
.set_clock = sdhci_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
@@ -109,11 +205,14 @@ static const struct sdhci_ops pxav2_sdhci_ops = {
.set_uhs_signaling = sdhci_set_uhs_signaling,
};
+static const struct sdhci_pxa_variant pxav2_variant = {
+ .ops = &pxav2_sdhci_ops,
+};
+
#ifdef CONFIG_OF
static const struct of_device_id sdhci_pxav2_of_match[] = {
- {
- .compatible = "mrvl,pxav2-mmc",
- },
+ { .compatible = "mrvl,pxav1-mmc", .data = &pxav1_variant, },
+ { .compatible = "mrvl,pxav2-mmc", .data = &pxav2_variant, },
{},
};
MODULE_DEVICE_TABLE(of, sdhci_pxav2_of_match);
@@ -155,40 +254,53 @@ static int sdhci_pxav2_probe(struct platform_device *pdev)
{
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
+ struct sdhci_pxav2_host *pxav2_host;
struct device *dev = &pdev->dev;
struct sdhci_host *host = NULL;
- const struct of_device_id *match;
+ const struct sdhci_pxa_variant *variant;
int ret;
- struct clk *clk;
+ struct clk *clk, *clk_core;
- host = sdhci_pltfm_init(pdev, NULL, 0);
+ host = sdhci_pltfm_init(pdev, NULL, sizeof(*pxav2_host));
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
+ pxav2_host = sdhci_pltfm_priv(pltfm_host);
- clk = devm_clk_get(dev, "PXA-SDHCLK");
+ clk = devm_clk_get(dev, "io");
+ if (IS_ERR(clk) && PTR_ERR(clk) != -EPROBE_DEFER)
+ clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
- dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(clk);
+ dev_err_probe(dev, ret, "failed to get io clock\n");
goto free;
}
pltfm_host->clk = clk;
ret = clk_prepare_enable(clk);
if (ret) {
- dev_err(&pdev->dev, "failed to enable io clock\n");
+ dev_err(dev, "failed to enable io clock\n");
goto free;
}
+ clk_core = devm_clk_get_optional_enabled(dev, "core");
+ if (IS_ERR(clk_core)) {
+ ret = PTR_ERR(clk_core);
+ dev_err_probe(dev, ret, "failed to enable core clock\n");
+ goto disable_clk;
+ }
+
host->quirks = SDHCI_QUIRK_BROKEN_ADMA
| SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
- match = of_match_device(of_match_ptr(sdhci_pxav2_of_match), &pdev->dev);
- if (match) {
+ variant = of_device_get_match_data(dev);
+ if (variant)
pdata = pxav2_get_mmc_pdata(dev);
- }
+ else
+ variant = &pxav2_variant;
+
if (pdata) {
if (pdata->flags & PXA_FLAG_CARD_PERMANENT) {
/* on-chip device */
@@ -208,7 +320,23 @@ static int sdhci_pxav2_probe(struct platform_device *pdev)
host->mmc->pm_caps |= pdata->pm_caps;
}
- host->ops = &pxav2_sdhci_ops;
+ host->quirks |= variant->extra_quirks;
+ host->ops = variant->ops;
+
+ /* Set up optional pinctrl for PXA168 SDIO IRQ fix */
+ pxav2_host->pinctrl = devm_pinctrl_get(dev);
+ if (!IS_ERR(pxav2_host->pinctrl)) {
+ pxav2_host->pins_cmd_gpio = pinctrl_lookup_state(pxav2_host->pinctrl,
+ "state_cmd_gpio");
+ if (IS_ERR(pxav2_host->pins_cmd_gpio))
+ pxav2_host->pins_cmd_gpio = NULL;
+ pxav2_host->pins_default = pinctrl_lookup_state(pxav2_host->pinctrl,
+ "default");
+ if (IS_ERR(pxav2_host->pins_default))
+ pxav2_host->pins_default = NULL;
+ } else {
+ pxav2_host->pinctrl = NULL;
+ }
ret = sdhci_add_host(host);
if (ret)