From fdb2a0eefc98f7b9d24d9b965b4a6b7e5830d5c1 Mon Sep 17 00:00:00 2001 From: Ranjani Vaidyanathan Date: Wed, 13 Nov 2013 14:03:35 -0600 Subject: ENGR00288123 [iMX6SL] Add low power audio support Add support for low power audio playback: 1. SSI2 is sourced from PLL4 2. Extern_audio_clk is sourced from pll4 3. PLL4 is in bypass mode during audio playback (based on freq requested by extern_audio_clk and ssi2 clk) 4. DDR is at 100MHz, AHB is at 24MHz. Signed-off-by: Ranjani Vaidyanathan --- arch/arm/mach-imx/busfreq-imx6.c | 15 +++++-- arch/arm/mach-imx/clk-imx6sl.c | 31 ++++++++++----- arch/arm/mach-imx/clk-pllv3.c | 86 +++++++++++++++++++++++++++++++++++----- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/arch/arm/mach-imx/busfreq-imx6.c b/arch/arm/mach-imx/busfreq-imx6.c index 142a0cc0228a..eddc5a0245a6 100644 --- a/arch/arm/mach-imx/busfreq-imx6.c +++ b/arch/arm/mach-imx/busfreq-imx6.c @@ -130,7 +130,8 @@ static void enter_lpm_imx6sl(void) * Swtich ARM to run off PLL2_PFD2_400MHz * since DDR is anyway at 100MHz. */ - clk_set_parent(pll1_sw_clk, pll2_400); + clk_set_parent(step_clk, pll2_400); + clk_set_parent(pll1_sw_clk, step_clk); /* * Ensure that the clock will be * at original speed. @@ -174,6 +175,15 @@ static void enter_lpm_imx6sl(void) /* Now set the ARM clk parent to PLL1_SYS. */ clk_set_parent(pll1_sw_clk, pll1_sys); + /* + * Set STEP_CLK back to OSC to save power and + * also to maintain the parent.The WFI iram code + * will switch step_clk to osc, but the clock API + * is not aware of the change and when a new request + * to change the step_clk parent to pll2_pfd2_400M + * is requested sometime later, the change is ignored. + */ + clk_set_parent(step_clk, osc_clk); /* Now set DDR to 24MHz. */ spin_lock_irqsave(&freq_lock, flags); update_lpddr2_freq(LPAPM_CLK); @@ -336,7 +346,7 @@ int set_low_bus_freq(void) * low bus freq mode to audio bus freq mode. * If so, the change needs to be done immediately. */ - if (audio_bus_count && low_bus_freq_mode) + if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode)) reduce_bus_freq(); else /* @@ -465,7 +475,6 @@ void request_bus_freq(enum bus_freq_mode mode) mutex_unlock(&bus_freq_mutex); return; } - cancel_delayed_work_sync(&low_bus_freq_handler); if (cpu_is_imx6dl()) { diff --git a/arch/arm/mach-imx/clk-imx6sl.c b/arch/arm/mach-imx/clk-imx6sl.c index 7ff93fa2e84f..8210649fe4b3 100644 --- a/arch/arm/mach-imx/clk-imx6sl.c +++ b/arch/arm/mach-imx/clk-imx6sl.c @@ -21,6 +21,7 @@ #define PFD2_CLK_GATE (1 << 23) #define PFD3_CLK_GATE (1 << 31) #define CCDR_CH0_HS_BYP 17 +#define OSC_RATE 24000000 #define CCM_CCGR_OFFSET(index) (index * 2) @@ -88,6 +89,7 @@ static struct clk_div_table video_div_table[] = { static struct clk *clks[IMX6SL_CLK_CLK_END]; static struct clk_onecell_data clk_data; static u32 cur_arm_podf; +static u32 pll1_org_rate; extern int low_bus_freq_mode; extern int audio_bus_freq_mode; @@ -104,7 +106,8 @@ void imx6sl_set_wait_clk(bool enter) u32 parent_rate; if (enter) { - u32 wait_podf, new_parent_rate; + u32 wait_podf; + u32 new_parent_rate = OSC_RATE; u32 ipg_rate = clk_get_rate(clks[IMX6SL_CLK_IPG]); u32 max_arm_wait_clk = (12 * ipg_rate) / 5; parent_rate = clk_get_rate(clks[IMX6SL_CLK_PLL1_SW]); @@ -127,10 +130,12 @@ void imx6sl_set_wait_clk(bool enter) * from the bypassed PLL1 clocks so that we can run * ARM at 24MHz. */ - clk_set_parent(clks[IMX6SL_CLK_PLL1_SW], - clks[IMX6SL_CLK_PLL1_SYS]); - } - new_parent_rate = clk_get_rate(clks[IMX6SL_CLK_PLL1_SW]); + pll1_org_rate = clk_get_rate(clks[IMX6SL_CLK_PLL1_SYS]); + /* Ensure PLL1 is at 24MHz. */ + clk_set_rate(clks[IMX6SL_CLK_PLL1_SYS], OSC_RATE); + clk_set_parent(clks[IMX6SL_CLK_PLL1_SW], clks[IMX6SL_CLK_PLL1_SYS]); + } else + new_parent_rate = clk_get_rate(clks[IMX6SL_CLK_PLL1_SW]); wait_podf = (new_parent_rate + max_arm_wait_clk - 1) / max_arm_wait_clk; @@ -140,10 +145,11 @@ void imx6sl_set_wait_clk(bool enter) /* Move ARM back to PLL1. */ clk_set_parent(clks[IMX6SL_CLK_PLL1_SW], clks[IMX6SL_CLK_PLL1_SYS]); - else if (audio_bus_freq_mode) + else if (audio_bus_freq_mode) { /* Move ARM back to PLL2_PFD2 via STEP_CLK. */ - clk_set_parent(clks[IMX6SL_CLK_PLL1_SW], - clks[IMX6SL_CLK_STEP]); + clk_set_parent(clks[IMX6SL_CLK_PLL1_SW], clks[IMX6SL_CLK_STEP]); + clk_set_rate(clks[IMX6SL_CLK_PLL1_SYS], pll1_org_rate); + } parent_rate = clk_get_rate(clks[IMX6SL_CLK_PLL1_SW]); clk_set_rate(clks[IMX6SL_CLK_ARM], parent_rate / cur_arm_podf); } @@ -274,7 +280,7 @@ static void __init imx6sl_clocks_init(struct device_node *ccm_node) clks[IMX6SL_CLK_EPDC_PIX_SEL] = imx_clk_mux_flags("epdc_pix_sel", base + 0x38, 15, 3, epdc_pix_sels, ARRAY_SIZE(epdc_pix_sels), CLK_SET_RATE_PARENT); clks[IMX6SL_CLK_SPDIF0_SEL] = imx_clk_mux("spdif0_sel", base + 0x30, 20, 2, audio_sels, ARRAY_SIZE(audio_sels)); clks[IMX6SL_CLK_SPDIF1_SEL] = imx_clk_mux("spdif1_sel", base + 0x30, 7, 2, audio_sels, ARRAY_SIZE(audio_sels)); - clks[IMX6SL_CLK_EXTERN_AUDIO_SEL] = imx_clk_mux("extern_audio_sel", base + 0x20, 19, 2, audio_sels, ARRAY_SIZE(audio_sels)); + clks[IMX6SL_CLK_EXTERN_AUDIO_SEL] = imx_clk_mux_flags("extern_audio_sel", base + 0x20, 19, 2, audio_sels, ARRAY_SIZE(audio_sels), CLK_SET_RATE_PARENT); clks[IMX6SL_CLK_ECSPI_SEL] = imx_clk_mux("ecspi_sel", base + 0x38, 18, 1, ecspi_sels, ARRAY_SIZE(ecspi_sels)); clks[IMX6SL_CLK_UART_SEL] = imx_clk_mux("uart_sel", base + 0x24, 6, 1, uart_sels, ARRAY_SIZE(uart_sels)); @@ -452,9 +458,16 @@ static void __init imx6sl_clocks_init(struct device_node *ccm_node) /* Audio clocks */ clk_set_parent(clks[IMX6SL_CLK_SPDIF0_SEL], clks[IMX6SL_CLK_PLL3_PFD3]); + /* set extern_audio to be sourced from PLL4/audio PLL */ + clk_set_parent(clks[IMX6SL_CLK_EXTERN_AUDIO_SEL], clks[IMX6SL_CLK_PLL4_AUDIO_DIV]); /* set extern_audio to 24MHz */ + clk_set_rate(clks[IMX6SL_CLK_PLL4_AUDIO], 24000000); clk_set_rate(clks[IMX6SL_CLK_EXTERN_AUDIO], 24000000); + /* set SSI2 parent to PLL4 */ + clk_set_parent(clks[IMX6SL_CLK_SSI2_SEL], clks[IMX6SL_CLK_PLL4_AUDIO_DIV]); + clk_set_rate(clks[IMX6SL_CLK_SSI2], 24000000); + /* set perclk to source from OSC 24MHz */ clk_set_parent(clks[IMX6SL_CLK_PERCLK_SEL], clks[IMX6SL_CLK_OSC]); diff --git a/arch/arm/mach-imx/clk-pllv3.c b/arch/arm/mach-imx/clk-pllv3.c index ab0e48f57829..f187052d639d 100644 --- a/arch/arm/mach-imx/clk-pllv3.c +++ b/arch/arm/mach-imx/clk-pllv3.c @@ -45,6 +45,7 @@ struct clk_pllv3 { bool powerup_set; bool always_on; u32 div_mask; + u32 rate_req; }; #define to_clk_pllv3(_hw) container_of(_hw, struct clk_pllv3, hw) @@ -105,7 +106,8 @@ static int clk_pllv3_enable(struct clk_hw *hw) struct clk_pllv3 *pll = to_clk_pllv3(hw); u32 val; - clk_pllv3_power_up_down(hw, true); + if (pll->rate_req != BYPASS_RATE) + clk_pllv3_power_up_down(hw, true); val = readl_relaxed(pll->base); val |= BM_PLL_ENABLE; @@ -124,7 +126,8 @@ static void clk_pllv3_disable(struct clk_hw *hw) val &= ~BM_PLL_ENABLE; writel_relaxed(val, pll->base); - clk_pllv3_power_up_down(hw, false); + if (pll->rate_req != BYPASS_RATE) + clk_pllv3_power_up_down(hw, false); } static unsigned long clk_pllv3_recalc_rate(struct clk_hw *hw, @@ -135,7 +138,7 @@ static unsigned long clk_pllv3_recalc_rate(struct clk_hw *hw, u32 bypass = readl_relaxed(pll->base) & BYPASS_MASK; u32 rate; - if (bypass) + if (pll->rate_req == BYPASS_RATE && bypass) rate = BYPASS_RATE; else rate = (div == 1) ? parent_rate * 22 : parent_rate * 20; @@ -147,11 +150,9 @@ static long clk_pllv3_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { unsigned long parent_rate = *prate; - struct clk_pllv3 *pll = to_clk_pllv3(hw); - u32 bypass = readl_relaxed(pll->base) & BYPASS_MASK; /* If the PLL is bypassed, its rate is 24MHz. */ - if (bypass) + if (rate == BYPASS_RATE) return BYPASS_RATE; return (rate >= parent_rate * 22) ? parent_rate * 22 : @@ -163,12 +164,22 @@ static int clk_pllv3_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_pllv3 *pll = to_clk_pllv3(hw); u32 val, div; - u32 bypass = readl_relaxed(pll->base) & BYPASS_MASK; + + pll->rate_req = rate; + val = readl_relaxed(pll->base); /* If the PLL is bypassed, its rate is 24MHz. */ - if (bypass) - return 0; + if (rate == BYPASS_RATE) { + /* Set the bypass bit. */ + val |= BM_PLL_BYPASS; + if (pll->powerup_set) + val |= BM_PLL_POWER; + else + val &= ~BM_PLL_POWER; + writel_relaxed(val, pll->base); + return 0; + } if (rate == parent_rate * 22) div = 1; else if (rate == parent_rate * 20) @@ -197,6 +208,10 @@ static unsigned long clk_pllv3_sys_recalc_rate(struct clk_hw *hw, { struct clk_pllv3 *pll = to_clk_pllv3(hw); u32 div = readl_relaxed(pll->base) & pll->div_mask; + u32 bypass = readl_relaxed(pll->base) & BYPASS_MASK; + + if (pll->rate_req == BYPASS_RATE && bypass) + return BYPASS_RATE; return parent_rate * div / 2; } @@ -209,6 +224,9 @@ static long clk_pllv3_sys_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long max_rate = parent_rate * 108 / 2; u32 div; + if (rate == BYPASS_RATE) + return BYPASS_RATE; + if (rate > max_rate) rate = max_rate; else if (rate < min_rate) @@ -226,9 +244,26 @@ static int clk_pllv3_sys_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long max_rate = parent_rate * 108 / 2; u32 val, div; - if (rate < min_rate || rate > max_rate) + if (rate != BYPASS_RATE && (rate < min_rate || rate > max_rate)) return -EINVAL; + pll->rate_req = rate; + val = readl_relaxed(pll->base); + + if (rate == BYPASS_RATE) { + /* + * Set the PLL in bypass mode if rate requested is + * BYPASS_RATE. + */ + val |= BM_PLL_BYPASS; + /* Power down the PLL. */ + if (pll->powerup_set) + val &= ~BM_PLL_POWER; + else + val |= BM_PLL_POWER; + writel_relaxed(val, pll->base); + return 0; + } div = rate * 2 / parent_rate; val = readl_relaxed(pll->base); val &= ~pll->div_mask; @@ -253,6 +288,10 @@ static unsigned long clk_pllv3_av_recalc_rate(struct clk_hw *hw, u32 mfn = readl_relaxed(pll->base + PLL_NUM_OFFSET); u32 mfd = readl_relaxed(pll->base + PLL_DENOM_OFFSET); u32 div = readl_relaxed(pll->base) & pll->div_mask; + u32 bypass = readl_relaxed(pll->base) & BYPASS_MASK; + + if (pll->rate_req == BYPASS_RATE && bypass) + return BYPASS_RATE; return (parent_rate * div) + ((parent_rate / mfd) * mfn); } @@ -267,6 +306,9 @@ static long clk_pllv3_av_round_rate(struct clk_hw *hw, unsigned long rate, u32 mfn, mfd = 1000000; s64 temp64; + if (rate == BYPASS_RATE) + return BYPASS_RATE; + if (rate > max_rate) rate = max_rate; else if (rate < min_rate) @@ -291,9 +333,31 @@ static int clk_pllv3_av_set_rate(struct clk_hw *hw, unsigned long rate, u32 mfn, mfd = 1000000; s64 temp64; - if (rate < min_rate || rate > max_rate) + if (rate != BYPASS_RATE && (rate < min_rate || rate > max_rate)) return -EINVAL; + pll->rate_req = rate; + val = readl_relaxed(pll->base); + + if (rate == BYPASS_RATE) { + /* + * Set the PLL in bypass mode if rate requested is + * BYPASS_RATE. + */ + /* Bypass the PLL */ + val |= BM_PLL_BYPASS; + /* Power down the PLL. */ + if (pll->powerup_set) + val &= ~BM_PLL_POWER; + else + val |= BM_PLL_POWER; + writel_relaxed(val, pll->base); + return 0; + } + /* Else clear the bypass bit. */ + val &= ~BM_PLL_BYPASS; + writel_relaxed(val, pll->base); + div = rate / parent_rate; temp64 = (u64) (rate - div * parent_rate); temp64 *= mfd; -- cgit v1.2.2