/*
  * OMAP3-specific clock framework functions
  *
- * Copyright (C) 2007 Texas Instruments, Inc.
- * Copyright (C) 2007 Nokia Corporation
+ * Copyright (C) 2007-2008 Texas Instruments, Inc.
+ * Copyright (C) 2007-2008 Nokia Corporation
  *
  * Written by Paul Walmsley
+ * Testing and integration fixes by Jouni Högander
  *
  * Parts of this code are based on code written by
  * Richard Woodruff, Tony Lindgren, Tuukka Tikkanen, Karthik Dasu
 #include <linux/delay.h>
 #include <linux/clk.h>
 #include <linux/io.h>
+#include <linux/limits.h>
 
 #include <asm/arch/clock.h>
 #include <asm/arch/sram.h>
 #define DPLL_AUTOIDLE_DISABLE                  0x0
 #define DPLL_AUTOIDLE_LOW_POWER_STOP           0x1
 
-/* CM_CLKEN_PLL*.EN* bit values */
-#define DPLL_LOCKED            0x7
+#define MAX_DPLL_WAIT_TRIES            1000000
 
 /**
  * omap3_dpll_recalc - recalculate DPLL rate
        propagate_rate(clk);
 }
 
+/* _omap3_dpll_write_clken - write clken_bits arg to a DPLL's enable bits */
+static void _omap3_dpll_write_clken(struct clk *clk, u8 clken_bits)
+{
+       const struct dpll_data *dd;
+
+       dd = clk->dpll_data;
+
+       cm_rmw_reg_bits(dd->enable_mask, clken_bits << __ffs(dd->enable_mask),
+                       dd->control_reg);
+}
+
+/* _omap3_wait_dpll_status: wait for a DPLL to enter a specific state */
+static int _omap3_wait_dpll_status(struct clk *clk, u8 state)
+{
+       const struct dpll_data *dd;
+       int i = 0;
+       int ret = -EINVAL;
+       u32 idlest_mask;
+
+       dd = clk->dpll_data;
+
+       state <<= dd->idlest_bit;
+       idlest_mask = 1 << dd->idlest_bit;
+
+       while (((cm_read_reg(dd->idlest_reg) & idlest_mask) != state) &&
+              i < MAX_DPLL_WAIT_TRIES) {
+               i++;
+               udelay(1);
+       }
+
+       if (i == MAX_DPLL_WAIT_TRIES) {
+               printk(KERN_ERR "clock: %s failed transition to '%s'\n",
+                      clk->name, (state) ? "locked" : "bypassed");
+       } else {
+               pr_debug("clock: %s transition to '%s' in %d loops\n",
+                        clk->name, (state) ? "locked" : "bypassed", i);
+
+               ret = 0;
+       }
+
+       return ret;
+}
+
+/* Non-CORE DPLL (e.g., DPLLs that do not control SDRC) clock functions */
+
+/*
+ * _omap3_noncore_dpll_lock - instruct a DPLL to lock and wait for readiness
+ * @clk: pointer to a DPLL struct clk
+ *
+ * Instructs a non-CORE DPLL to lock.  Waits for the DPLL to report
+ * readiness before returning.  Will save and restore the DPLL's
+ * autoidle state across the enable, per the CDP code.  If the DPLL
+ * locked successfully, return 0; if the DPLL did not lock in the time
+ * allotted, or DPLL3 was passed in, return -EINVAL.
+ */
+static int _omap3_noncore_dpll_lock(struct clk *clk)
+{
+       u8 ai;
+       int r;
+
+       if (clk == &dpll3_ck)
+               return -EINVAL;
+
+       pr_debug("clock: locking DPLL %s\n", clk->name);
+
+       ai = omap3_dpll_autoidle_read(clk);
+
+       _omap3_dpll_write_clken(clk, DPLL_LOCKED);
+
+       if (ai) {
+               /*
+                * If no downstream clocks are enabled, CM_IDLEST bit
+                * may never become active, so don't wait for DPLL to lock.
+                */
+               r = 0;
+               omap3_dpll_allow_idle(clk);
+       } else {
+               r = _omap3_wait_dpll_status(clk, 1);
+               omap3_dpll_deny_idle(clk);
+       };
+
+       return r;
+}
+
+/*
+ * omap3_noncore_dpll_bypass - instruct a DPLL to bypass and wait for readiness
+ * @clk: pointer to a DPLL struct clk
+ *
+ * Instructs a non-CORE DPLL to enter low-power bypass mode.  In
+ * bypass mode, the DPLL's rate is set equal to its parent clock's
+ * rate.  Waits for the DPLL to report readiness before returning.
+ * Will save and restore the DPLL's autoidle state across the enable,
+ * per the CDP code.  If the DPLL entered bypass mode successfully,
+ * return 0; if the DPLL did not enter bypass in the time allotted, or
+ * DPLL3 was passed in, or the DPLL does not support low-power bypass,
+ * return -EINVAL.
+ */
+static int _omap3_noncore_dpll_bypass(struct clk *clk)
+{
+       int r;
+       u8 ai;
+
+       if (clk == &dpll3_ck)
+               return -EINVAL;
+
+       if (!(clk->dpll_data->modes & (1 << DPLL_LOW_POWER_BYPASS)))
+               return -EINVAL;
+
+       pr_debug("clock: configuring DPLL %s for low-power bypass\n",
+                clk->name);
+
+       ai = omap3_dpll_autoidle_read(clk);
+
+       _omap3_dpll_write_clken(clk, DPLL_LOW_POWER_BYPASS);
+
+       r = _omap3_wait_dpll_status(clk, 0);
+
+       if (ai)
+               omap3_dpll_allow_idle(clk);
+       else
+               omap3_dpll_deny_idle(clk);
+
+       return r;
+}
+
+/*
+ * _omap3_noncore_dpll_stop - instruct a DPLL to stop
+ * @clk: pointer to a DPLL struct clk
+ *
+ * Instructs a non-CORE DPLL to enter low-power stop. Will save and
+ * restore the DPLL's autoidle state across the stop, per the CDP
+ * code.  If DPLL3 was passed in, or the DPLL does not support
+ * low-power stop, return -EINVAL; otherwise, return 0.
+ */
+static int _omap3_noncore_dpll_stop(struct clk *clk)
+{
+       u8 ai;
+
+       if (clk == &dpll3_ck)
+               return -EINVAL;
+
+       if (!(clk->dpll_data->modes & (1 << DPLL_LOW_POWER_STOP)))
+               return -EINVAL;
+
+       pr_debug("clock: stopping DPLL %s\n", clk->name);
+
+       ai = omap3_dpll_autoidle_read(clk);
+
+       _omap3_dpll_write_clken(clk, DPLL_LOW_POWER_STOP);
+
+       if (ai)
+               omap3_dpll_allow_idle(clk);
+       else
+               omap3_dpll_deny_idle(clk);
+
+       return 0;
+}
+
+/**
+ * omap3_noncore_dpll_enable - instruct a DPLL to enter bypass or lock mode
+ * @clk: pointer to a DPLL struct clk
+ *
+ * Instructs a non-CORE DPLL to enable, e.g., to enter bypass or lock.
+ * The choice of modes depends on the DPLL's programmed rate: if it is
+ * the same as the DPLL's parent clock, it will enter bypass;
+ * otherwise, it will enter lock.  This code will wait for the DPLL to
+ * indicate readiness before returning, unless the DPLL takes too long
+ * to enter the target state.  Intended to be used as the struct clk's
+ * enable function.  If DPLL3 was passed in, or the DPLL does not
+ * support low-power stop, or if the DPLL took too long to enter
+ * bypass or lock, return -EINVAL; otherwise, return 0.
+ */
+static int omap3_noncore_dpll_enable(struct clk *clk)
+{
+       int r;
+
+       if (clk == &dpll3_ck)
+               return -EINVAL;
+
+       if (clk->parent->rate == clk_get_rate(clk))
+               r = _omap3_noncore_dpll_bypass(clk);
+       else
+               r = _omap3_noncore_dpll_lock(clk);
+
+       return r;
+}
+
+/**
+ * omap3_noncore_dpll_enable - instruct a DPLL to enter bypass or lock mode
+ * @clk: pointer to a DPLL struct clk
+ *
+ * Instructs a non-CORE DPLL to enable, e.g., to enter bypass or lock.
+ * The choice of modes depends on the DPLL's programmed rate: if it is
+ * the same as the DPLL's parent clock, it will enter bypass;
+ * otherwise, it will enter lock.  This code will wait for the DPLL to
+ * indicate readiness before returning, unless the DPLL takes too long
+ * to enter the target state.  Intended to be used as the struct clk's
+ * enable function.  If DPLL3 was passed in, or the DPLL does not
+ * support low-power stop, or if the DPLL took too long to enter
+ * bypass or lock, return -EINVAL; otherwise, return 0.
+ */
+static void omap3_noncore_dpll_disable(struct clk *clk)
+{
+       if (clk == &dpll3_ck)
+               return;
+
+       _omap3_noncore_dpll_stop(clk);
+}
+
 /**
  * omap3_dpll_autoidle_read - read a DPLL's autoidle bits
  * @clk: struct clk * of the DPLL to read
                        dd->autoidle_reg);
 }
 
-
+/* Clock control for DPLL outputs */
 
 /**
  * omap3_clkoutx2_recalc - recalculate DPLL X2 output virtual clock rate
                propagate_rate(clk);
 }
 
+/* Common clock code */
+
 /*
  * As it is structured now, this will prevent an OMAP2/3 multiboot
  * kernel from compiling.  This will need further attention.
 
 static void omap3_dpll_allow_idle(struct clk *clk);
 static void omap3_dpll_deny_idle(struct clk *clk);
 static u32 omap3_dpll_autoidle_read(struct clk *clk);
+static int omap3_noncore_dpll_enable(struct clk *clk);
+static void omap3_noncore_dpll_disable(struct clk *clk);
 
 /*
  * DPLL1 supplies clock to the MPU.
  * DPLL5 supplies other peripheral clocks (USBHOST, USIM).
  */
 
+/* CM_CLKEN_PLL*.EN* bit values - not all are available for every DPLL */
+#define DPLL_LOW_POWER_STOP            0x1
+#define DPLL_LOW_POWER_BYPASS          0x5
+#define DPLL_LOCKED                    0x7
+
 /* PRM CLOCKS */
 
 /* According to timer32k.c, this is a 32768Hz clock, not a 32000Hz clock. */
        .div1_mask      = OMAP3430_MPU_DPLL_DIV_MASK,
        .control_reg    = OMAP_CM_REGADDR(MPU_MOD, OMAP3430_CM_CLKEN_PLL),
        .enable_mask    = OMAP3430_EN_MPU_DPLL_MASK,
+       .modes          = (1 << DPLL_LOW_POWER_BYPASS) | (1 << DPLL_LOCKED),
        .auto_recal_bit = OMAP3430_EN_MPU_DPLL_DRIFTGUARD_SHIFT,
        .recal_en_bit   = OMAP3430_MPU_DPLL_RECAL_EN_SHIFT,
        .recal_st_bit   = OMAP3430_MPU_DPLL_ST_SHIFT,
        .autoidle_reg   = OMAP_CM_REGADDR(MPU_MOD, OMAP3430_CM_AUTOIDLE_PLL),
        .autoidle_mask  = OMAP3430_AUTO_MPU_DPLL_MASK,
+       .idlest_reg     = OMAP_CM_REGADDR(MPU_MOD, OMAP3430_CM_IDLEST_PLL),
+       .idlest_bit     = OMAP3430_ST_MPU_CLK_SHIFT,
 };
 
 static struct clk dpll1_ck = {
        .div1_mask      = OMAP3430_IVA2_DPLL_DIV_MASK,
        .control_reg    = OMAP_CM_REGADDR(OMAP3430_IVA2_MOD, OMAP3430_CM_CLKEN_PLL),
        .enable_mask    = OMAP3430_EN_IVA2_DPLL_MASK,
+       .modes          = (1 << DPLL_LOW_POWER_STOP) | (1 << DPLL_LOCKED) |
+                               (1 << DPLL_LOW_POWER_BYPASS),
        .auto_recal_bit = OMAP3430_EN_IVA2_DPLL_DRIFTGUARD_SHIFT,
        .recal_en_bit   = OMAP3430_PRM_IRQENABLE_MPU_IVA2_DPLL_RECAL_EN_SHIFT,
        .recal_st_bit   = OMAP3430_PRM_IRQSTATUS_MPU_IVA2_DPLL_ST_SHIFT,
        .autoidle_reg   = OMAP_CM_REGADDR(OMAP3430_IVA2_MOD, OMAP3430_CM_AUTOIDLE_PLL),
        .autoidle_mask  = OMAP3430_AUTO_IVA2_DPLL_MASK,
-
+       .idlest_reg     = OMAP_CM_REGADDR(OMAP3430_IVA2_MOD, OMAP3430_CM_IDLEST_PLL),
+       .idlest_bit     = OMAP3430_ST_IVA2_CLK_SHIFT
 };
 
 static struct clk dpll2_ck = {
        .name           = "dpll2_ck",
        .parent         = &sys_ck,
        .dpll_data      = &dpll2_dd,
-       .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES | ALWAYS_ENABLED,
+       .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES,
+       .enable         = &omap3_noncore_dpll_enable,
+       .disable        = &omap3_noncore_dpll_disable,
        .recalc         = &omap3_dpll_recalc,
 };
 
        .recalc         = &omap2_clksel_recalc,
 };
 
-/* DPLL3 */
-/* Source clock for all interfaces and for some device fclks */
-/* Type: DPLL */
+/*
+ * DPLL3
+ * Source clock for all interfaces and for some device fclks
+ * REVISIT: Also supports fast relock bypass - not included below
+ */
 static const struct dpll_data dpll3_dd = {
        .mult_div1_reg  = OMAP_CM_REGADDR(PLL_MOD, CM_CLKSEL1),
        .mult_mask      = OMAP3430_CORE_DPLL_MULT_MASK,
        .name           = "core_ck",
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_CORE_CLK,
+       .clksel_mask    = OMAP3430_ST_CORE_CLK_MASK,
        .clksel         = core_ck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                PARENT_CONTROLS_CLOCK,
        .name           = "dpll3_m2x2_ck",
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_CORE_CLK,
+       .clksel_mask    = OMAP3430_ST_CORE_CLK_MASK,
        .clksel         = dpll3_m2x2_ck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                PARENT_CONTROLS_CLOCK,
        .parent         = &dpll3_m3x2_ck,
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_CORE_CLK,
+       .clksel_mask    = OMAP3430_ST_CORE_CLK_MASK,
        .clksel         = emu_core_alwon_ck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                PARENT_CONTROLS_CLOCK,
        .div1_mask      = OMAP3430_PERIPH_DPLL_DIV_MASK,
        .control_reg    = OMAP_CM_REGADDR(PLL_MOD, CM_CLKEN),
        .enable_mask    = OMAP3430_EN_PERIPH_DPLL_MASK,
+       .modes          = (1 << DPLL_LOW_POWER_STOP) | (1 << DPLL_LOCKED),
        .auto_recal_bit = OMAP3430_EN_PERIPH_DPLL_DRIFTGUARD_SHIFT,
        .recal_en_bit   = OMAP3430_PERIPH_DPLL_RECAL_EN_SHIFT,
        .recal_st_bit   = OMAP3430_PERIPH_DPLL_ST_SHIFT,
        .autoidle_reg   = OMAP_CM_REGADDR(PLL_MOD, CM_AUTOIDLE),
        .autoidle_mask  = OMAP3430_AUTO_PERIPH_DPLL_MASK,
+       .idlest_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
+       .idlest_bit     = OMAP3430_ST_PERIPH_CLK_SHIFT,
 };
 
 static struct clk dpll4_ck = {
        .name           = "dpll4_ck",
        .parent         = &sys_ck,
        .dpll_data      = &dpll4_dd,
-       .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES | ALWAYS_ENABLED,
+       .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES,
+       .enable         = &omap3_noncore_dpll_enable,
+       .disable        = &omap3_noncore_dpll_disable,
        .recalc         = &omap3_dpll_recalc,
 };
 
        .parent         = &dpll4_m2x2_ck,
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_PERIPH_CLK,
+       .clksel_mask    = OMAP3430_ST_PERIPH_CLK_MASK,
        .clksel         = omap_96m_alwon_fck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                 PARENT_CONTROLS_CLOCK,
        .parent         = &dpll4_m2x2_ck,
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_PERIPH_CLK,
+       .clksel_mask    = OMAP3430_ST_PERIPH_CLK_MASK,
        .clksel         = cm_96m_fck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                PARENT_CONTROLS_CLOCK,
        .parent         = &dpll4_m3x2_ck,
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_PERIPH_CLK,
+       .clksel_mask    = OMAP3430_ST_PERIPH_CLK_MASK,
        .clksel         = virt_omap_54m_fck_clksel,
        .flags          = CLOCK_IN_OMAP343X | RATE_PROPAGATES |
                                PARENT_CONTROLS_CLOCK,
        .div1_mask      = OMAP3430ES2_PERIPH2_DPLL_DIV_MASK,
        .control_reg    = OMAP_CM_REGADDR(PLL_MOD, OMAP3430ES2_CM_CLKEN2),
        .enable_mask    = OMAP3430ES2_EN_PERIPH2_DPLL_MASK,
+       .modes          = (1 << DPLL_LOW_POWER_STOP) | (1 << DPLL_LOCKED),
        .auto_recal_bit = OMAP3430ES2_EN_PERIPH2_DPLL_DRIFTGUARD_SHIFT,
        .recal_en_bit   = OMAP3430ES2_SND_PERIPH_DPLL_RECAL_EN_SHIFT,
        .recal_st_bit   = OMAP3430ES2_SND_PERIPH_DPLL_ST_SHIFT,
        .autoidle_reg   = OMAP_CM_REGADDR(PLL_MOD, OMAP3430ES2_CM_AUTOIDLE2_PLL),
        .autoidle_mask  = OMAP3430ES2_AUTO_PERIPH2_DPLL_MASK,
+       .idlest_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST2),
+       .idlest_bit     = OMAP3430ES2_ST_PERIPH2_CLK_SHIFT,
 };
 
 static struct clk dpll5_ck = {
        .name           = "dpll5_ck",
        .parent         = &sys_ck,
        .dpll_data      = &dpll5_dd,
-       .flags          = CLOCK_IN_OMAP3430ES2 | RATE_PROPAGATES |
-                               ALWAYS_ENABLED,
+       .flags          = CLOCK_IN_OMAP3430ES2 | RATE_PROPAGATES,
+       .enable         = &omap3_noncore_dpll_enable,
+       .disable        = &omap3_noncore_dpll_disable,
        .recalc         = &omap3_dpll_recalc,
 };
 
        .enable_reg     = OMAP_CM_REGADDR(OMAP3430_DSS_MOD, CM_FCLKEN),
        .enable_bit     = OMAP3430_EN_DSS1_SHIFT,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_PERIPH_CLK,
+       .clksel_mask    = OMAP3430_ST_PERIPH_CLK_MASK,
        .clksel         = dss1_alwon_fck_clksel,
        .flags          = CLOCK_IN_OMAP343X,
        .recalc         = &omap2_clksel_recalc,
        .parent         = &dpll4_m5x2_ck,
        .init           = &omap2_init_clksel_parent,
        .clksel_reg     = OMAP_CM_REGADDR(PLL_MOD, CM_IDLEST),
-       .clksel_mask    = OMAP3430_ST_PERIPH_CLK,
+       .clksel_mask    = OMAP3430_ST_PERIPH_CLK_MASK,
        .clksel         = cam_mclk_clksel,
        .enable_reg     = OMAP_CM_REGADDR(OMAP3430_CAM_MOD, CM_FCLKEN),
        .enable_bit     = OMAP3430_EN_CAM_SHIFT,