Hi,
This set of patches introduce a driver for the new Atmel SDMMC device avaible
on SAMA5D2 SoC.
There is also a resend of an old patch which has not been taken. Ulf asked
for some reviews since it could impact all sdhci devices but nobody did it...
Regards
Ludovic Desroches (3):
mmc: sdhci: switch from programmable clock mode to divided one if
needed
mmc: sdhci-of-at91: introduce driver for the Atmel SDMMC
MAINTAINERS: add entry for Atmel sdhci-of-at91 driver
.../devicetree/bindings/mmc/sdhci-atmel.txt | 21 +++
MAINTAINERS | 6 +
drivers/mmc/host/Kconfig | 8 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-of-at91.c | 188 +++++++++++++++++++++
drivers/mmc/host/sdhci.c | 29 +++-
6 files changed, 244 insertions(+), 9 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
create mode 100644 drivers/mmc/host/sdhci-of-at91.c
--
2.2.0
In programmable mode, if the clock frequency is too high, the divider
can be too small to meet the clock frequency requirement especially to
init the SD card. In this case, switch to the divided clock mode.
Signed-off-by: Ludovic Desroches <[email protected]>
---
drivers/mmc/host/sdhci.c | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index bc14452..32cf274 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1152,6 +1152,7 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
int real_div = div, clk_mul = 1;
u16 clk = 0;
unsigned long timeout;
+ bool switch_base_clk = false;
host->mmc->actual_clock = 0;
@@ -1189,15 +1190,25 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
<= clock)
break;
}
- /*
- * Set Programmable Clock Mode in the Clock
- * Control register.
- */
- clk = SDHCI_PROG_CLOCK_MODE;
- real_div = div;
- clk_mul = host->clk_mul;
- div--;
- } else {
+ if ((host->max_clk * host->clk_mul / div) <= clock) {
+ /*
+ * Set Programmable Clock Mode in the Clock
+ * Control register.
+ */
+ clk = SDHCI_PROG_CLOCK_MODE;
+ real_div = div;
+ clk_mul = host->clk_mul;
+ div--;
+ } else {
+ /*
+ * Divisor can be too small to reach clock
+ * speed requirement. Then use the base clock.
+ */
+ switch_base_clk = true;
+ }
+ }
+
+ if (!host->clk_mul || switch_base_clk) {
/* Version 3.00 divisors must be a multiple of 2. */
if (host->max_clk <= clock)
div = 1;
--
2.2.0
Introduce driver for he Atmel SDMMC available on sama5d2. It is a sdhci
compliant controller.
Signed-off-by: Ludovic Desroches <[email protected]>
---
.../devicetree/bindings/mmc/sdhci-atmel.txt | 21 +++
drivers/mmc/host/Kconfig | 8 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/sdhci-of-at91.c | 188 +++++++++++++++++++++
4 files changed, 218 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
create mode 100644 drivers/mmc/host/sdhci-of-at91.c
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
new file mode 100644
index 0000000..1b662d7
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
@@ -0,0 +1,21 @@
+* Atmel SDHCI controller
+
+This file documents the differences between the core properties in
+Documentation/devicetree/bindings/mmc/mmc.txt and the properties used by the
+sdhci-of-at91 driver.
+
+Required properties:
+- compatible: Must be "atmel,sama5d2-sdhci".
+- clocks: Phandlers to the clocks.
+- clock-names: Must be "hclock", "multclk", "baseclk";
+
+
+Example:
+
+sdmmc0: sdio-host@a0000000 {
+ compatible = "atmel,sama5d2-sdhci";
+ reg = <0xa0000000 0x300>;
+ interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&sdmmc0_hclk>, <&sdmmc0_gclk>, <&main>;
+ clock-names = "hclock", "multclk", "baseclk";
+};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index fd9a58e..c1c75e8 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -129,6 +129,14 @@ config MMC_SDHCI_OF_ARASAN
If unsure, say N.
+config MMC_SDHCI_OF_AT91
+ tristate "SDHCI OF support for the Atmel SDMMC controller"
+ depends on MMC_SDHCI_PLTFM
+ depends on OF
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the Atmel SDMMC driver
+
config MMC_SDHCI_OF_ESDHC
tristate "SDHCI OF support for the Freescale eSDHC controller"
depends on MMC_SDHCI_PLTFM
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e928d61..4f3452a 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o
obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o
obj-$(CONFIG_MMC_SDHCI_OF_ARASAN) += sdhci-of-arasan.o
+obj-$(CONFIG_MMC_SDHCI_OF_AT91) += sdhci-of-at91.o
obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
new file mode 100644
index 0000000..2e3716a
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -0,0 +1,188 @@
+/*
+ * Atmel SDMMC controller driver.
+ *
+ * Copyright (C) 2015 Atmel,
+ * 2015 Ludovic Desroches <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include "sdhci-pltfm.h"
+
+#define SDMMC_CACR 0x230
+#define SDMMC_CACR_CAPWREN BIT(0)
+#define SDMMC_CACR_KEY (0x46 << 8)
+
+struct sdhci_at91_priv {
+ struct clk *hclock;
+ struct clk *gck;
+ struct clk *mainck;
+};
+
+static const struct sdhci_ops sdhci_at91_sama5d2_ops = {
+ .set_clock = sdhci_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = sdhci_reset,
+ .set_uhs_signaling = sdhci_set_uhs_signaling,
+};
+
+static const struct sdhci_pltfm_data soc_data_sama5d2 = {
+ .ops = &sdhci_at91_sama5d2_ops,
+};
+
+static const struct of_device_id sdhci_at91_dt_match[] = {
+ { .compatible = "atmel,sama5d2-sdhci", .data = &soc_data_sama5d2 },
+ {}
+};
+
+static int sdhci_at91_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ const struct sdhci_pltfm_data *soc_data;
+ struct sdhci_host *host;
+ struct sdhci_pltfm_host *pltfm_host;
+ struct sdhci_at91_priv *priv;
+ unsigned int caps0, caps1;
+ unsigned int clk_base, clk_mul;
+ unsigned int gck_rate, real_gck_rate;
+ int ret;
+
+ match = of_match_device(sdhci_at91_dt_match, &pdev->dev);
+ if (!match)
+ return -EINVAL;
+ soc_data = match->data;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ priv->mainck = devm_clk_get(&pdev->dev, "baseclk");
+ if (IS_ERR(priv->mainck)) {
+ dev_err(&pdev->dev, "failed to get baseclk\n");
+ return PTR_ERR(priv->mainck);
+ }
+
+ priv->hclock = devm_clk_get(&pdev->dev, "hclock");
+ if (IS_ERR(priv->hclock)) {
+ dev_err(&pdev->dev, "failed to get hclock\n");
+ return PTR_ERR(priv->hclock);
+ }
+
+ priv->gck = devm_clk_get(&pdev->dev, "multclk");
+ if (IS_ERR(priv->gck)) {
+ dev_err(&pdev->dev, "failed to get multclk\n");
+ return PTR_ERR(priv->gck);
+ }
+
+ host = sdhci_pltfm_init(pdev, soc_data, 0);
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ /*
+ * 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_prepare_enable(priv->hclock);
+ caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
+ caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
+ clk_base = (caps0 & SDHCI_CLOCK_V3_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT;
+ clk_mul = (caps1 & SDHCI_CLOCK_MUL_MASK) >> SDHCI_CLOCK_MUL_SHIFT;
+ gck_rate = clk_base * 1000000 * (clk_mul + 1);
+ ret = clk_set_rate(priv->gck, gck_rate);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to set gck");
+ goto hclock_disable_unprepare;
+ return -EINVAL;
+ }
+ /*
+ * We need to check if we have the requested rate for gck because in
+ * some cases this rate could be not supported. If it happens, the rate
+ * is the closest one gck can provide. We have to update the value
+ * of clk mul.
+ */
+ real_gck_rate = clk_get_rate(priv->gck);
+ if (real_gck_rate != gck_rate) {
+ clk_mul = real_gck_rate / (clk_base * 1000000) - 1;
+ caps1 &= (~SDHCI_CLOCK_MUL_MASK);
+ caps1 |= ((clk_mul << SDHCI_CLOCK_MUL_SHIFT) & SDHCI_CLOCK_MUL_MASK);
+ /* Set capabilities in r/w mode. */
+ writel(SDMMC_CACR_KEY | SDMMC_CACR_CAPWREN, host->ioaddr + SDMMC_CACR);
+ writel(caps1, host->ioaddr + SDHCI_CAPABILITIES_1);
+ /* Set capabilities in ro mode. */
+ writel(0, host->ioaddr + SDMMC_CACR);
+ dev_info(&pdev->dev, "update clk mul to %u as gck rate is %u Hz\n",
+ clk_mul, real_gck_rate);
+ }
+
+ clk_prepare_enable(priv->mainck);
+ clk_prepare_enable(priv->gck);
+
+ pltfm_host = sdhci_priv(host);
+ pltfm_host->priv = priv;
+
+ ret = mmc_of_parse(host->mmc);
+ if (ret)
+ goto clocks_disable_unprepare;
+
+ sdhci_get_of_property(pdev);
+
+ ret = sdhci_add_host(host);
+ if (ret)
+ goto clocks_disable_unprepare;
+
+ return 0;
+
+clocks_disable_unprepare:
+ clk_disable_unprepare(priv->gck);
+ clk_disable_unprepare(priv->mainck);
+hclock_disable_unprepare:
+ clk_disable_unprepare(priv->hclock);
+ sdhci_pltfm_free(pdev);
+ return ret;
+}
+
+static int sdhci_at91_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_at91_priv *priv = pltfm_host->priv;
+
+ sdhci_pltfm_unregister(pdev);
+
+ clk_disable_unprepare(priv->gck);
+ clk_disable_unprepare(priv->hclock);
+ clk_disable_unprepare(priv->mainck);
+
+ return 0;
+}
+
+static struct platform_driver sdhci_at91_driver = {
+ .driver = {
+ .name = "sdhci-at91",
+ .owner = THIS_MODULE,
+ .of_match_table = sdhci_at91_dt_match,
+ .pm = SDHCI_PLTFM_PMOPS,
+ },
+ .probe = sdhci_at91_probe,
+ .remove = sdhci_at91_remove,
+};
+
+module_platform_driver(sdhci_at91_driver);
+
+MODULE_DESCRIPTION("SDHCI driver for at91");
+MODULE_AUTHOR("Ludovic Desroches <[email protected]>");
+MODULE_LICENSE("GPL v2");
--
2.2.0
Add an entry for Atmel SDMMC device.
Signed-off-by: Ludovic Desroches <[email protected]>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e7bdbac..6480ce9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1880,6 +1880,12 @@ L: [email protected]
S: Supported
F: drivers/mtd/nand/atmel_nand*
+ATMEL SDMMC DRIVER
+M: Ludovic Desroches <[email protected]>
+L: [email protected]
+S: Supported
+F: drivers/mmc/host/sdhci-of-at91.c
+
ATMEL SPI DRIVER
M: Nicolas Ferre <[email protected]>
S: Supported
--
2.2.0
A nit only: a license mismatch.
On vr, 2015-07-03 at 16:17 +0200, Ludovic Desroches wrote:
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-of-at91.c
>
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or (at
> + * your option) any later version.
This states the license is GPL v2 or later.
> +MODULE_LICENSE("GPL v2");
And, according to include/linux/module.h, this states the license is
(only) GPL v2. So I think that either the comment at the top of this
file or the ident used in MODULE_LICENSE() needs to change.
Thanks,
Paul Bolle
On Friday 03 July 2015 16:17:16 Ludovic Desroches wrote:
> Introduce driver for he Atmel SDMMC available on sama5d2. It is a sdhci
> compliant controller.
>
> Signed-off-by: Ludovic Desroches <[email protected]>
> ---
> .../devicetree/bindings/mmc/sdhci-atmel.txt | 21 +++
> drivers/mmc/host/Kconfig | 8 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/sdhci-of-at91.c | 188 +++++++++++++++++++++
> 4 files changed, 218 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> create mode 100644 drivers/mmc/host/sdhci-of-at91.c
>
> diff --git a/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> new file mode 100644
> index 0000000..1b662d7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> @@ -0,0 +1,21 @@
> +* Atmel SDHCI controller
> +
> +This file documents the differences between the core properties in
> +Documentation/devicetree/bindings/mmc/mmc.txt and the properties used by the
> +sdhci-of-at91 driver.
> +
> +Required properties:
> +- compatible: Must be "atmel,sama5d2-sdhci".
> +- clocks: Phandlers to the clocks.
> +- clock-names: Must be "hclock", "multclk", "baseclk";
> +
Are you sure that these are all new clocks that are unrelated to the "core",
"mmc", and "iface" clocks that are used in other drivers?
> +
> + priv->mainck = devm_clk_get(&pdev->dev, "baseclk");
> + if (IS_ERR(priv->mainck)) {
> + dev_err(&pdev->dev, "failed to get baseclk\n");
> + return PTR_ERR(priv->mainck);
> + }
> +
> + priv->hclock = devm_clk_get(&pdev->dev, "hclock");
> + if (IS_ERR(priv->hclock)) {
> + dev_err(&pdev->dev, "failed to get hclock\n");
> + return PTR_ERR(priv->hclock);
> + }
> +
> + priv->gck = devm_clk_get(&pdev->dev, "multclk");
> + if (IS_ERR(priv->gck)) {
> + dev_err(&pdev->dev, "failed to get multclk\n");
> + return PTR_ERR(priv->gck);
> + }
> +
> + host = sdhci_pltfm_init(pdev, soc_data, 0);
> + if (IS_ERR(host))
> + return PTR_ERR(host);
> +
> + /*
> + * 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_prepare_enable(priv->hclock);
> + caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
> + caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
> + clk_base = (caps0 & SDHCI_CLOCK_V3_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT;
> + clk_mul = (caps1 & SDHCI_CLOCK_MUL_MASK) >> SDHCI_CLOCK_MUL_SHIFT;
> + gck_rate = clk_base * 1000000 * (clk_mul + 1);
> + ret = clk_set_rate(priv->gck, gck_rate);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to set gck");
> + goto hclock_disable_unprepare;
> + return -EINVAL;
> + }
Could this be shared with other drivers?
Arnd
On Sat, Jul 04, 2015 at 10:06:49PM +0200, Arnd Bergmann wrote:
> On Friday 03 July 2015 16:17:16 Ludovic Desroches wrote:
> > Introduce driver for he Atmel SDMMC available on sama5d2. It is a sdhci
> > compliant controller.
> >
> > Signed-off-by: Ludovic Desroches <[email protected]>
> > ---
> > .../devicetree/bindings/mmc/sdhci-atmel.txt | 21 +++
> > drivers/mmc/host/Kconfig | 8 +
> > drivers/mmc/host/Makefile | 1 +
> > drivers/mmc/host/sdhci-of-at91.c | 188 +++++++++++++++++++++
> > 4 files changed, 218 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> > create mode 100644 drivers/mmc/host/sdhci-of-at91.c
> >
> > diff --git a/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> > new file mode 100644
> > index 0000000..1b662d7
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt
> > @@ -0,0 +1,21 @@
> > +* Atmel SDHCI controller
> > +
> > +This file documents the differences between the core properties in
> > +Documentation/devicetree/bindings/mmc/mmc.txt and the properties used by the
> > +sdhci-of-at91 driver.
> > +
> > +Required properties:
> > +- compatible: Must be "atmel,sama5d2-sdhci".
> > +- clocks: Phandlers to the clocks.
> > +- clock-names: Must be "hclock", "multclk", "baseclk";
> > +
>
> Are you sure that these are all new clocks that are unrelated to the "core",
> "mmc", and "iface" clocks that are used in other drivers?
>
Maybe hclock could be considered as the core and interface clock. The
mmc clock will be baseclk or multclk. I think other devices manage the
multclk internally. Here we have to provide both clocks. Internally, we
only choose which clock to use and add a divider if necessary.
> > +
> > + priv->mainck = devm_clk_get(&pdev->dev, "baseclk");
> > + if (IS_ERR(priv->mainck)) {
> > + dev_err(&pdev->dev, "failed to get baseclk\n");
> > + return PTR_ERR(priv->mainck);
> > + }
> > +
> > + priv->hclock = devm_clk_get(&pdev->dev, "hclock");
> > + if (IS_ERR(priv->hclock)) {
> > + dev_err(&pdev->dev, "failed to get hclock\n");
> > + return PTR_ERR(priv->hclock);
> > + }
> > +
> > + priv->gck = devm_clk_get(&pdev->dev, "multclk");
> > + if (IS_ERR(priv->gck)) {
> > + dev_err(&pdev->dev, "failed to get multclk\n");
> > + return PTR_ERR(priv->gck);
> > + }
> > +
> > + host = sdhci_pltfm_init(pdev, soc_data, 0);
> > + if (IS_ERR(host))
> > + return PTR_ERR(host);
> > +
> > + /*
> > + * 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_prepare_enable(priv->hclock);
> > + caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
> > + caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
> > + clk_base = (caps0 & SDHCI_CLOCK_V3_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT;
> > + clk_mul = (caps1 & SDHCI_CLOCK_MUL_MASK) >> SDHCI_CLOCK_MUL_SHIFT;
> > + gck_rate = clk_base * 1000000 * (clk_mul + 1);
> > + ret = clk_set_rate(priv->gck, gck_rate);
> > + if (ret < 0) {
> > + dev_err(&pdev->dev, "failed to set gck");
> > + goto hclock_disable_unprepare;
> > + return -EINVAL;
> > + }
>
> Could this be shared with other drivers?
No, it's risk-free to do this.
>
> Arnd
On Sat, Jul 04, 2015 at 10:30:28AM +0200, Paul Bolle wrote:
> A nit only: a license mismatch.
Thanks Paul, I'll correct it.
>
> On vr, 2015-07-03 at 16:17 +0200, Ludovic Desroches wrote:
> > --- /dev/null
> > +++ b/drivers/mmc/host/sdhci-of-at91.c
> >
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or (at
> > + * your option) any later version.
>
> This states the license is GPL v2 or later.
>
> > +MODULE_LICENSE("GPL v2");
>
> And, according to include/linux/module.h, this states the license is
> (only) GPL v2. So I think that either the comment at the top of this
> file or the ident used in MODULE_LICENSE() needs to change.
>
> Thanks,
>
>
> Paul Bolle