blob: 1f048c069d288f715c58e928fb60d1dbe638601d [file] [log] [blame]
/*
* Marvell SDHCI controller driver for the platform bus.
*
* Author: Jisheng Zhang <jszhang@marvell.com>
* Copyright: Marvell International Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sdio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include "sdhci-pltfm.h"
struct pltfm_mv_data {
int flags;
};
static const struct of_device_id sdhci_mv_dt_ids[] = {
{ .compatible = "mrvl,berlin-sdhci",},
{}
};
MODULE_DEVICE_TABLE(of, sdhci_mv_dt_ids);
static unsigned int sdhci_mv_get_max_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return clk_get_rate(pltfm_host->clk);
}
static struct sdhci_ops sdhci_mv_ops = {
.get_max_clock = sdhci_mv_get_max_clock,
};
static struct sdhci_pltfm_data sdhci_mv_pdata = {
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
.ops = &sdhci_mv_ops,
};
static void sdhci_mv_probe_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct sdhci_host *host = platform_get_drvdata(pdev);
if (of_get_property(np, "mrvl,card-wired", NULL)) {
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
}
if (of_get_property(np, "mrvl,8bit-data", NULL))
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
if (of_get_property(np, "mrvl,host-off-card-on", NULL))
host->quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON;
if (of_get_property(np, "mrvl,no-hispd", NULL))
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
}
static int sdhci_mv_probe(struct platform_device *pdev)
{
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_host *host;
struct clk *clk;
int err;
struct pltfm_mv_data *mv_data;
host = sdhci_pltfm_init(pdev, &sdhci_mv_pdata);
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
mv_data = devm_kzalloc(&pdev->dev, sizeof(*mv_data), GFP_KERNEL);
if (!mv_data) {
err = -ENOMEM;
goto err_mv_data;
}
pltfm_host->priv = mv_data;
clk = clk_get(mmc_dev(host->mmc), NULL);
if (IS_ERR(clk)) {
dev_err(mmc_dev(host->mmc), "clk err\n");
err = PTR_ERR(clk);
goto err_mv_data;
}
clk_prepare_enable(clk);
pltfm_host->clk = clk;
host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
sdhci_mv_probe_dt(pdev);
host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;
err = sdhci_add_host(host);
if (err)
goto err_add_host;
return 0;
err_add_host:
clk_disable_unprepare(pltfm_host->clk);
clk_put(pltfm_host->clk);
err_mv_data:
sdhci_pltfm_free(pdev);
return err;
}
static int sdhci_mv_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
sdhci_remove_host(host, dead);
clk_disable_unprepare(pltfm_host->clk);
clk_put(pltfm_host->clk);
sdhci_pltfm_free(pdev);
return 0;
}
static struct platform_driver sdhci_mv_driver = {
.driver = {
.name = "mv-sdhci",
.owner = THIS_MODULE,
.of_match_table = sdhci_mv_dt_ids,
.pm = SDHCI_PLTFM_PMOPS,
},
.probe = sdhci_mv_probe,
.remove = sdhci_mv_remove,
};
module_platform_driver(sdhci_mv_driver);
MODULE_DESCRIPTION("SDHCI driver for Marvell Berlin SoC");
MODULE_AUTHOR("Jisheng Zhang <jszhang@marvell.com>");
MODULE_LICENSE("GPL v2");