clk: clk_meson: Add mux and div support for reparent and rate setting
[oweals/u-boot.git] / drivers / clk / clk_meson.c
index 3850128e5c75c2e33b5fa2ea78a5a03f309e99f4..236d7342b798b7dae5e3b07340f3ad4869d66a30 100644 (file)
 #include <dt-bindings/clock/gxbb-clkc.h>
 #include "clk_meson.h"
 
+/* This driver support only basic clock tree operations :
+ * - Can calculate clock frequency on a limited tree
+ * - Can Read muxes and basic dividers (0-based only)
+ * - Can enable/disable gates with limited propagation
+ * - Can reparent without propagation, only on muxes
+ * - Can set rates without reparenting
+ * This driver is adapted to what is actually supported by U-Boot
+ */
+
+/* Only the clocks ids we don't want to expose, such as the internal muxes
+ * and dividers of composite clocks, will remain defined here.
+ */
+#define CLKID_MPEG_SEL           10
+#define CLKID_MPEG_DIV           11
+#define CLKID_SAR_ADC_DIV        99
+#define CLKID_MALI_0_DIV         101
+#define CLKID_MALI_1_DIV         104
+#define CLKID_CTS_AMCLK_SEL      108
+#define CLKID_CTS_AMCLK_DIV      109
+#define CLKID_CTS_MCLK_I958_SEL          111
+#define CLKID_CTS_MCLK_I958_DIV          112
+#define CLKID_32K_CLK_SEL        115
+#define CLKID_32K_CLK_DIV        116
+#define CLKID_SD_EMMC_A_CLK0_SEL  117
+#define CLKID_SD_EMMC_A_CLK0_DIV  118
+#define CLKID_SD_EMMC_B_CLK0_SEL  120
+#define CLKID_SD_EMMC_B_CLK0_DIV  121
+#define CLKID_SD_EMMC_C_CLK0_SEL  123
+#define CLKID_SD_EMMC_C_CLK0_DIV  124
+#define CLKID_VPU_0_DIV                  127
+#define CLKID_VPU_1_DIV                  130
+#define CLKID_VAPB_0_DIV         134
+#define CLKID_VAPB_1_DIV         137
+#define CLKID_HDMI_PLL_PRE_MULT          141
+#define CLKID_MPLL0_DIV                  142
+#define CLKID_MPLL1_DIV                  143
+#define CLKID_MPLL2_DIV                  144
+#define CLKID_MPLL_PREDIV        145
+#define CLKID_FCLK_DIV2_DIV      146
+#define CLKID_FCLK_DIV3_DIV      147
+#define CLKID_FCLK_DIV4_DIV      148
+#define CLKID_FCLK_DIV5_DIV      149
+#define CLKID_FCLK_DIV7_DIV      150
+#define CLKID_VDEC_1_SEL         151
+#define CLKID_VDEC_1_DIV         152
+#define CLKID_VDEC_HEVC_SEL      154
+#define CLKID_VDEC_HEVC_DIV      155
+
 #define XTAL_RATE 24000000
 
 struct meson_clk {
        void __iomem *addr;
 };
 
+static ulong meson_div_get_rate(struct clk *clk, unsigned long id);
+static ulong meson_div_set_rate(struct clk *clk, unsigned long id, ulong rate,
+                               ulong current_rate);
+static ulong meson_mux_set_parent(struct clk *clk, unsigned long id,
+                                 unsigned long parent_id);
+static ulong meson_mux_get_rate(struct clk *clk, unsigned long id);
+static ulong meson_clk_set_rate_by_id(struct clk *clk, unsigned long id,
+                                     ulong rate, ulong current_rate);
+static ulong meson_mux_get_parent(struct clk *clk, unsigned long id);
 static ulong meson_clk_get_rate_by_id(struct clk *clk, unsigned long id);
 
 struct meson_gate gates[] = {
@@ -126,34 +183,387 @@ struct meson_gate gates[] = {
        MESON_GATE(CLKID_SD_EMMC_A_CLK0, HHI_SD_EMMC_CLK_CNTL, 7),
        MESON_GATE(CLKID_SD_EMMC_B_CLK0, HHI_SD_EMMC_CLK_CNTL, 23),
        MESON_GATE(CLKID_SD_EMMC_C_CLK0, HHI_NAND_CLK_CNTL, 7),
+       MESON_GATE(CLKID_VPU_0, HHI_VPU_CLK_CNTL, 8),
+       MESON_GATE(CLKID_VPU_1, HHI_VPU_CLK_CNTL, 24),
+       MESON_GATE(CLKID_VAPB_0, HHI_VAPBCLK_CNTL, 8),
+       MESON_GATE(CLKID_VAPB_1, HHI_VAPBCLK_CNTL, 24),
+       MESON_GATE(CLKID_VAPB, HHI_VAPBCLK_CNTL, 30),
 };
 
-static int meson_set_gate(struct clk *clk, bool on)
+static int meson_set_gate_by_id(struct clk *clk, unsigned long id, bool on)
 {
        struct meson_clk *priv = dev_get_priv(clk->dev);
        struct meson_gate *gate;
 
-       if (clk->id >= ARRAY_SIZE(gates))
+       debug("%s: %sabling %ld\n", __func__, on ? "en" : "dis", id);
+
+       /* Propagate through muxes */
+       switch (id) {
+       case CLKID_VPU:
+               return meson_set_gate_by_id(clk,
+                               meson_mux_get_parent(clk, CLKID_VPU), on);
+       case CLKID_VAPB_SEL:
+               return meson_set_gate_by_id(clk,
+                               meson_mux_get_parent(clk, CLKID_VAPB_SEL), on);
+       }
+
+       if (id >= ARRAY_SIZE(gates))
                return -ENOENT;
 
-       gate = &gates[clk->id];
+       gate = &gates[id];
 
        if (gate->reg == 0)
                return 0;
 
+       debug("%s: really %sabling %ld\n", __func__, on ? "en" : "dis", id);
+
        clrsetbits_le32(priv->addr + gate->reg,
                        BIT(gate->bit), on ? BIT(gate->bit) : 0);
+
+       /* Propagate to next gate(s) */
+       switch (id) {
+       case CLKID_VAPB:
+               return meson_set_gate_by_id(clk, CLKID_VAPB_SEL, on);
+       }
+
        return 0;
 }
 
 static int meson_clk_enable(struct clk *clk)
 {
-       return meson_set_gate(clk, true);
+       return meson_set_gate_by_id(clk, clk->id, true);
 }
 
 static int meson_clk_disable(struct clk *clk)
 {
-       return meson_set_gate(clk, false);
+       return meson_set_gate_by_id(clk, clk->id, false);
+}
+
+static struct parm meson_vpu_0_div_parm = {
+       HHI_VPU_CLK_CNTL, 0, 7,
+};
+
+int meson_vpu_0_div_parent = CLKID_VPU_0_SEL;
+
+static struct parm meson_vpu_1_div_parm = {
+       HHI_VPU_CLK_CNTL, 16, 7,
+};
+
+int meson_vpu_1_div_parent = CLKID_VPU_1_SEL;
+
+static struct parm meson_vapb_0_div_parm = {
+       HHI_VAPBCLK_CNTL, 0, 7,
+};
+
+int meson_vapb_0_div_parent = CLKID_VAPB_0_SEL;
+
+static struct parm meson_vapb_1_div_parm = {
+       HHI_VAPBCLK_CNTL, 16, 7,
+};
+
+int meson_vapb_1_div_parent = CLKID_VAPB_1_SEL;
+
+static ulong meson_div_get_rate(struct clk *clk, unsigned long id)
+{
+       struct meson_clk *priv = dev_get_priv(clk->dev);
+       unsigned int rate, parent_rate;
+       struct parm *parm;
+       int parent;
+       u32 reg;
+
+       switch (id) {
+       case CLKID_VPU_0_DIV:
+               parm = &meson_vpu_0_div_parm;
+               parent = meson_vpu_0_div_parent;
+               break;
+       case CLKID_VPU_1_DIV:
+               parm = &meson_vpu_1_div_parm;
+               parent = meson_vpu_1_div_parent;
+               break;
+       case CLKID_VAPB_0_DIV:
+               parm = &meson_vapb_0_div_parm;
+               parent = meson_vapb_0_div_parent;
+               break;
+       case CLKID_VAPB_1_DIV:
+               parm = &meson_vapb_1_div_parm;
+               parent = meson_vapb_1_div_parent;
+               break;
+       default:
+               return -ENOENT;
+       }
+
+       reg = readl(priv->addr + parm->reg_off);
+       reg = PARM_GET(parm->width, parm->shift, reg);
+
+       debug("%s: div of %ld is %d\n", __func__, id, reg + 1);
+
+       parent_rate = meson_clk_get_rate_by_id(clk, parent);
+       if (IS_ERR_VALUE(parent_rate))
+               return parent_rate;
+
+       debug("%s: parent rate of %ld is %d\n", __func__, id, parent_rate);
+
+       rate = parent_rate / (reg + 1);
+
+       debug("%s: rate of %ld is %d\n", __func__, id, rate);
+
+       return rate;
+}
+
+static ulong meson_div_set_rate(struct clk *clk, unsigned long id, ulong rate,
+                               ulong current_rate)
+{
+       struct meson_clk *priv = dev_get_priv(clk->dev);
+       unsigned int new_div = -EINVAL;
+       unsigned long parent_rate;
+       struct parm *parm;
+       int parent;
+       u32 reg;
+       int ret;
+
+       if (current_rate == rate)
+               return 0;
+
+       debug("%s: setting rate of %ld from %ld to %ld\n",
+             __func__, id, current_rate, rate);
+
+       switch (id) {
+       case CLKID_VPU_0_DIV:
+               parm = &meson_vpu_0_div_parm;
+               parent = meson_vpu_0_div_parent;
+               break;
+       case CLKID_VPU_1_DIV:
+               parm = &meson_vpu_1_div_parm;
+               parent = meson_vpu_1_div_parent;
+               break;
+       case CLKID_VAPB_0_DIV:
+               parm = &meson_vapb_0_div_parm;
+               parent = meson_vapb_0_div_parent;
+               break;
+       case CLKID_VAPB_1_DIV:
+               parm = &meson_vapb_1_div_parm;
+               parent = meson_vapb_1_div_parent;
+               break;
+       default:
+               return -ENOENT;
+       }
+
+       parent_rate = meson_clk_get_rate_by_id(clk, parent);
+       if (IS_ERR_VALUE(parent_rate))
+               return parent_rate;
+
+       debug("%s: parent rate of %ld is %ld\n", __func__, id, parent_rate);
+
+       /* If can't divide, set parent instead */
+       if (!parent_rate || rate > parent_rate)
+               return meson_clk_set_rate_by_id(clk, parent, rate,
+                                               current_rate);
+
+       new_div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+       debug("%s: new div of %ld is %d\n", __func__, id, new_div);
+
+       /* If overflow, try to set parent rate and retry */
+       if (!new_div || new_div > (1 << parm->width)) {
+               ret = meson_clk_set_rate_by_id(clk, parent, rate, current_rate);
+               if (IS_ERR_VALUE(ret))
+                       return ret;
+
+               parent_rate = meson_clk_get_rate_by_id(clk, parent);
+               if (IS_ERR_VALUE(parent_rate))
+                       return parent_rate;
+
+               new_div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+               debug("%s: new new div of %ld is %d\n", __func__, id, new_div);
+
+               if (!new_div || new_div > (1 << parm->width))
+                       return -EINVAL;
+       }
+
+       debug("%s: setting div of %ld to %d\n", __func__, id, new_div);
+
+       reg = readl(priv->addr + parm->reg_off);
+       writel(PARM_SET(parm->width, parm->shift, reg, new_div - 1),
+              priv->addr + parm->reg_off);
+
+       debug("%s: new rate of %ld is %ld\n",
+             __func__, id, meson_div_get_rate(clk, id));
+
+       return 0;
+}
+
+static struct parm meson_vpu_mux_parm = {
+       HHI_VPU_CLK_CNTL, 31, 1,
+};
+
+int meson_vpu_mux_parents[] = {
+       CLKID_VPU_0,
+       CLKID_VPU_1,
+};
+
+static struct parm meson_vpu_0_mux_parm = {
+       HHI_VPU_CLK_CNTL, 9, 2,
+};
+
+static struct parm meson_vpu_1_mux_parm = {
+       HHI_VPU_CLK_CNTL, 25, 2,
+};
+
+static int meson_vpu_0_1_mux_parents[] = {
+       CLKID_FCLK_DIV4,
+       CLKID_FCLK_DIV3,
+       CLKID_FCLK_DIV5,
+       CLKID_FCLK_DIV7,
+};
+
+static struct parm meson_vapb_sel_mux_parm = {
+       HHI_VAPBCLK_CNTL, 31, 1,
+};
+
+int meson_vapb_sel_mux_parents[] = {
+       CLKID_VAPB_0,
+       CLKID_VAPB_1,
+};
+
+static struct parm meson_vapb_0_mux_parm = {
+       HHI_VAPBCLK_CNTL, 9, 2,
+};
+
+static struct parm meson_vapb_1_mux_parm = {
+       HHI_VAPBCLK_CNTL, 25, 2,
+};
+
+static int meson_vapb_0_1_mux_parents[] = {
+       CLKID_FCLK_DIV4,
+       CLKID_FCLK_DIV3,
+       CLKID_FCLK_DIV5,
+       CLKID_FCLK_DIV7,
+};
+
+static ulong meson_mux_get_parent(struct clk *clk, unsigned long id)
+{
+       struct meson_clk *priv = dev_get_priv(clk->dev);
+       struct parm *parm;
+       int *parents;
+       u32 reg;
+
+       switch (id) {
+       case CLKID_VPU:
+               parm = &meson_vpu_mux_parm;
+               parents = meson_vpu_mux_parents;
+               break;
+       case CLKID_VPU_0_SEL:
+               parm = &meson_vpu_0_mux_parm;
+               parents = meson_vpu_0_1_mux_parents;
+               break;
+       case CLKID_VPU_1_SEL:
+               parm = &meson_vpu_1_mux_parm;
+               parents = meson_vpu_0_1_mux_parents;
+               break;
+       case CLKID_VAPB_SEL:
+               parm = &meson_vapb_sel_mux_parm;
+               parents = meson_vapb_sel_mux_parents;
+               break;
+       case CLKID_VAPB_0_SEL:
+               parm = &meson_vapb_0_mux_parm;
+               parents = meson_vapb_0_1_mux_parents;
+               break;
+       case CLKID_VAPB_1_SEL:
+               parm = &meson_vapb_1_mux_parm;
+               parents = meson_vapb_0_1_mux_parents;
+               break;
+       default:
+               return -ENOENT;
+       }
+
+       reg = readl(priv->addr + parm->reg_off);
+       reg = PARM_GET(parm->width, parm->shift, reg);
+
+       debug("%s: parent of %ld is %d (%d)\n",
+             __func__, id, parents[reg], reg);
+
+       return parents[reg];
+}
+
+static ulong meson_mux_set_parent(struct clk *clk, unsigned long id,
+                                 unsigned long parent_id)
+{
+       unsigned long cur_parent = meson_mux_get_parent(clk, id);
+       struct meson_clk *priv = dev_get_priv(clk->dev);
+       unsigned int new_index = -EINVAL;
+       struct parm *parm;
+       int *parents;
+       u32 reg;
+       int i;
+
+       if (IS_ERR_VALUE(cur_parent))
+               return cur_parent;
+
+       debug("%s: setting parent of %ld from %ld to %ld\n",
+             __func__, id, cur_parent, parent_id);
+
+       if (cur_parent == parent_id)
+               return 0;
+
+       switch (id) {
+       case CLKID_VPU:
+               parm = &meson_vpu_mux_parm;
+               parents = meson_vpu_mux_parents;
+               break;
+       case CLKID_VPU_0_SEL:
+               parm = &meson_vpu_0_mux_parm;
+               parents = meson_vpu_0_1_mux_parents;
+               break;
+       case CLKID_VPU_1_SEL:
+               parm = &meson_vpu_1_mux_parm;
+               parents = meson_vpu_0_1_mux_parents;
+               break;
+       case CLKID_VAPB_SEL:
+               parm = &meson_vapb_sel_mux_parm;
+               parents = meson_vapb_sel_mux_parents;
+               break;
+       case CLKID_VAPB_0_SEL:
+               parm = &meson_vapb_0_mux_parm;
+               parents = meson_vapb_0_1_mux_parents;
+               break;
+       case CLKID_VAPB_1_SEL:
+               parm = &meson_vapb_1_mux_parm;
+               parents = meson_vapb_0_1_mux_parents;
+               break;
+       default:
+               /* Not a mux */
+               return -ENOENT;
+       }
+
+       for (i = 0 ; i < (1 << parm->width) ; ++i) {
+               if (parents[i] == parent_id)
+                       new_index = i;
+       }
+
+       if (IS_ERR_VALUE(new_index))
+               return new_index;
+
+       debug("%s: new index of %ld is %d\n", __func__, id, new_index);
+
+       reg = readl(priv->addr + parm->reg_off);
+       writel(PARM_SET(parm->width, parm->shift, reg, new_index),
+              priv->addr + parm->reg_off);
+
+       debug("%s: new parent of %ld is %ld\n",
+             __func__, id, meson_mux_get_parent(clk, id));
+
+       return 0;
+}
+
+static ulong meson_mux_get_rate(struct clk *clk, unsigned long id)
+{
+       int parent = meson_mux_get_parent(clk, id);
+
+       if (IS_ERR_VALUE(parent))
+               return parent;
+
+       return meson_clk_get_rate_by_id(clk, parent);
 }
 
 static unsigned long meson_clk81_get_rate(struct clk *clk)
@@ -342,6 +752,35 @@ static ulong meson_clk_get_rate_by_id(struct clk *clk, unsigned long id)
        case CLKID_CLK81:
                rate = meson_clk81_get_rate(clk);
                break;
+       case CLKID_VPU_0:
+               rate = meson_div_get_rate(clk, CLKID_VPU_0_DIV);
+               break;
+       case CLKID_VPU_1:
+               rate = meson_div_get_rate(clk, CLKID_VPU_1_DIV);
+               break;
+       case CLKID_VAPB:
+               rate = meson_mux_get_rate(clk, CLKID_VAPB_SEL);
+               break;
+       case CLKID_VAPB_0:
+               rate = meson_div_get_rate(clk, CLKID_VAPB_0_DIV);
+               break;
+       case CLKID_VAPB_1:
+               rate = meson_div_get_rate(clk, CLKID_VAPB_1_DIV);
+               break;
+       case CLKID_VPU_0_DIV:
+       case CLKID_VPU_1_DIV:
+       case CLKID_VAPB_0_DIV:
+       case CLKID_VAPB_1_DIV:
+               rate = meson_div_get_rate(clk, id);
+               break;
+       case CLKID_VPU:
+       case CLKID_VPU_0_SEL:
+       case CLKID_VPU_1_SEL:
+       case CLKID_VAPB_SEL:
+       case CLKID_VAPB_0_SEL:
+       case CLKID_VAPB_1_SEL:
+               rate = meson_mux_get_rate(clk, id);
+               break;
        default:
                if (gates[id].reg != 0) {
                        /* a clock gate */
@@ -360,6 +799,88 @@ static ulong meson_clk_get_rate(struct clk *clk)
        return meson_clk_get_rate_by_id(clk, clk->id);
 }
 
+static int meson_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+       return meson_mux_set_parent(clk, clk->id, parent->id);
+}
+
+static ulong meson_clk_set_rate_by_id(struct clk *clk, unsigned long id,
+                                     ulong rate, ulong current_rate)
+{
+       if (current_rate == rate)
+               return 0;
+
+       switch (id) {
+       /* Fixed clocks */
+       case CLKID_FIXED_PLL:
+       case CLKID_SYS_PLL:
+       case CLKID_FCLK_DIV2:
+       case CLKID_FCLK_DIV3:
+       case CLKID_FCLK_DIV4:
+       case CLKID_FCLK_DIV5:
+       case CLKID_FCLK_DIV7:
+       case CLKID_MPLL0:
+       case CLKID_MPLL1:
+       case CLKID_MPLL2:
+       case CLKID_CLK81:
+               if (current_rate != rate)
+                       return -EINVAL;
+
+               return 0;
+       case CLKID_VPU:
+               return meson_clk_set_rate_by_id(clk,
+                               meson_mux_get_parent(clk, CLKID_VPU), rate,
+                                                    current_rate);
+       case CLKID_VAPB:
+       case CLKID_VAPB_SEL:
+               return meson_clk_set_rate_by_id(clk,
+                               meson_mux_get_parent(clk, CLKID_VAPB_SEL),
+                               rate, current_rate);
+       case CLKID_VPU_0:
+               return meson_div_set_rate(clk, CLKID_VPU_0_DIV, rate,
+                                         current_rate);
+       case CLKID_VPU_1:
+               return meson_div_set_rate(clk, CLKID_VPU_1_DIV, rate,
+                                         current_rate);
+       case CLKID_VAPB_0:
+               return meson_div_set_rate(clk, CLKID_VAPB_0_DIV, rate,
+                                         current_rate);
+       case CLKID_VAPB_1:
+               return meson_div_set_rate(clk, CLKID_VAPB_1_DIV, rate,
+                                         current_rate);
+       case CLKID_VPU_0_DIV:
+       case CLKID_VPU_1_DIV:
+       case CLKID_VAPB_0_DIV:
+       case CLKID_VAPB_1_DIV:
+               return meson_div_set_rate(clk, id, rate, current_rate);
+       default:
+               return -ENOENT;
+       }
+
+       return -EINVAL;
+}
+
+static ulong meson_clk_set_rate(struct clk *clk, ulong rate)
+{
+       ulong current_rate = meson_clk_get_rate_by_id(clk, clk->id);
+       int ret;
+
+       if (IS_ERR_VALUE(current_rate))
+               return current_rate;
+
+       debug("%s: setting rate of %ld from %ld to %ld\n",
+             __func__, clk->id, current_rate, rate);
+
+       ret = meson_clk_set_rate_by_id(clk, clk->id, rate, current_rate);
+       if (IS_ERR_VALUE(ret))
+               return ret;
+
+       printf("clock %lu has new rate %lu\n", clk->id,
+              meson_clk_get_rate_by_id(clk, clk->id));
+
+       return 0;
+}
+
 static int meson_clk_probe(struct udevice *dev)
 {
        struct meson_clk *priv = dev_get_priv(dev);
@@ -375,6 +896,8 @@ static struct clk_ops meson_clk_ops = {
        .disable        = meson_clk_disable,
        .enable         = meson_clk_enable,
        .get_rate       = meson_clk_get_rate,
+       .set_parent     = meson_clk_set_parent,
+       .set_rate       = meson_clk_set_rate,
 };
 
 static const struct udevice_id meson_clk_ids[] = {