mmc: sdhci-of-arasan: Properly set corecfg_baseclkfreq on rk3399
authorDouglas Anderson <dianders@chromium.org>
Mon, 20 Jun 2016 17:56:47 +0000 (10:56 -0700)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 25 Jul 2016 08:34:18 +0000 (10:34 +0200)
In the the earlier change in this series ("Documentation: mmc:
sdhci-of-arasan: Add soc-ctl-syscon for corecfg regs") we can see the
mechansim for specifying a syscon to properly set corecfg registers in
sdhci-of-arasan.  Now let's use this mechanism to properly set
corecfg_baseclkfreq on rk3399.

>From [1] the corecfg_baseclkfreq is supposed to be set to:
  Base Clock Frequency for SD Clock.
  This is the frequency of the xin_clk.

This is a relatively easy thing to do.  Note that we assume that xin_clk
is not dynamic and we can check the clock at probe time.  If any real
devices have a dynamic xin_clk future patches could register for
notifiers for the clock.

At the moment, setting corecfg_baseclkfreq is only supported for rk3399
since we need a specific map for each implementation.  The code is
written in a generic way that should make this easy to extend to other
SoCs.  Note that a specific compatible string for rk3399 is already in
use and so we add that to the table to match rk3399.

[1]: https://arasan.com/wp-content/media/eMMC-5-1-Total-Solution_Rev-1-3.pdf

Signed-off-by: Douglas Anderson <dianders@chromium.org>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Shawn Lin <shawn.lin@rock-chips.com>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/host/sdhci-of-arasan.c

index 3ff1711077c26cbbfdabbf581a0dd3b5ff23d560..1286fe8905dc7afc6e63a514018e844646601041 100644 (file)
  * your option) any later version.
  */
 
+#include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/phy/phy.h>
+#include <linux/regmap.h>
 #include "sdhci-pltfm.h"
 
 #define SDHCI_ARASAN_CLK_CTRL_OFFSET   0x2c
 #define CLK_CTRL_TIMEOUT_MASK          (0xf << CLK_CTRL_TIMEOUT_SHIFT)
 #define CLK_CTRL_TIMEOUT_MIN_EXP       13
 
+/*
+ * On some SoCs the syscon area has a feature where the upper 16-bits of
+ * each 32-bit register act as a write mask for the lower 16-bits.  This allows
+ * atomic updates of the register without locking.  This macro is used on SoCs
+ * that have that feature.
+ */
+#define HIWORD_UPDATE(val, mask, shift) \
+               ((val) << (shift) | (mask) << ((shift) + 16))
+
+/**
+ * struct sdhci_arasan_soc_ctl_field - Field used in sdhci_arasan_soc_ctl_map
+ *
+ * @reg:       Offset within the syscon of the register containing this field
+ * @width:     Number of bits for this field
+ * @shift:     Bit offset within @reg of this field (or -1 if not avail)
+ */
+struct sdhci_arasan_soc_ctl_field {
+       u32 reg;
+       u16 width;
+       s16 shift;
+};
+
+/**
+ * struct sdhci_arasan_soc_ctl_map - Map in syscon to corecfg registers
+ *
+ * It's up to the licensee of the Arsan IP block to make these available
+ * somewhere if needed.  Presumably these will be scattered somewhere that's
+ * accessible via the syscon API.
+ *
+ * @baseclkfreq:       Where to find corecfg_baseclkfreq
+ * @hiword_update:     If true, use HIWORD_UPDATE to access the syscon
+ */
+struct sdhci_arasan_soc_ctl_map {
+       struct sdhci_arasan_soc_ctl_field       baseclkfreq;
+       bool                                    hiword_update;
+};
+
 /**
  * struct sdhci_arasan_data
- * @clk_ahb:   Pointer to the AHB clock
- * @phy:       Pointer to the generic phy
- * @phy_on:    True if the PHY is turned on.
+ * @clk_ahb:           Pointer to the AHB clock
+ * @phy:               Pointer to the generic phy
+ * @phy_on:            True if the PHY is turned on.
+ * @soc_ctl_base:      Pointer to regmap for syscon for soc_ctl registers.
+ * @soc_ctl_map:       Map to get offsets into soc_ctl registers.
  */
 struct sdhci_arasan_data {
        struct clk      *clk_ahb;
        struct phy      *phy;
        bool            phy_on;
+
+       struct regmap   *soc_ctl_base;
+       const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
+};
+
+static const struct sdhci_arasan_soc_ctl_map rk3399_soc_ctl_map = {
+       .baseclkfreq = { .reg = 0xf000, .width = 8, .shift = 8 },
+       .hiword_update = true,
 };
 
+/**
+ * sdhci_arasan_syscon_write - Write to a field in soc_ctl registers
+ *
+ * This function allows writing to fields in sdhci_arasan_soc_ctl_map.
+ * Note that if a field is specified as not available (shift < 0) then
+ * this function will silently return an error code.  It will be noisy
+ * and print errors for any other (unexpected) errors.
+ *
+ * @host:      The sdhci_host
+ * @fld:       The field to write to
+ * @val:       The value to write
+ */
+static int sdhci_arasan_syscon_write(struct sdhci_host *host,
+                                  const struct sdhci_arasan_soc_ctl_field *fld,
+                                  u32 val)
+{
+       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+       struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+       struct regmap *soc_ctl_base = sdhci_arasan->soc_ctl_base;
+       u32 reg = fld->reg;
+       u16 width = fld->width;
+       s16 shift = fld->shift;
+       int ret;
+
+       /*
+        * Silently return errors for shift < 0 so caller doesn't have
+        * to check for fields which are optional.  For fields that
+        * are required then caller needs to do something special
+        * anyway.
+        */
+       if (shift < 0)
+               return -EINVAL;
+
+       if (sdhci_arasan->soc_ctl_map->hiword_update)
+               ret = regmap_write(soc_ctl_base, reg,
+                                  HIWORD_UPDATE(val, GENMASK(width, 0),
+                                                shift));
+       else
+               ret = regmap_update_bits(soc_ctl_base, reg,
+                                        GENMASK(shift + width, shift),
+                                        val << shift);
+
+       /* Yell about (unexpected) regmap errors */
+       if (ret)
+               pr_warn("%s: Regmap write fail: %d\n",
+                        mmc_hostname(host->mmc), ret);
+
+       return ret;
+}
+
 static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host)
 {
        u32 div;
@@ -191,9 +290,66 @@ static int sdhci_arasan_resume(struct device *dev)
 static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend,
                         sdhci_arasan_resume);
 
+static const struct of_device_id sdhci_arasan_of_match[] = {
+       /* SoC-specific compatible strings w/ soc_ctl_map */
+       {
+               .compatible = "rockchip,rk3399-sdhci-5.1",
+               .data = &rk3399_soc_ctl_map,
+       },
+
+       /* Generic compatible below here */
+       { .compatible = "arasan,sdhci-8.9a" },
+       { .compatible = "arasan,sdhci-5.1" },
+       { .compatible = "arasan,sdhci-4.9a" },
+
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
+
+/**
+ * sdhci_arasan_update_baseclkfreq - Set corecfg_baseclkfreq
+ *
+ * The corecfg_baseclkfreq is supposed to contain the MHz of clk_xin.  This
+ * function can be used to make that happen.
+ *
+ * NOTES:
+ * - Many existing devices don't seem to do this and work fine.  To keep
+ *   compatibility for old hardware where the device tree doesn't provide a
+ *   register map, this function is a noop if a soc_ctl_map hasn't been provided
+ *   for this platform.
+ * - It's assumed that clk_xin is not dynamic and that we use the SDHCI divider
+ *   to achieve lower clock rates.  That means that this function is called once
+ *   at probe time and never called again.
+ *
+ * @host:              The sdhci_host
+ */
+static void sdhci_arasan_update_baseclkfreq(struct sdhci_host *host)
+{
+       struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+       struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+       const struct sdhci_arasan_soc_ctl_map *soc_ctl_map =
+               sdhci_arasan->soc_ctl_map;
+       u32 mhz = DIV_ROUND_CLOSEST(clk_get_rate(pltfm_host->clk), 1000000);
+
+       /* Having a map is optional */
+       if (!soc_ctl_map)
+               return;
+
+       /* If we have a map, we expect to have a syscon */
+       if (!sdhci_arasan->soc_ctl_base) {
+               pr_warn("%s: Have regmap, but no soc-ctl-syscon\n",
+                       mmc_hostname(host->mmc));
+               return;
+       }
+
+       sdhci_arasan_syscon_write(host, &soc_ctl_map->baseclkfreq, mhz);
+}
+
 static int sdhci_arasan_probe(struct platform_device *pdev)
 {
        int ret;
+       const struct of_device_id *match;
+       struct device_node *node;
        struct clk *clk_xin;
        struct sdhci_host *host;
        struct sdhci_pltfm_host *pltfm_host;
@@ -207,6 +363,23 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
        pltfm_host = sdhci_priv(host);
        sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
 
+       match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node);
+       sdhci_arasan->soc_ctl_map = match->data;
+
+       node = of_parse_phandle(pdev->dev.of_node, "arasan,soc-ctl-syscon", 0);
+       if (node) {
+               sdhci_arasan->soc_ctl_base = syscon_node_to_regmap(node);
+               of_node_put(node);
+
+               if (IS_ERR(sdhci_arasan->soc_ctl_base)) {
+                       ret = PTR_ERR(sdhci_arasan->soc_ctl_base);
+                       if (ret != -EPROBE_DEFER)
+                               dev_err(&pdev->dev, "Can't get syscon: %d\n",
+                                       ret);
+                       goto err_pltfm_free;
+               }
+       }
+
        sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb");
        if (IS_ERR(sdhci_arasan->clk_ahb)) {
                dev_err(&pdev->dev, "clk_ahb clock not found.\n");
@@ -236,6 +409,8 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
        sdhci_get_of_property(pdev);
        pltfm_host->clk = clk_xin;
 
+       sdhci_arasan_update_baseclkfreq(host);
+
        ret = mmc_of_parse(host->mmc);
        if (ret) {
                dev_err(&pdev->dev, "parsing dt failed (%u)\n", ret);
@@ -301,14 +476,6 @@ static int sdhci_arasan_remove(struct platform_device *pdev)
        return ret;
 }
 
-static const struct of_device_id sdhci_arasan_of_match[] = {
-       { .compatible = "arasan,sdhci-8.9a" },
-       { .compatible = "arasan,sdhci-5.1" },
-       { .compatible = "arasan,sdhci-4.9a" },
-       { }
-};
-MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
-
 static struct platform_driver sdhci_arasan_driver = {
        .driver = {
                .name = "sdhci-arasan",