summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sdhci-of-arasan.c
diff options
context:
space:
mode:
authorDouglas Anderson <dianders@chromium.org>2016-06-20 20:56:47 +0300
committerUlf Hansson <ulf.hansson@linaro.org>2016-07-25 11:34:18 +0300
commit3ea4666e8d429223fbb39c1dccee7599ef7657d5 (patch)
tree8bb1cad25b7341c5e065a07b6590216fd8e6da69 /drivers/mmc/host/sdhci-of-arasan.c
parent65820199272d0fe764aa31bb2e30dc670e98135a (diff)
downloadlinux-3ea4666e8d429223fbb39c1dccee7599ef7657d5.tar.xz
mmc: sdhci-of-arasan: Properly set corecfg_baseclkfreq on rk3399
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>
Diffstat (limited to 'drivers/mmc/host/sdhci-of-arasan.c')
-rw-r--r--drivers/mmc/host/sdhci-of-arasan.c189
1 files changed, 178 insertions, 11 deletions
diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c
index 3ff1711077c2..1286fe8905dc 100644
--- a/drivers/mmc/host/sdhci-of-arasan.c
+++ b/drivers/mmc/host/sdhci-of-arasan.c
@@ -19,9 +19,11 @@
* 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
@@ -32,18 +34,115 @@
#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",