Merge tag 'mac80211-for-davem-2018-05-09' of git://git.kernel.org/pub/scm/linux/kerne...
[sfrench/cifs-2.6.git] / drivers / clk / rockchip / clk-mmc-phase.c
index 077fcdc7908bb9f3791fe20bc60a0266327ca050..026a26bb702d9b9f6ab53a6ffaa8864149dfc01b 100644 (file)
@@ -25,6 +25,8 @@ struct rockchip_mmc_clock {
        void __iomem    *reg;
        int             id;
        int             shift;
+       int             cached_phase;
+       struct notifier_block clk_rate_change_nb;
 };
 
 #define to_mmc_clock(_hw) container_of(_hw, struct rockchip_mmc_clock, hw)
@@ -58,6 +60,12 @@ static int rockchip_mmc_get_phase(struct clk_hw *hw)
        u16 degrees;
        u32 delay_num = 0;
 
+       /* See the comment for rockchip_mmc_set_phase below */
+       if (!rate) {
+               pr_err("%s: invalid clk rate\n", __func__);
+               return -EINVAL;
+       }
+
        raw_value = readl(mmc_clock->reg) >> (mmc_clock->shift);
 
        degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90;
@@ -84,6 +92,23 @@ static int rockchip_mmc_set_phase(struct clk_hw *hw, int degrees)
        u32 raw_value;
        u32 delay;
 
+       /*
+        * The below calculation is based on the output clock from
+        * MMC host to the card, which expects the phase clock inherits
+        * the clock rate from its parent, namely the output clock
+        * provider of MMC host. However, things may go wrong if
+        * (1) It is orphan.
+        * (2) It is assigned to the wrong parent.
+        *
+        * This check help debug the case (1), which seems to be the
+        * most likely problem we often face and which makes it difficult
+        * for people to debug unstable mmc tuning results.
+        */
+       if (!rate) {
+               pr_err("%s: invalid clk rate\n", __func__);
+               return -EINVAL;
+       }
+
        nineties = degrees / 90;
        remainder = (degrees % 90);
 
@@ -139,6 +164,41 @@ static const struct clk_ops rockchip_mmc_clk_ops = {
        .set_phase      = rockchip_mmc_set_phase,
 };
 
+#define to_rockchip_mmc_clock(x) \
+       container_of(x, struct rockchip_mmc_clock, clk_rate_change_nb)
+static int rockchip_mmc_clk_rate_notify(struct notifier_block *nb,
+                                       unsigned long event, void *data)
+{
+       struct rockchip_mmc_clock *mmc_clock = to_rockchip_mmc_clock(nb);
+       struct clk_notifier_data *ndata = data;
+
+       /*
+        * rockchip_mmc_clk is mostly used by mmc controllers to sample
+        * the intput data, which expects the fixed phase after the tuning
+        * process. However if the clock rate is changed, the phase is stale
+        * and may break the data sampling. So here we try to restore the phase
+        * for that case, except that
+        * (1) cached_phase is invaild since we inevitably cached it when the
+        * clock provider be reparented from orphan to its real parent in the
+        * first place. Otherwise we may mess up the initialization of MMC cards
+        * since we only set the default sample phase and drive phase later on.
+        * (2) the new coming rate is higher than the older one since mmc driver
+        * set the max-frequency to match the boards' ability but we can't go
+        * over the heads of that, otherwise the tests smoke out the issue.
+        */
+       if (ndata->old_rate <= ndata->new_rate)
+               return NOTIFY_DONE;
+
+       if (event == PRE_RATE_CHANGE)
+               mmc_clock->cached_phase =
+                       rockchip_mmc_get_phase(&mmc_clock->hw);
+       else if (mmc_clock->cached_phase != -EINVAL &&
+                event == POST_RATE_CHANGE)
+               rockchip_mmc_set_phase(&mmc_clock->hw, mmc_clock->cached_phase);
+
+       return NOTIFY_DONE;
+}
+
 struct clk *rockchip_clk_register_mmc(const char *name,
                                const char *const *parent_names, u8 num_parents,
                                void __iomem *reg, int shift)
@@ -146,6 +206,7 @@ struct clk *rockchip_clk_register_mmc(const char *name,
        struct clk_init_data init;
        struct rockchip_mmc_clock *mmc_clock;
        struct clk *clk;
+       int ret;
 
        mmc_clock = kmalloc(sizeof(*mmc_clock), GFP_KERNEL);
        if (!mmc_clock)
@@ -162,8 +223,21 @@ struct clk *rockchip_clk_register_mmc(const char *name,
        mmc_clock->shift = shift;
 
        clk = clk_register(NULL, &mmc_clock->hw);
-       if (IS_ERR(clk))
-               kfree(mmc_clock);
+       if (IS_ERR(clk)) {
+               ret = PTR_ERR(clk);
+               goto err_register;
+       }
+
+       mmc_clock->clk_rate_change_nb.notifier_call =
+                               &rockchip_mmc_clk_rate_notify;
+       ret = clk_notifier_register(clk, &mmc_clock->clk_rate_change_nb);
+       if (ret)
+               goto err_notifier;
 
        return clk;
+err_notifier:
+       clk_unregister(clk);
+err_register:
+       kfree(mmc_clock);
+       return ERR_PTR(ret);
 }