ARC: HSDK: CGU: add support for timer clock
[oweals/u-boot.git] / drivers / clk / clk-hsdk-cgu.c
index c80f90ec2f01e04573f724240be18ca8674268a0..3035c5fb38ea320971494a5da56badba4726d8de 100644 (file)
  *            |-->| TUNNEL PLL |
  *            |   --------------
  *            |        |
- *            |        |-->|CGU_TUN_IDIV|----------->
- *            |
- *            |   ------------
- *            |-->| HDMI PLL |
- *            |   ------------
- *            |        |
- *            |        |-->|CGU_HDMI_IDIV_APB|------>
+ *            |        |-->|CGU_TUN_IDIV_TUN|----------->
+ *            |        |-->|CGU_TUN_IDIV_ROM|----------->
+ *            |        |-->|CGU_TUN_IDIV_PWM|----------->
  *            |
  *            |   -----------
  *            |-->| DDR PLL |
  *                -----------
  *                     |
  *                     |---------------------------->
+ *
+ *   ------------------
+ *   | 27.00 MHz xtal |
+ *   ------------------
+ *            |
+ *            |   ------------
+ *            |-->| HDMI PLL |
+ *                ------------
+ *                     |
+ *                     |-->|CGU_HDMI_IDIV_APB|------>
  */
 
-DECLARE_GLOBAL_DATA_PTR;
-
 #define CGU_ARC_IDIV           0x080
-#define CGU_TUN_IDIV           0x380
+#define CGU_TUN_IDIV_TUN       0x380
+#define CGU_TUN_IDIV_ROM       0x390
+#define CGU_TUN_IDIV_PWM       0x3A0
+#define CGU_TUN_IDIV_TIMER     0x3B0
 #define CGU_HDMI_IDIV_APB      0x480
 #define CGU_SYS_IDIV_APB       0x180
 #define CGU_SYS_IDIV_AXI       0x190
@@ -114,8 +121,70 @@ DECLARE_GLOBAL_DATA_PTR;
 #define CREG_CORE_IF_CLK_DIV_1         0x0
 #define CREG_CORE_IF_CLK_DIV_2         0x1
 
-#define PARENT_RATE                    33333333 /* fixed clock - xtal */
-#define CGU_MAX_CLOCKS                 24
+#define MIN_PLL_RATE                   100000000 /* 100 MHz */
+#define PARENT_RATE_33                 33333333 /* fixed clock - xtal */
+#define PARENT_RATE_27                 27000000 /* fixed clock - xtal */
+#define CGU_MAX_CLOCKS                 27
+
+#define CGU_SYS_CLOCKS                 16
+#define MAX_AXI_CLOCKS                 4
+
+#define CGU_TUN_CLOCKS                 4
+#define MAX_TUN_CLOCKS                 6
+
+struct hsdk_tun_idiv_cfg {
+       u32 oft;
+       u8  val[MAX_TUN_CLOCKS];
+};
+
+struct hsdk_tun_clk_cfg {
+       const u32 clk_rate[MAX_TUN_CLOCKS];
+       const u32 pll_rate[MAX_TUN_CLOCKS];
+       const struct hsdk_tun_idiv_cfg idiv[CGU_TUN_CLOCKS];
+};
+
+static const struct hsdk_tun_clk_cfg tun_clk_cfg = {
+       { 25000000,  50000000,  75000000,  100000000, 125000000, 150000000 },
+       { 600000000, 600000000, 600000000, 600000000, 750000000, 600000000 }, {
+       { CGU_TUN_IDIV_TUN,     { 24,   12,     8,      6,      6,      4 } },
+       { CGU_TUN_IDIV_ROM,     { 4,    4,      4,      4,      5,      4 } },
+       { CGU_TUN_IDIV_PWM,     { 8,    8,      8,      8,      10,     8 } },
+       { CGU_TUN_IDIV_TIMER,   { 12,   12,     12,     12,     15,     12 } }
+       }
+};
+
+struct hsdk_sys_idiv_cfg {
+       u32 oft;
+       u8  val[MAX_AXI_CLOCKS];
+};
+
+struct hsdk_axi_clk_cfg {
+       const u32 clk_rate[MAX_AXI_CLOCKS];
+       const u32 pll_rate[MAX_AXI_CLOCKS];
+       const struct hsdk_sys_idiv_cfg idiv[CGU_SYS_CLOCKS];
+};
+
+static const struct hsdk_axi_clk_cfg axi_clk_cfg = {
+       { 200000000,    400000000,      600000000,      800000000 },
+       { 800000000,    800000000,      600000000,      800000000 }, {
+       { CGU_SYS_IDIV_APB,      { 4,   4,      3,      4 } },  /* APB */
+       { CGU_SYS_IDIV_AXI,      { 4,   2,      1,      1 } },  /* AXI */
+       { CGU_SYS_IDIV_ETH,      { 2,   2,      2,      2 } },  /* ETH */
+       { CGU_SYS_IDIV_USB,      { 2,   2,      2,      2 } },  /* USB */
+       { CGU_SYS_IDIV_SDIO,     { 2,   2,      2,      2 } },  /* SDIO */
+       { CGU_SYS_IDIV_HDMI,     { 2,   2,      2,      2 } },  /* HDMI */
+       { CGU_SYS_IDIV_GFX_CORE, { 1,   1,      1,      1 } },  /* GPU-CORE */
+       { CGU_SYS_IDIV_GFX_DMA,  { 2,   2,      2,      2 } },  /* GPU-DMA */
+       { CGU_SYS_IDIV_GFX_CFG,  { 4,   4,      3,      4 } },  /* GPU-CFG */
+       { CGU_SYS_IDIV_DMAC_CORE,{ 2,   2,      2,      2 } },  /* DMAC-CORE */
+       { CGU_SYS_IDIV_DMAC_CFG, { 4,   4,      3,      4 } },  /* DMAC-CFG */
+       { CGU_SYS_IDIV_SDIO_REF, { 8,   8,      6,      8 } },  /* SDIO-REF */
+       { CGU_SYS_IDIV_SPI_REF,  { 24,  24,     18,     24 } }, /* SPI-REF */
+       { CGU_SYS_IDIV_I2C_REF,  { 4,   4,      3,      4 } },  /* I2C-REF */
+       { CGU_SYS_IDIV_UART_REF, { 24,  24,     18,     24 } }, /* UART-REF */
+       { CGU_SYS_IDIV_EBI_REF,  { 16,  16,     12,     16 } }  /* EBI-REF */
+       }
+};
 
 struct hsdk_pll_cfg {
        u32 rate;
@@ -138,6 +207,7 @@ static const struct hsdk_pll_cfg asdt_pll_cfg[] = {
        { 500000000,  0, 14, 1, 0 },
        { 600000000,  0, 17, 1, 0 },
        { 700000000,  0, 20, 1, 0 },
+       { 750000000,  1, 44, 1, 0 },
        { 800000000,  0, 23, 1, 0 },
        { 900000000,  1, 26, 0, 0 },
        { 1000000000, 1, 29, 0, 0 },
@@ -175,6 +245,7 @@ struct hsdk_cgu_clk {
 };
 
 struct hsdk_pll_devdata {
+       const u32 parent_rate;
        const struct hsdk_pll_cfg *pll_cfg;
        int (*update_rate)(struct hsdk_cgu_clk *clk, unsigned long rate,
                           const struct hsdk_pll_cfg *cfg);
@@ -186,21 +257,27 @@ static int hsdk_pll_comm_update_rate(struct hsdk_cgu_clk *, unsigned long,
                                     const struct hsdk_pll_cfg *);
 
 static const struct hsdk_pll_devdata core_pll_dat = {
+       .parent_rate = PARENT_RATE_33,
        .pll_cfg = asdt_pll_cfg,
        .update_rate = hsdk_pll_core_update_rate,
 };
 
 static const struct hsdk_pll_devdata sdt_pll_dat = {
+       .parent_rate = PARENT_RATE_33,
        .pll_cfg = asdt_pll_cfg,
        .update_rate = hsdk_pll_comm_update_rate,
 };
 
 static const struct hsdk_pll_devdata hdmi_pll_dat = {
+       .parent_rate = PARENT_RATE_27,
        .pll_cfg = hdmi_pll_cfg,
        .update_rate = hsdk_pll_comm_update_rate,
 };
 
 static ulong idiv_set(struct clk *, ulong);
+static ulong cpu_clk_set(struct clk *, ulong);
+static ulong axi_clk_set(struct clk *, ulong);
+static ulong tun_clk_set(struct clk *, ulong);
 static ulong idiv_get(struct clk *);
 static int idiv_off(struct clk *);
 static ulong pll_set(struct clk *, ulong);
@@ -218,11 +295,11 @@ struct hsdk_cgu_clock_map {
 
 static const struct hsdk_cgu_clock_map clock_map[] = {
        { CGU_ARC_PLL, 0, 0, &core_pll_dat, pll_get, pll_set, NULL },
-       { CGU_ARC_PLL, 0, CGU_ARC_IDIV, &core_pll_dat, idiv_get, idiv_set, idiv_off },
+       { CGU_ARC_PLL, 0, CGU_ARC_IDIV, &core_pll_dat, idiv_get, cpu_clk_set, idiv_off },
        { CGU_DDR_PLL, 0, 0, &sdt_pll_dat, pll_get, pll_set, NULL },
        { CGU_SYS_PLL, 0, 0, &sdt_pll_dat, pll_get, pll_set, NULL },
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_APB, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
-       { CGU_SYS_PLL, 0, CGU_SYS_IDIV_AXI, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
+       { CGU_SYS_PLL, 0, CGU_SYS_IDIV_AXI, &sdt_pll_dat, idiv_get, axi_clk_set, idiv_off },
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_ETH, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_USB, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_SDIO, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
@@ -238,7 +315,10 @@ static const struct hsdk_cgu_clock_map clock_map[] = {
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_UART_REF, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
        { CGU_SYS_PLL, 0, CGU_SYS_IDIV_EBI_REF, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
        { CGU_TUN_PLL, 0, 0, &sdt_pll_dat, pll_get, pll_set, NULL },
-       { CGU_TUN_PLL, 0, CGU_TUN_IDIV, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
+       { CGU_TUN_PLL, 0, CGU_TUN_IDIV_TUN, &sdt_pll_dat, idiv_get, tun_clk_set, idiv_off },
+       { CGU_TUN_PLL, 0, CGU_TUN_IDIV_ROM, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
+       { CGU_TUN_PLL, 0, CGU_TUN_IDIV_PWM, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
+       { CGU_TUN_PLL, 0, CGU_TUN_IDIV_TIMER, &sdt_pll_dat, idiv_get, idiv_set, idiv_off },
        { CGU_HDMI_PLL, 0, 0, &hdmi_pll_dat, pll_get, pll_set, NULL },
        { CGU_HDMI_PLL, 0, CGU_HDMI_IDIV_APB, &hdmi_pll_dat, idiv_get, idiv_set, idiv_off }
 };
@@ -305,19 +385,20 @@ static ulong pll_get(struct clk *sclk)
        u64 rate;
        u32 idiv, fbdiv, odiv;
        struct hsdk_cgu_clk *clk = dev_get_priv(sclk->dev);
+       u32 parent_rate = clk->pll_devdata->parent_rate;
 
        val = hsdk_pll_read(clk, CGU_PLL_CTRL);
 
        pr_debug("current configurarion: %#x\n", val);
 
+       /* Check if PLL is bypassed */
+       if (val & CGU_PLL_CTRL_BYPASS)
+               return parent_rate;
+
        /* Check if PLL is disabled */
        if (val & CGU_PLL_CTRL_PD)
                return 0;
 
-       /* Check if PLL is bypassed */
-       if (val & CGU_PLL_CTRL_BYPASS)
-               return PARENT_RATE;
-
        /* input divider = reg.idiv + 1 */
        idiv = 1 + ((val & CGU_PLL_CTRL_IDIV_MASK) >> CGU_PLL_CTRL_IDIV_SHIFT);
        /* fb divider = 2*(reg.fbdiv + 1) */
@@ -325,7 +406,7 @@ static ulong pll_get(struct clk *sclk)
        /* output divider = 2^(reg.odiv) */
        odiv = 1 << ((val & CGU_PLL_CTRL_ODIV_MASK) >> CGU_PLL_CTRL_ODIV_SHIFT);
 
-       rate = (u64)PARENT_RATE * fbdiv;
+       rate = (u64)parent_rate * fbdiv;
        do_div(rate, idiv * odiv);
 
        return rate;
@@ -423,7 +504,8 @@ static ulong pll_set(struct clk *sclk, ulong rate)
                }
        }
 
-       pr_err("invalid rate=%ld, parent_rate=%d\n", best_rate, PARENT_RATE);
+       pr_err("invalid rate=%ld Hz, parent_rate=%d Hz\n", best_rate,
+              clk->pll_devdata->parent_rate);
 
        return -EINVAL;
 }
@@ -453,6 +535,94 @@ static ulong idiv_get(struct clk *sclk)
        return parent_rate / div_factor;
 }
 
+/* Special behavior: wen we set this clock we set both idiv and pll */
+static ulong cpu_clk_set(struct clk *sclk, ulong rate)
+{
+       ulong ret;
+
+       ret = pll_set(sclk, rate);
+       idiv_set(sclk, rate);
+
+       return ret;
+}
+
+/* Special behavior: wen we set this clock we set both idiv and pll and all pll dividers */
+static ulong axi_clk_set(struct clk *sclk, ulong rate)
+{
+       struct hsdk_cgu_clk *clk = dev_get_priv(sclk->dev);
+       ulong pll_rate;
+       int i, freq_idx = -1;
+       ulong ret = 0;
+
+       pll_rate = pll_get(sclk);
+
+       for (i = 0; i < MAX_AXI_CLOCKS; i++) {
+               if (axi_clk_cfg.clk_rate[i] == rate) {
+                       freq_idx = i;
+                       break;
+               }
+       }
+
+       if (freq_idx < 0) {
+               pr_err("axi clk: invalid rate=%ld Hz\n", rate);
+               return -EINVAL;
+       }
+
+       /* configure PLL before dividers */
+       if (axi_clk_cfg.pll_rate[freq_idx] < pll_rate)
+               ret = pll_set(sclk, axi_clk_cfg.pll_rate[freq_idx]);
+
+       /* configure SYS dividers */
+       for (i = 0; i < CGU_SYS_CLOCKS; i++) {
+               clk->idiv_regs = clk->cgu_regs + axi_clk_cfg.idiv[i].oft;
+               hsdk_idiv_write(clk, axi_clk_cfg.idiv[i].val[freq_idx]);
+       }
+
+       /* configure PLL after dividers */
+       if (axi_clk_cfg.pll_rate[freq_idx] >= pll_rate)
+               ret = pll_set(sclk, axi_clk_cfg.pll_rate[freq_idx]);
+
+       return ret;
+}
+
+static ulong tun_clk_set(struct clk *sclk, ulong rate)
+{
+       struct hsdk_cgu_clk *clk = dev_get_priv(sclk->dev);
+       ulong pll_rate;
+       int i, freq_idx = -1;
+       ulong ret = 0;
+
+       pll_rate = pll_get(sclk);
+
+       for (i = 0; i < MAX_TUN_CLOCKS; i++) {
+               if (tun_clk_cfg.clk_rate[i] == rate) {
+                       freq_idx = i;
+                       break;
+               }
+       }
+
+       if (freq_idx < 0) {
+               pr_err("tun clk: invalid rate=%ld Hz\n", rate);
+               return -EINVAL;
+       }
+
+       /* configure PLL before dividers */
+       if (tun_clk_cfg.pll_rate[freq_idx] < pll_rate)
+               ret = pll_set(sclk, tun_clk_cfg.pll_rate[freq_idx]);
+
+       /* configure SYS dividers */
+       for (i = 0; i < CGU_TUN_CLOCKS; i++) {
+               clk->idiv_regs = clk->cgu_regs + tun_clk_cfg.idiv[i].oft;
+               hsdk_idiv_write(clk, tun_clk_cfg.idiv[i].val[freq_idx]);
+       }
+
+       /* configure PLL after dividers */
+       if (tun_clk_cfg.pll_rate[freq_idx] >= pll_rate)
+               ret = pll_set(sclk, tun_clk_cfg.pll_rate[freq_idx]);
+
+       return ret;
+}
+
 static ulong idiv_set(struct clk *sclk, ulong rate)
 {
        struct hsdk_cgu_clk *clk = dev_get_priv(sclk->dev);
@@ -466,14 +636,14 @@ static ulong idiv_set(struct clk *sclk, ulong rate)
        }
 
        if (div_factor & ~CGU_IDIV_MASK) {
-               pr_err("invalid rate=%ld, parent_rate=%ld, div=%d: max divider valie is%d\n",
+               pr_err("invalid rate=%ld Hz, parent_rate=%ld Hz, div=%d: max divider valie is%d\n",
                       rate, parent_rate, div_factor, CGU_IDIV_MASK);
 
                div_factor = CGU_IDIV_MASK;
        }
 
        if (div_factor == 0) {
-               pr_err("invalid rate=%ld, parent_rate=%ld, div=%d: min divider valie is 1\n",
+               pr_err("invalid rate=%ld Hz, parent_rate=%ld Hz, div=%d: min divider valie is 1\n",
                       rate, parent_rate, div_factor);
 
                div_factor = 1;
@@ -559,6 +729,6 @@ U_BOOT_DRIVER(hsdk_cgu_clk) = {
        .id = UCLASS_CLK,
        .of_match = hsdk_cgu_clk_id,
        .probe = hsdk_cgu_clk_probe,
-       .platdata_auto_alloc_size = sizeof(struct hsdk_cgu_clk),
+       .priv_auto_alloc_size = sizeof(struct hsdk_cgu_clk),
        .ops = &hsdk_cgu_ops,
 };