diff options
Diffstat (limited to 'drivers/mci/atmel-sdhci.c')
-rw-r--r-- | drivers/mci/atmel-sdhci.c | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/drivers/mci/atmel-sdhci.c b/drivers/mci/atmel-sdhci.c new file mode 100644 index 0000000000..6351186476 --- /dev/null +++ b/drivers/mci/atmel-sdhci.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Atmel SDMMC controller driver. + * + * Copyright (C) 2015 Atmel, + * 2015 Ludovic Desroches <ludovic.desroches@atmel.com> + * 2020 Ahmad Fatoum <a.fatoum@pengutronix.de> + */ + +#include <common.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <of.h> +#include <mci.h> + +#include "atmel-sdhci.h" + +#define ATMEL_SDHC_MIN_FREQ 400000 +#define ATMEL_SDHC_GCK_RATE 240000000 + +struct at91_sdhci_priv { + struct at91_sdhci host; + struct mci_host mci; + struct clk *hclock, *gck, *mainck; + bool cal_always_on; + u32 gck_rate; +}; + +#define to_priv(h) container_of(h, struct at91_sdhci_priv, mci) + +static int at91_sdhci_mci_send_cmd(struct mci_host *mci, struct mci_cmd *cmd, + struct mci_data *data) +{ + return at91_sdhci_send_command(&to_priv(mci)->host, cmd, data); +} + +static void at91_sdhci_mci_set_ios(struct mci_host *mci, struct mci_ios *ios) +{ + at91_sdhci_set_ios(&to_priv(mci)->host, ios); +} + +static int at91_sdhci_mci_init(struct mci_host *mci, struct device_d *dev) +{ + struct at91_sdhci_priv *priv = to_priv(mci); + struct sdhci *sdhci = &priv->host.sdhci; + int ret; + + ret = sdhci_reset(sdhci, SDHCI_RESET_ALL); + if (ret) + return ret; + + return at91_sdhci_init(&priv->host, priv->gck_rate, + priv->mci.non_removable, priv->cal_always_on); +} + +static int at91_sdhci_conf_clks(struct at91_sdhci_priv *priv) +{ + unsigned long real_gck_rate; + int ret; + + /* + * The mult clock is provided by as a generated clock by the PMC + * controller. In order to set the rate of gck, we have to get the + * base clock rate and the clock mult from capabilities. + */ + clk_enable(priv->hclock); + ret = clk_set_rate(priv->gck, ATMEL_SDHC_GCK_RATE); + if (ret < 0) { + clk_disable(priv->hclock); + return ret; + } + + real_gck_rate = clk_get_rate(priv->gck); + + clk_enable(priv->mainck); + clk_enable(priv->gck); + + return clamp_t(int, real_gck_rate, ATMEL_SDHC_MIN_FREQ, INT_MAX); +} + +static void at91_sdhci_set_mci_caps(struct at91_sdhci_priv *priv) +{ + struct mci_host *mci = &priv->mci; + at91_sdhci_host_capability(&priv->host, &mci->voltages); + + if (mci->f_max >= 26000000) + mci->host_caps |= MMC_CAP_MMC_HIGHSPEED; + if (mci->f_max >= 52000000) + mci->host_caps |= MMC_CAP_MMC_HIGHSPEED_52MHZ; + + mci_of_parse(mci); +} + +static int at91_sdhci_card_present(struct mci_host *mci) +{ + return at91_sdhci_is_card_inserted(&to_priv(mci)->host); +} + +static int at91_sdhci_probe(struct device_d *dev) +{ + struct at91_sdhci_priv *priv; + struct resource *iores; + + priv = xzalloc(sizeof(*priv)); + dev->priv = priv; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + dev_err(dev, "could not get iomem region\n"); + return PTR_ERR(iores); + } + + priv->mainck = clk_get(dev, "baseclk"); + if (IS_ERR(priv->mainck)) { + dev_err(dev, "failed to get baseclk\n"); + return PTR_ERR(priv->mainck); + } + + priv->hclock = clk_get(dev, "hclock"); + if (IS_ERR(priv->hclock)) { + dev_err(dev, "failed to get hclock\n"); + return PTR_ERR(priv->hclock); + } + + priv->gck = clk_get(dev, "multclk"); + if (IS_ERR(priv->gck)) { + dev_err(dev, "failed to get multclk\n"); + return PTR_ERR(priv->gck); + } + + /* + * if SDCAL pin is wrongly connected, we must enable + * the analog calibration cell permanently. + */ + priv->cal_always_on = of_property_read_bool(dev->device_node, + "microchip,sdcal-inverted"); + + at91_sdhci_mmio_init(&priv->host, IOMEM(iores->start)); + + priv->gck_rate = at91_sdhci_conf_clks(priv); + if (priv->gck_rate < 0) + return priv->gck_rate; + + priv->mci.hw_dev = dev; + priv->mci.send_cmd = at91_sdhci_mci_send_cmd; + priv->mci.set_ios = at91_sdhci_mci_set_ios; + priv->mci.init = at91_sdhci_mci_init; + priv->mci.f_max = priv->gck_rate; + priv->mci.f_min = ATMEL_SDHC_MIN_FREQ; + priv->mci.card_present = at91_sdhci_card_present; + + at91_sdhci_set_mci_caps(priv); + + return mci_register(&priv->mci); +} + +static const struct of_device_id at91_sdhci_dt_match[] = { + { .compatible = "atmel,sama5d2-sdhci" }, + { .compatible = "microchip,sam9x60-sdhci" }, + { /* sentinel */ } +}; + +static struct driver_d at91_sdhci_driver = { + .name = "sdhci-at91", + .of_compatible = DRV_OF_COMPAT(at91_sdhci_dt_match), + .probe = at91_sdhci_probe, +}; +device_platform_driver(at91_sdhci_driver); |