| /* |
| * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. |
| * Copyright 2017 NXP. |
| * Copyright 2018 NXP. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <asm/cacheflush.h> |
| #include <asm/fncpy.h> |
| #include <asm/io.h> |
| #include <asm/mach/map.h> |
| #include <asm/mach-types.h> |
| #include <asm/tlb.h> |
| #include <linux/busfreq-imx.h> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of.h> |
| #include <linux/of_fdt.h> |
| #include <linux/platform_device.h> |
| #include <linux/proc_fs.h> |
| #include <linux/reboot.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/sched.h> |
| #include <linux/suspend.h> |
| #include "hardware.h" |
| #include "common.h" |
| |
| #define LPAPM_CLK 24000000 |
| #define LOW_AUDIO_CLK 50000000 |
| #define HIGH_AUDIO_CLK 100000000 |
| |
| #define LOW_POWER_RUN_VOLTAGE 950000 |
| |
| #define MMDC_MDMISC_DDR_TYPE_DDR3 0 |
| #define MMDC_MDMISC_DDR_TYPE_LPDDR2 1 |
| |
| unsigned int ddr_med_rate; |
| unsigned int ddr_normal_rate; |
| unsigned long ddr_freq_change_total_size; |
| unsigned long ddr_freq_change_iram_base; |
| unsigned long ddr_freq_change_iram_phys; |
| |
| static int ddr_type; |
| static int low_bus_freq_mode; |
| static int audio_bus_freq_mode; |
| static int ultra_low_bus_freq_mode; |
| static int high_bus_freq_mode; |
| static int med_bus_freq_mode; |
| static int bus_freq_scaling_initialized; |
| static bool cancel_reduce_bus_freq; |
| static struct device *busfreq_dev; |
| static int busfreq_suspended; |
| static int bus_freq_scaling_is_active; |
| static int high_bus_count, med_bus_count, audio_bus_count, low_bus_count; |
| static unsigned int ddr_low_rate; |
| static int cur_bus_freq_mode; |
| static u32 org_arm_rate; |
| static int origin_arm_volt, origin_soc_volt; |
| |
| extern unsigned long iram_tlb_phys_addr; |
| extern int unsigned long iram_tlb_base_addr; |
| |
| /* |
| * Bus frequency management by Linux |
| */ |
| extern int init_mmdc_lpddr2_settings(struct platform_device *dev); |
| extern int init_mmdc_lpddr2_settings_mx6q(struct platform_device *dev); |
| extern int init_mmdc_ddr3_settings_imx6_up(struct platform_device *dev); |
| extern int init_mmdc_ddr3_settings_imx6_smp(struct platform_device *dev); |
| extern int init_ddrc_ddr_settings(struct platform_device *dev); |
| extern int update_ddr_freq_imx_smp(int ddr_rate); |
| extern int update_ddr_freq_imx6_up(int ddr_rate); |
| extern int update_lpddr2_freq(int ddr_rate); |
| extern int update_lpddr2_freq_smp(int ddr_rate); |
| |
| #ifdef CONFIG_OPTEE |
| /* |
| * Bus frequency management by OPTEE OS |
| */ |
| extern int update_freq_optee(int ddr_rate); |
| extern int init_freq_optee(struct platform_device *busfreq_pdev); |
| #endif |
| |
| /** |
| * @brief Functions to init and update the busfreq function of |
| * device and memory type |
| */ |
| static struct busfreq_func { |
| int (*init)(struct platform_device *dev); |
| int (*update)(int ddr_rate); |
| } busfreq_func = {NULL, NULL}; |
| |
| DEFINE_MUTEX(bus_freq_mutex); |
| |
| static struct clk *osc_clk; |
| static struct clk *ahb_clk; |
| static struct clk *axi_sel_clk; |
| static struct clk *dram_root; |
| static struct clk *dram_alt_sel; |
| static struct clk *dram_alt_root; |
| static struct clk *pfd0_392m; |
| static struct clk *pfd2_270m; |
| static struct clk *pfd1_332m; |
| static struct clk *pll_dram; |
| static struct clk *ahb_sel_clk; |
| static struct clk *axi_clk; |
| |
| static struct clk *m4_clk; |
| static struct clk *arm_clk; |
| static struct clk *pll3_clk; |
| static struct clk *step_clk; |
| static struct clk *mmdc_clk; |
| static struct clk *ocram_clk; |
| static struct clk *pll1_clk; |
| static struct clk *pll1_bypass_clk; |
| static struct clk *pll1_bypass_src_clk; |
| static struct clk *pll1_sys_clk; |
| static struct clk *pll1_sw_clk; |
| static struct clk *pll2_bypass_src_clk; |
| static struct clk *pll2_bypass_clk; |
| static struct clk *pll2_clk; |
| static struct clk *pll2_400_clk; |
| static struct clk *pll2_200_clk; |
| static struct clk *pll2_bus_clk; |
| static struct clk *periph_clk; |
| static struct clk *periph_pre_clk; |
| static struct clk *periph_clk2_clk; |
| static struct clk *periph_clk2_sel_clk; |
| static struct clk *periph2_clk; |
| static struct clk *periph2_pre_clk; |
| static struct clk *periph2_clk2_clk; |
| static struct clk *periph2_clk2_sel_clk; |
| static struct clk *axi_alt_sel_clk; |
| static struct clk *pll3_pfd1_540m_clk; |
| |
| static struct delayed_work low_bus_freq_handler; |
| static struct delayed_work bus_freq_daemon; |
| |
| static RAW_NOTIFIER_HEAD(busfreq_notifier_chain); |
| |
| static bool check_m4_sleep(void) |
| { |
| unsigned long timeout = jiffies + msecs_to_jiffies(500); |
| |
| while (imx_gpc_is_m4_sleeping() == 0) |
| if (time_after(jiffies, timeout)) |
| return false; |
| return true; |
| } |
| |
| static bool busfreq_notified_low = false; |
| |
| static int busfreq_notify(enum busfreq_event event) |
| { |
| int ret; |
| |
| if (event == LOW_BUSFREQ_ENTER) { |
| WARN_ON(busfreq_notified_low); |
| busfreq_notified_low = true; |
| } else if (event == LOW_BUSFREQ_EXIT) { |
| WARN_ON(!busfreq_notified_low); |
| busfreq_notified_low = false; |
| } |
| ret = raw_notifier_call_chain(&busfreq_notifier_chain, event, NULL); |
| |
| return notifier_to_errno(ret); |
| } |
| |
| int register_busfreq_notifier(struct notifier_block *nb) |
| { |
| return raw_notifier_chain_register(&busfreq_notifier_chain, nb); |
| } |
| EXPORT_SYMBOL(register_busfreq_notifier); |
| |
| int unregister_busfreq_notifier(struct notifier_block *nb) |
| { |
| return raw_notifier_chain_unregister(&busfreq_notifier_chain, nb); |
| } |
| EXPORT_SYMBOL(unregister_busfreq_notifier); |
| |
| static struct clk *origin_step_parent; |
| |
| /* |
| * on i.MX6ULL, when entering low bus mode, the ARM core |
| * can run at 24MHz to support the low power run mode per |
| * to design team. |
| */ |
| static void imx6ull_lower_cpu_rate(bool enter) |
| { |
| int ret; |
| |
| if (enter) { |
| org_arm_rate = clk_get_rate(arm_clk); |
| origin_arm_volt = regulator_get_voltage(arm_reg); |
| origin_soc_volt = regulator_get_voltage(soc_reg); |
| } |
| |
| clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); |
| clk_set_parent(pll1_sw_clk, pll1_sys_clk); |
| |
| if (enter) { |
| origin_step_parent = clk_get_parent(step_clk); |
| clk_set_parent(step_clk, osc_clk); |
| clk_set_parent(pll1_sw_clk, step_clk); |
| clk_set_rate(arm_clk, LPAPM_CLK); |
| if (cpu_is_imx6sll() && uart_from_osc) { |
| ret = regulator_set_voltage_tol(arm_reg, LOW_POWER_RUN_VOLTAGE, 0); |
| if (ret) |
| pr_err("set arm reg voltage failed\n"); |
| ret = regulator_set_voltage_tol(soc_reg, LOW_POWER_RUN_VOLTAGE, 0); |
| if (ret) |
| pr_err("set soc reg voltage failed\n"); |
| } |
| } else { |
| if (uart_from_osc) { |
| ret = regulator_set_voltage_tol(soc_reg, origin_soc_volt, 0); |
| if (ret) |
| pr_err("set soc reg voltage failed\n"); |
| ret = regulator_set_voltage_tol(arm_reg, origin_arm_volt, 0); |
| if (ret) |
| pr_err("set arm reg voltage failed\n"); |
| } |
| clk_set_parent(step_clk, origin_step_parent); |
| clk_set_parent(pll1_sw_clk, step_clk); |
| clk_set_rate(arm_clk, org_arm_rate); |
| clk_set_parent(pll1_bypass_clk, pll1_clk); |
| } |
| } |
| |
| /* |
| * enter_lpm_imx6_up and exit_lpm_imx6_up is used by |
| * i.MX6SX/i.MX6UL for entering and exiting lpm mode. |
| */ |
| static void enter_lpm_imx6_up(void) |
| { |
| if (cpu_is_imx6sx() && imx_src_is_m4_enabled()) |
| if (!check_m4_sleep()) |
| pr_err("M4 is NOT in sleep!!!\n"); |
| |
| /* set periph_clk2 to source from OSC for periph */ |
| clk_set_parent(periph_clk2_sel_clk, osc_clk); |
| clk_set_parent(periph_clk, periph_clk2_clk); |
| /* set ahb/ocram to 24MHz */ |
| clk_set_rate(ahb_clk, LPAPM_CLK); |
| clk_set_rate(ocram_clk, LPAPM_CLK); |
| |
| if (audio_bus_count) { |
| /* Need to ensure that PLL2_PFD_400M is kept ON. */ |
| clk_prepare_enable(pll2_400_clk); |
| if (ddr_type == IMX_DDR_TYPE_DDR3) |
| busfreq_func.update(LOW_AUDIO_CLK); |
| else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || |
| ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) |
| busfreq_func.update(HIGH_AUDIO_CLK); |
| clk_set_parent(periph2_clk2_sel_clk, pll3_clk); |
| clk_set_parent(periph2_pre_clk, pll2_400_clk); |
| clk_set_parent(periph2_clk, periph2_pre_clk); |
| /* |
| * As periph2_clk's parent is not changed from |
| * high mode to audio mode, so clk framework |
| * will not update its children's freq, but we |
| * change the mmdc's podf in asm code, so here |
| * need to update mmdc rate to make sure clk |
| * tree is right, although it will not do any |
| * change to hardware. |
| */ |
| if (high_bus_freq_mode) { |
| if (ddr_type == IMX_DDR_TYPE_DDR3) |
| clk_set_rate(mmdc_clk, LOW_AUDIO_CLK); |
| else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || |
| ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) |
| clk_set_rate(mmdc_clk, HIGH_AUDIO_CLK); |
| } |
| |
| if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && low_bus_freq_mode) |
| imx6ull_lower_cpu_rate(false); |
| |
| audio_bus_freq_mode = 1; |
| low_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_AUDIO; |
| } else { |
| busfreq_func.update(LPAPM_CLK); |
| |
| clk_set_parent(periph2_clk2_sel_clk, osc_clk); |
| clk_set_parent(periph2_clk, periph2_clk2_clk); |
| |
| if (audio_bus_freq_mode) |
| clk_disable_unprepare(pll2_400_clk); |
| |
| if (cpu_is_imx6ull() || cpu_is_imx6sll()) |
| imx6ull_lower_cpu_rate(true); |
| |
| low_bus_freq_mode = 1; |
| audio_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_LOW; |
| } |
| } |
| |
| static void enter_lpm_imx6_smp(void) |
| { |
| if (cpu_is_imx6dl()) |
| /* Set axi to periph_clk */ |
| clk_set_parent(axi_sel_clk, periph_clk); |
| |
| if (audio_bus_count) { |
| /* Need to ensure that PLL2_PFD_400M is kept ON. */ |
| clk_prepare_enable(pll2_400_clk); |
| if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) |
| busfreq_func.update(LOW_AUDIO_CLK); |
| else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) |
| busfreq_func.update(HIGH_AUDIO_CLK); |
| /* Make sure periph clk's parent also got updated */ |
| clk_set_parent(periph_clk2_sel_clk, pll3_clk); |
| if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) |
| clk_set_parent(periph_pre_clk, pll2_200_clk); |
| else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) |
| clk_set_parent(periph_pre_clk, pll2_400_clk); |
| clk_set_parent(periph_clk, periph_pre_clk); |
| |
| /* |
| * As periph_pre_clk's parent is not changed from |
| * high mode to audio mode on lpddr2, the clk framework |
| * will not update its children's freq, but we |
| * change the mmdc_ch0_axi podf in asm code, so here |
| * need to update mmdc rate to make sure clk |
| * tree is right, although it will not do any |
| * change to hardware. Calling get_rate will only call |
| * the .rate_recalc which is all we need. |
| */ |
| if (high_bus_freq_mode && mmdc_clk) |
| if (ddr_type == IMX_DDR_TYPE_LPDDR2) |
| clk_get_rate(mmdc_clk); |
| |
| audio_bus_freq_mode = 1; |
| low_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_AUDIO; |
| } else { |
| busfreq_func.update(LPAPM_CLK); |
| |
| /* Make sure periph clk's parent also got updated */ |
| clk_set_parent(periph_clk2_sel_clk, osc_clk); |
| /* Set periph_clk parent to OSC via periph_clk2_sel */ |
| clk_set_parent(periph_clk, periph_clk2_clk); |
| if (audio_bus_freq_mode) |
| clk_disable_unprepare(pll2_400_clk); |
| low_bus_freq_mode = 1; |
| audio_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_LOW; |
| } |
| } |
| |
| static void exit_lpm_imx6_up(void) |
| { |
| if ((cpu_is_imx6ull() || cpu_is_imx6sll()) && low_bus_freq_mode) |
| imx6ull_lower_cpu_rate(false); |
| |
| clk_prepare_enable(pll2_400_clk); |
| |
| /* |
| * lower ahb/ocram's freq first to avoid too high |
| * freq during parent switch from OSC to pll3. |
| */ |
| if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) |
| clk_set_rate(ahb_clk, LPAPM_CLK / 4); |
| else |
| clk_set_rate(ahb_clk, LPAPM_CLK / 3); |
| |
| clk_set_rate(ocram_clk, LPAPM_CLK / 2); |
| /* set periph clk to from pll2_bus on i.MX6UL */ |
| if (cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) |
| clk_set_parent(periph_pre_clk, pll2_bus_clk); |
| /* set periph clk to from pll2_400 */ |
| else |
| clk_set_parent(periph_pre_clk, pll2_400_clk); |
| clk_set_parent(periph_clk, periph_pre_clk); |
| /* set periph_clk2 to pll3 */ |
| clk_set_parent(periph_clk2_sel_clk, pll3_clk); |
| |
| busfreq_func.update(ddr_normal_rate); |
| |
| /* correct parent info after ddr freq change in asm code */ |
| clk_set_parent(periph2_pre_clk, pll2_400_clk); |
| clk_set_parent(periph2_clk, periph2_pre_clk); |
| clk_set_parent(periph2_clk2_sel_clk, pll3_clk); |
| |
| /* |
| * As periph2_clk's parent is not changed from |
| * audio mode to high mode, so clk framework |
| * will not update its children's freq, but we |
| * change the mmdc's podf in asm code, so here |
| * need to update mmdc rate to make sure clk |
| * tree is right, although it will not do any |
| * change to hardware. |
| */ |
| if (audio_bus_freq_mode) |
| clk_set_rate(mmdc_clk, ddr_normal_rate); |
| |
| clk_disable_unprepare(pll2_400_clk); |
| |
| if (audio_bus_freq_mode) |
| clk_disable_unprepare(pll2_400_clk); |
| } |
| |
| static void exit_lpm_imx6_smp(void) |
| { |
| struct clk *periph_clk_parent; |
| |
| if (cpu_is_imx6q() && ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) |
| periph_clk_parent = pll2_bus_clk; |
| else |
| periph_clk_parent = pll2_400_clk; |
| |
| clk_prepare_enable(pll2_400_clk); |
| |
| busfreq_func.update(ddr_normal_rate); |
| |
| /* Make sure periph clk's parent also got updated */ |
| clk_set_parent(periph_clk2_sel_clk, pll3_clk); |
| clk_set_parent(periph_pre_clk, periph_clk_parent); |
| clk_set_parent(periph_clk, periph_pre_clk); |
| if (cpu_is_imx6dl()) { |
| /* Set axi to pll3_pfd1_540m */ |
| clk_set_parent(axi_alt_sel_clk, pll3_pfd1_540m_clk); |
| clk_set_parent(axi_sel_clk, axi_alt_sel_clk); |
| } |
| /* |
| * As periph_pre_clk's parent is not changed from |
| * high mode to audio mode on lpddr2, the clk framework |
| * will not update its children's freq, but we |
| * change the mmdc_ch0_axi podf in asm code, so here |
| * need to update mmdc rate to make sure clk |
| * tree is right, although it will not do any |
| * change to hardware. Calling get_rate will only call |
| * the .rate_recalc which is all we need. |
| */ |
| if (audio_bus_freq_mode && mmdc_clk) |
| if (ddr_type == IMX_DDR_TYPE_LPDDR2) |
| clk_get_rate(mmdc_clk); |
| |
| clk_disable_unprepare(pll2_400_clk); |
| if (audio_bus_freq_mode) |
| clk_disable_unprepare(pll2_400_clk); |
| } |
| |
| static void enter_lpm_imx6sl(void) |
| { |
| if (high_bus_freq_mode) { |
| /* Set periph_clk to be sourced from OSC_CLK */ |
| clk_set_parent(periph_clk2_sel_clk, osc_clk); |
| clk_set_parent(periph_clk, periph_clk2_clk); |
| /* Ensure AHB/AXI clks are at 24MHz. */ |
| clk_set_rate(ahb_clk, LPAPM_CLK); |
| clk_set_rate(ocram_clk, LPAPM_CLK); |
| } |
| if (audio_bus_count) { |
| /* Set AHB to 8MHz to lower pwer.*/ |
| clk_set_rate(ahb_clk, LPAPM_CLK / 3); |
| |
| /* Set up DDR to 100MHz. */ |
| busfreq_func.update(HIGH_AUDIO_CLK); |
| |
| /* Fix the clock tree in kernel */ |
| clk_set_parent(periph2_pre_clk, pll2_200_clk); |
| clk_set_parent(periph2_clk, periph2_pre_clk); |
| |
| if (low_bus_freq_mode || ultra_low_bus_freq_mode) { |
| /* |
| * Fix the clock tree in kernel, make sure |
| * pll2_bypass is updated as it is |
| * sourced from PLL2. |
| */ |
| clk_set_parent(pll2_bypass_clk, pll2_clk); |
| /* |
| * Swtich ARM to run off PLL2_PFD2_400MHz |
| * since DDR is anyway at 100MHz. |
| */ |
| clk_set_parent(step_clk, pll2_400_clk); |
| clk_set_parent(pll1_sw_clk, step_clk); |
| |
| /* |
| * Need to ensure that PLL1 is bypassed and enabled |
| * before ARM-PODF is set. |
| */ |
| clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); |
| |
| /* |
| * Ensure that the clock will be |
| * at original speed. |
| */ |
| clk_set_rate(arm_clk, org_arm_rate); |
| } |
| low_bus_freq_mode = 0; |
| ultra_low_bus_freq_mode = 0; |
| audio_bus_freq_mode = 1; |
| cur_bus_freq_mode = BUS_FREQ_AUDIO; |
| } else { |
| u32 arm_div, pll1_rate; |
| org_arm_rate = clk_get_rate(arm_clk); |
| if (org_arm_rate == 0) { |
| WARN_ON(1); |
| return; |
| } |
| if (low_bus_freq_mode && low_bus_count == 0) { |
| /* |
| * We are already in DDR @ 24MHz state, but |
| * no one but ARM needs the DDR. In this case, |
| * we can lower the DDR freq to 1MHz when ARM |
| * enters WFI in this state. Keep track of this state. |
| */ |
| ultra_low_bus_freq_mode = 1; |
| low_bus_freq_mode = 0; |
| audio_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_ULTRA_LOW; |
| } else { |
| if (!ultra_low_bus_freq_mode && !low_bus_freq_mode) { |
| /* |
| * Anyway, make sure the AHB is running at 24MHz |
| * in low_bus_freq_mode. |
| */ |
| if (audio_bus_freq_mode) |
| clk_set_rate(ahb_clk, LPAPM_CLK); |
| /* |
| * Set DDR to 24MHz. |
| * Since we are going to bypass PLL2, |
| * we need to move ARM clk off PLL2_PFD2 |
| * to PLL1. Make sure the PLL1 is running |
| * at the lowest possible freq. |
| * To work well with CPUFREQ we want to ensure that |
| * the CPU freq does not change, so attempt to |
| * get a freq as close to 396MHz as possible. |
| */ |
| clk_set_rate(pll1_clk, |
| clk_round_rate(pll1_clk, (org_arm_rate * 2))); |
| pll1_rate = clk_get_rate(pll1_clk); |
| arm_div = pll1_rate / org_arm_rate; |
| if (pll1_rate / arm_div > org_arm_rate) |
| arm_div++; |
| /* |
| * Need to ensure that PLL1 is bypassed and enabled |
| * before ARM-PODF is set. |
| */ |
| clk_set_parent(pll1_bypass_clk, pll1_clk); |
| /* |
| * Ensure ARM CLK is lower before |
| * changing the parent. |
| */ |
| clk_set_rate(arm_clk, org_arm_rate / arm_div); |
| /* Now set the ARM clk parent to PLL1_SYS. */ |
| clk_set_parent(pll1_sw_clk, pll1_sys_clk); |
| |
| /* |
| * 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. */ |
| busfreq_func.update(LPAPM_CLK); |
| |
| /* |
| * Fix the clock tree in kernel. |
| * Make sure PLL2 rate is updated as it gets |
| * bypassed in the DDR freq change code. |
| */ |
| clk_set_parent(pll2_bypass_clk, pll2_bypass_src_clk); |
| clk_set_parent(periph2_clk2_sel_clk, pll2_bus_clk); |
| clk_set_parent(periph2_clk, periph2_clk2_clk); |
| } |
| if (low_bus_count == 0) { |
| ultra_low_bus_freq_mode = 1; |
| low_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_ULTRA_LOW; |
| } else { |
| ultra_low_bus_freq_mode = 0; |
| low_bus_freq_mode = 1; |
| cur_bus_freq_mode = BUS_FREQ_LOW; |
| } |
| audio_bus_freq_mode = 0; |
| } |
| } |
| } |
| |
| static void exit_lpm_imx6sl(void) |
| { |
| /* Change DDR freq in IRAM. */ |
| busfreq_func.update(ddr_normal_rate); |
| |
| /* |
| * Fix the clock tree in kernel. |
| * Make sure PLL2 rate is updated as it gets |
| * un-bypassed in the DDR freq change code. |
| */ |
| clk_set_parent(pll2_bypass_clk, pll2_clk); |
| clk_set_parent(periph2_pre_clk, pll2_400_clk); |
| clk_set_parent(periph2_clk, periph2_pre_clk); |
| |
| /* Ensure that periph_clk is sourced from PLL2_400. */ |
| clk_set_parent(periph_pre_clk, pll2_400_clk); |
| /* |
| * Before switching the perhiph_clk, ensure that the |
| * AHB/AXI will not be too fast. |
| */ |
| clk_set_rate(ahb_clk, LPAPM_CLK / 3); |
| clk_set_rate(ocram_clk, LPAPM_CLK / 2); |
| clk_set_parent(periph_clk, periph_pre_clk); |
| |
| if (low_bus_freq_mode || ultra_low_bus_freq_mode) { |
| /* Move ARM from PLL1_SW_CLK to PLL2_400. */ |
| clk_set_parent(step_clk, pll2_400_clk); |
| clk_set_parent(pll1_sw_clk, step_clk); |
| /* |
| * Need to ensure that PLL1 is bypassed and enabled |
| * before ARM-PODF is set. |
| */ |
| clk_set_parent(pll1_bypass_clk, pll1_bypass_src_clk); |
| clk_set_rate(arm_clk, org_arm_rate); |
| ultra_low_bus_freq_mode = 0; |
| } |
| } |
| |
| static void enter_lpm_imx7d(void) |
| { |
| /* |
| * The AHB clock parent switch and divider change |
| * needs to keep previous/current parent enabled |
| * per design requirement, but when we switch the |
| * clock parent, previous AHB clock parent may be |
| * disabled by common clock framework, so here we |
| * have to make sure AHB's previous parent pfd2_270m |
| * is enabled during AHB set rate. |
| */ |
| clk_prepare_enable(pfd2_270m); |
| if (audio_bus_count) { |
| clk_prepare_enable(pfd0_392m); |
| busfreq_func.update(HIGH_AUDIO_CLK); |
| |
| clk_set_parent(dram_alt_sel, pfd0_392m); |
| clk_set_parent(dram_root, dram_alt_root); |
| if (high_bus_freq_mode) { |
| clk_set_parent(axi_sel_clk, osc_clk); |
| clk_set_parent(ahb_sel_clk, osc_clk); |
| clk_set_rate(ahb_clk, LPAPM_CLK); |
| } |
| clk_disable_unprepare(pfd0_392m); |
| audio_bus_freq_mode = 1; |
| low_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_AUDIO; |
| } else { |
| busfreq_func.update(LPAPM_CLK); |
| |
| clk_set_parent(dram_alt_sel, osc_clk); |
| clk_set_parent(dram_root, dram_alt_root); |
| if (high_bus_freq_mode) { |
| clk_set_parent(axi_sel_clk, osc_clk); |
| clk_set_parent(ahb_sel_clk, osc_clk); |
| clk_set_rate(ahb_clk, LPAPM_CLK); |
| } |
| low_bus_freq_mode = 1; |
| audio_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_LOW; |
| } |
| clk_disable_unprepare(pfd2_270m); |
| } |
| |
| static void exit_lpm_imx7d(void) |
| { |
| clk_set_parent(axi_sel_clk, pfd1_332m); |
| clk_set_rate(ahb_clk, LPAPM_CLK / 2); |
| clk_set_parent(ahb_sel_clk, pfd2_270m); |
| |
| busfreq_func.update(ddr_normal_rate); |
| |
| clk_set_parent(dram_root, pll_dram); |
| } |
| |
| static void reduce_bus_freq(void) |
| { |
| if (cpu_is_imx6()) |
| clk_prepare_enable(pll3_clk); |
| |
| if (audio_bus_count && (low_bus_freq_mode || ultra_low_bus_freq_mode)) |
| busfreq_notify(LOW_BUSFREQ_EXIT); |
| else if (!audio_bus_count) |
| busfreq_notify(LOW_BUSFREQ_ENTER); |
| |
| if (cpu_is_imx7d()) |
| enter_lpm_imx7d(); |
| else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) |
| enter_lpm_imx6_up(); |
| else if (cpu_is_imx6q() || cpu_is_imx6dl()) |
| enter_lpm_imx6_smp(); |
| else if (cpu_is_imx6sl()) |
| enter_lpm_imx6sl(); |
| |
| med_bus_freq_mode = 0; |
| high_bus_freq_mode = 0; |
| |
| if (cpu_is_imx6()) |
| clk_disable_unprepare(pll3_clk); |
| |
| if (audio_bus_freq_mode) |
| dev_dbg(busfreq_dev, |
| "Bus freq set to audio mode. Count: high %d, med %d, audio %d\n", |
| high_bus_count, med_bus_count, audio_bus_count); |
| if (low_bus_freq_mode) |
| dev_dbg(busfreq_dev, |
| "Bus freq set to low mode. Count: high %d, med %d, audio %d\n", |
| high_bus_count, med_bus_count, audio_bus_count); |
| } |
| |
| static inline void cancel_low_bus_freq_handler(void) |
| { |
| cancel_delayed_work(&low_bus_freq_handler); |
| cancel_reduce_bus_freq = true; |
| } |
| |
| static void reduce_bus_freq_handler(struct work_struct *work) |
| { |
| mutex_lock(&bus_freq_mutex); |
| |
| if (!cancel_reduce_bus_freq) { |
| reduce_bus_freq(); |
| cancel_low_bus_freq_handler(); |
| } |
| |
| mutex_unlock(&bus_freq_mutex); |
| } |
| |
| /* |
| * Set the DDR, AHB to 24MHz. |
| * This mode will be activated only when none of the modules that |
| * need a higher DDR or AHB frequency are active. |
| */ |
| static int set_low_bus_freq(void) |
| { |
| if (busfreq_suspended) |
| return 0; |
| |
| if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) |
| return 0; |
| |
| cancel_reduce_bus_freq = false; |
| |
| /* |
| * Check to see if we need to got from |
| * 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 || ultra_low_bus_freq_mode)) |
| reduce_bus_freq(); |
| else |
| /* |
| * Don't lower the frequency immediately. Instead |
| * scheduled a delayed work and drop the freq if |
| * the conditions still remain the same. |
| */ |
| schedule_delayed_work(&low_bus_freq_handler, |
| usecs_to_jiffies(3000000)); |
| return 0; |
| } |
| |
| /* |
| * Set the DDR to either 528MHz or 400MHz for iMX6qd |
| * or 400MHz for iMX6dl. |
| */ |
| static int set_high_bus_freq(int high_bus_freq) |
| { |
| if (bus_freq_scaling_initialized && bus_freq_scaling_is_active) |
| cancel_low_bus_freq_handler(); |
| |
| if (busfreq_suspended) |
| return 0; |
| |
| if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active) |
| return 0; |
| |
| if (high_bus_freq_mode) |
| return 0; |
| |
| /* medium bus freq is only supported for MX6DQ */ |
| if (med_bus_freq_mode && !high_bus_freq) |
| return 0; |
| |
| if (low_bus_freq_mode || ultra_low_bus_freq_mode) |
| busfreq_notify(LOW_BUSFREQ_EXIT); |
| |
| if (cpu_is_imx6()) |
| clk_prepare_enable(pll3_clk); |
| |
| if (cpu_is_imx7d()) |
| exit_lpm_imx7d(); |
| else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) |
| exit_lpm_imx6_up(); |
| else if (cpu_is_imx6q() || cpu_is_imx6dl()) |
| exit_lpm_imx6_smp(); |
| else if (cpu_is_imx6sl()) |
| exit_lpm_imx6sl(); |
| |
| high_bus_freq_mode = 1; |
| med_bus_freq_mode = 0; |
| low_bus_freq_mode = 0; |
| audio_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_HIGH; |
| |
| if (cpu_is_imx6()) |
| clk_disable_unprepare(pll3_clk); |
| |
| if (high_bus_freq_mode) |
| dev_dbg(busfreq_dev, |
| "Bus freq set to high mode. Count: high %d, med %d, audio %d\n", |
| high_bus_count, med_bus_count, audio_bus_count); |
| if (med_bus_freq_mode) |
| dev_dbg(busfreq_dev, |
| "Bus freq set to med mode. Count: high %d, med %d, audio %d\n", |
| high_bus_count, med_bus_count, audio_bus_count); |
| |
| return 0; |
| } |
| |
| void request_bus_freq(enum bus_freq_mode mode) |
| { |
| mutex_lock(&bus_freq_mutex); |
| |
| if (mode == BUS_FREQ_ULTRA_LOW) { |
| dev_dbg(busfreq_dev, "This mode cannot be requested!\n"); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| if (mode == BUS_FREQ_HIGH) |
| high_bus_count++; |
| else if (mode == BUS_FREQ_MED) |
| med_bus_count++; |
| else if (mode == BUS_FREQ_AUDIO) |
| audio_bus_count++; |
| else if (mode == BUS_FREQ_LOW) |
| low_bus_count++; |
| |
| if (busfreq_suspended || !bus_freq_scaling_initialized || |
| !bus_freq_scaling_is_active) { |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| cancel_low_bus_freq_handler(); |
| |
| if ((mode == BUS_FREQ_HIGH) && (!high_bus_freq_mode)) { |
| set_high_bus_freq(1); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| if ((mode == BUS_FREQ_MED) && (!high_bus_freq_mode) && |
| (!med_bus_freq_mode)) { |
| set_high_bus_freq(0); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| if ((mode == BUS_FREQ_AUDIO) && (!high_bus_freq_mode) && |
| (!med_bus_freq_mode) && (!audio_bus_freq_mode)) { |
| set_low_bus_freq(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| mutex_unlock(&bus_freq_mutex); |
| } |
| EXPORT_SYMBOL(request_bus_freq); |
| |
| void release_bus_freq(enum bus_freq_mode mode) |
| { |
| mutex_lock(&bus_freq_mutex); |
| |
| if (mode == BUS_FREQ_ULTRA_LOW) { |
| dev_dbg(busfreq_dev, |
| "This mode cannot be released!\n"); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| if (mode == BUS_FREQ_HIGH) { |
| if (high_bus_count == 0) { |
| dev_err(busfreq_dev, "high bus count mismatch!\n"); |
| dump_stack(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| high_bus_count--; |
| } else if (mode == BUS_FREQ_MED) { |
| if (med_bus_count == 0) { |
| dev_err(busfreq_dev, "med bus count mismatch!\n"); |
| dump_stack(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| med_bus_count--; |
| } else if (mode == BUS_FREQ_AUDIO) { |
| if (audio_bus_count == 0) { |
| dev_err(busfreq_dev, "audio bus count mismatch!\n"); |
| dump_stack(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| audio_bus_count--; |
| } else if (mode == BUS_FREQ_LOW) { |
| if (low_bus_count == 0) { |
| dev_err(busfreq_dev, "low bus count mismatch!\n"); |
| dump_stack(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| low_bus_count--; |
| } |
| |
| if (busfreq_suspended || !bus_freq_scaling_initialized || |
| !bus_freq_scaling_is_active) { |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| if ((!audio_bus_freq_mode) && (high_bus_count == 0) && |
| (med_bus_count == 0) && (audio_bus_count != 0)) { |
| set_low_bus_freq(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| if ((!low_bus_freq_mode) && (high_bus_count == 0) && |
| (med_bus_count == 0) && (audio_bus_count == 0) && |
| (low_bus_count != 0)) { |
| set_low_bus_freq(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| if ((!ultra_low_bus_freq_mode) && (high_bus_count == 0) && |
| (med_bus_count == 0) && (audio_bus_count == 0) && |
| (low_bus_count == 0)) { |
| set_low_bus_freq(); |
| mutex_unlock(&bus_freq_mutex); |
| return; |
| } |
| |
| mutex_unlock(&bus_freq_mutex); |
| } |
| EXPORT_SYMBOL(release_bus_freq); |
| |
| int get_bus_freq_mode(void) |
| { |
| return cur_bus_freq_mode; |
| } |
| EXPORT_SYMBOL(get_bus_freq_mode); |
| |
| static struct map_desc ddr_iram_io_desc __initdata = { |
| /* .virtual and .pfn are run-time assigned */ |
| .length = SZ_1M, |
| .type = MT_MEMORY_RWX_NONCACHED, |
| }; |
| |
| const static char *ddr_freq_iram_match[] __initconst = { |
| "fsl,ddr-lpm-sram", |
| NULL |
| }; |
| |
| static int __init imx_dt_find_ddr_sram(unsigned long node, |
| const char *uname, int depth, void *data) |
| { |
| unsigned long ddr_iram_addr; |
| const __be32 *prop; |
| |
| if (of_flat_dt_match(node, ddr_freq_iram_match)) { |
| unsigned int len; |
| |
| prop = of_get_flat_dt_prop(node, "reg", &len); |
| if (prop == NULL || len != (sizeof(unsigned long) * 2)) |
| return -EINVAL; |
| ddr_iram_addr = be32_to_cpu(prop[0]); |
| ddr_freq_change_total_size = be32_to_cpu(prop[1]); |
| ddr_freq_change_iram_phys = ddr_iram_addr; |
| |
| /* Make sure ddr_freq_change_iram_phys is 8 byte aligned. */ |
| if ((uintptr_t)(ddr_freq_change_iram_phys) & (FNCPY_ALIGN - 1)) |
| ddr_freq_change_iram_phys += FNCPY_ALIGN - |
| ((uintptr_t)ddr_freq_change_iram_phys % |
| (FNCPY_ALIGN)); |
| } |
| return 0; |
| } |
| |
| void __init imx_busfreq_map_io(void) |
| { |
| /* |
| * Get the address of IRAM to be used by the ddr frequency |
| * change code from the device tree. |
| */ |
| WARN_ON(of_scan_flat_dt(imx_dt_find_ddr_sram, NULL)); |
| if (ddr_freq_change_iram_phys) { |
| ddr_freq_change_iram_base = IMX_IO_P2V( |
| ddr_freq_change_iram_phys); |
| if ((iram_tlb_phys_addr & 0xFFF00000) != |
| (ddr_freq_change_iram_phys & 0xFFF00000)) { |
| /* We need to create a 1M page table entry. */ |
| ddr_iram_io_desc.virtual = IMX_IO_P2V( |
| ddr_freq_change_iram_phys & 0xFFF00000); |
| ddr_iram_io_desc.pfn = __phys_to_pfn( |
| ddr_freq_change_iram_phys & 0xFFF00000); |
| iotable_init(&ddr_iram_io_desc, 1); |
| } |
| memset((void *)ddr_freq_change_iram_base, 0, |
| ddr_freq_change_total_size); |
| } |
| } |
| |
| static void bus_freq_daemon_handler(struct work_struct *work) |
| { |
| mutex_lock(&bus_freq_mutex); |
| if ((!low_bus_freq_mode) && (!ultra_low_bus_freq_mode) |
| && (high_bus_count == 0) && |
| (med_bus_count == 0) && (audio_bus_count == 0)) |
| set_low_bus_freq(); |
| mutex_unlock(&bus_freq_mutex); |
| } |
| |
| static ssize_t bus_freq_scaling_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| if (bus_freq_scaling_is_active) |
| return sprintf(buf, "Bus frequency scaling is enabled\n"); |
| else |
| return sprintf(buf, "Bus frequency scaling is disabled\n"); |
| } |
| |
| static ssize_t bus_freq_scaling_enable_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| if (strncmp(buf, "1", 1) == 0) { |
| bus_freq_scaling_is_active = 1; |
| set_high_bus_freq(1); |
| /* |
| * We set bus freq to highest at the beginning, |
| * so we use this daemon thread to make sure system |
| * can enter low bus mode if |
| * there is no high bus request pending |
| */ |
| schedule_delayed_work(&bus_freq_daemon, |
| usecs_to_jiffies(5000000)); |
| } else if (strncmp(buf, "0", 1) == 0) { |
| if (bus_freq_scaling_is_active) |
| set_high_bus_freq(1); |
| bus_freq_scaling_is_active = 0; |
| } |
| return size; |
| } |
| |
| static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event, |
| void *dummy) |
| { |
| mutex_lock(&bus_freq_mutex); |
| |
| if (event == PM_SUSPEND_PREPARE) { |
| if (cpu_is_imx7d() && imx_src_is_m4_enabled()) |
| imx_mu_lpm_ready(false); |
| high_bus_count++; |
| set_high_bus_freq(1); |
| busfreq_suspended = 1; |
| } else if (event == PM_POST_SUSPEND) { |
| busfreq_suspended = 0; |
| high_bus_count--; |
| if (cpu_is_imx7d() && imx_src_is_m4_enabled()) |
| imx_mu_lpm_ready(true); |
| schedule_delayed_work(&bus_freq_daemon, |
| usecs_to_jiffies(5000000)); |
| } |
| |
| mutex_unlock(&bus_freq_mutex); |
| |
| return NOTIFY_OK; |
| } |
| |
| static int busfreq_reboot_notifier_event(struct notifier_block *this, |
| unsigned long event, void *ptr) |
| { |
| /* System is rebooting. Set the system into high_bus_freq_mode. */ |
| request_bus_freq(BUS_FREQ_HIGH); |
| |
| return 0; |
| } |
| |
| static struct notifier_block imx_bus_freq_pm_notifier = { |
| .notifier_call = bus_freq_pm_notify, |
| }; |
| |
| static struct notifier_block imx_busfreq_reboot_notifier = { |
| .notifier_call = busfreq_reboot_notifier_event, |
| }; |
| |
| |
| static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show, |
| bus_freq_scaling_enable_store); |
| |
| /*! |
| * This is the probe routine for the bus frequency driver. |
| * |
| * @param pdev The platform device structure |
| * |
| * @return The function returns 0 on success |
| * |
| */ |
| |
| static int busfreq_probe(struct platform_device *pdev) |
| { |
| u32 err; |
| #ifdef CONFIG_OPTEE |
| struct device_node *node_optee = 0; |
| uint32_t busfreq_val; |
| #endif |
| |
| busfreq_dev = &pdev->dev; |
| |
| /* Return if no IRAM space is allocated for ddr freq change code. */ |
| if (!ddr_freq_change_iram_base) |
| return -ENOMEM; |
| |
| if (cpu_is_imx6()) { |
| osc_clk = devm_clk_get(&pdev->dev, "osc"); |
| pll2_400_clk = devm_clk_get(&pdev->dev, "pll2_pfd2_396m"); |
| pll2_200_clk = devm_clk_get(&pdev->dev, "pll2_198m"); |
| pll2_bus_clk = devm_clk_get(&pdev->dev, "pll2_bus"); |
| pll3_clk = devm_clk_get(&pdev->dev, "pll3_usb_otg"); |
| periph_clk = devm_clk_get(&pdev->dev, "periph"); |
| periph_pre_clk = devm_clk_get(&pdev->dev, "periph_pre"); |
| periph_clk2_clk = devm_clk_get(&pdev->dev, "periph_clk2"); |
| periph_clk2_sel_clk = devm_clk_get(&pdev->dev, |
| "periph_clk2_sel"); |
| if (IS_ERR(osc_clk) || IS_ERR(pll2_400_clk) |
| || IS_ERR(pll2_200_clk) || IS_ERR(pll2_bus_clk) |
| || IS_ERR(pll3_clk) || IS_ERR(periph_clk) |
| || IS_ERR(periph_pre_clk) || IS_ERR(periph_clk2_clk) |
| || IS_ERR(periph_clk2_sel_clk)) { |
| dev_err(busfreq_dev, |
| "%s: failed to get busfreq clk\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6dl()) { |
| axi_alt_sel_clk = devm_clk_get(&pdev->dev, "axi_alt_sel"); |
| axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); |
| pll3_pfd1_540m_clk = devm_clk_get(&pdev->dev, "pll3_pfd1_540m"); |
| if (IS_ERR(axi_alt_sel_clk) || IS_ERR(axi_sel_clk) |
| || IS_ERR(pll3_pfd1_540m_clk)) { |
| dev_err(busfreq_dev, |
| "%s: failed to get busfreq clk\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6sl() || cpu_is_imx6ull() || |
| cpu_is_imx6sll()) { |
| ahb_clk = devm_clk_get(&pdev->dev, "ahb"); |
| ocram_clk = devm_clk_get(&pdev->dev, "ocram"); |
| periph2_clk = devm_clk_get(&pdev->dev, "periph2"); |
| periph2_pre_clk = devm_clk_get(&pdev->dev, "periph2_pre"); |
| periph2_clk2_clk = devm_clk_get(&pdev->dev, "periph2_clk2"); |
| periph2_clk2_sel_clk = |
| devm_clk_get(&pdev->dev, "periph2_clk2_sel"); |
| if (IS_ERR(ahb_clk) || IS_ERR(ocram_clk) |
| || IS_ERR(periph2_clk) || IS_ERR(periph2_pre_clk) |
| || IS_ERR(periph2_clk2_clk) |
| || IS_ERR(periph2_clk2_sel_clk)) { |
| dev_err(busfreq_dev, |
| "%s: failed to get busfreq clk for imx6ul/sx/sl.\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || cpu_is_imx6sll()) { |
| mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); |
| if (IS_ERR(mmdc_clk)) { |
| dev_err(busfreq_dev, |
| "%s: failed to get mmdc clk for imx6sx/ul.\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6q()) { |
| mmdc_clk = devm_clk_get(&pdev->dev, "mmdc"); |
| if (IS_ERR(mmdc_clk)) { |
| mmdc_clk = NULL; |
| } |
| } |
| |
| if (cpu_is_imx6sx()) { |
| m4_clk = devm_clk_get(&pdev->dev, "m4"); |
| if (IS_ERR(m4_clk)) { |
| dev_err(busfreq_dev, "%s: failed to get m4 clk.\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6sl()) { |
| pll2_bypass_src_clk = devm_clk_get(&pdev->dev, "pll2_bypass_src"); |
| pll2_bypass_clk = devm_clk_get(&pdev->dev, "pll2_bypass"); |
| pll2_clk = devm_clk_get(&pdev->dev, "pll2"); |
| if (IS_ERR(pll2_bypass_src_clk) || IS_ERR(pll2_bypass_clk) |
| || IS_ERR(pll2_clk)) { |
| dev_err(busfreq_dev, |
| "%s failed to get busfreq clk for imx6sl.\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx6ull() || cpu_is_imx6sl() || cpu_is_imx6sll()) { |
| arm_clk = devm_clk_get(&pdev->dev, "arm"); |
| step_clk = devm_clk_get(&pdev->dev, "step"); |
| pll1_clk = devm_clk_get(&pdev->dev, "pll1"); |
| pll1_bypass_src_clk = devm_clk_get(&pdev->dev, "pll1_bypass_src"); |
| pll1_bypass_clk = devm_clk_get(&pdev->dev, "pll1_bypass"); |
| pll1_sys_clk = devm_clk_get(&pdev->dev, "pll1_sys"); |
| pll1_sw_clk = devm_clk_get(&pdev->dev, "pll1_sw"); |
| if (IS_ERR(arm_clk) || IS_ERR(step_clk) || IS_ERR(pll1_clk) |
| || IS_ERR(pll1_bypass_src_clk) || IS_ERR(pll1_bypass_clk) |
| || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk)) { |
| dev_err(busfreq_dev, "%s failed to get busfreq clk for imx6ull/sl.\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpu_is_imx7d()) { |
| osc_clk = devm_clk_get(&pdev->dev, "osc"); |
| axi_sel_clk = devm_clk_get(&pdev->dev, "axi_sel"); |
| ahb_sel_clk = devm_clk_get(&pdev->dev, "ahb_sel"); |
| pfd0_392m = devm_clk_get(&pdev->dev, "pfd0_392m"); |
| dram_root = devm_clk_get(&pdev->dev, "dram_root"); |
| dram_alt_sel = devm_clk_get(&pdev->dev, "dram_alt_sel"); |
| pll_dram = devm_clk_get(&pdev->dev, "pll_dram"); |
| dram_alt_root = devm_clk_get(&pdev->dev, "dram_alt_root"); |
| pfd1_332m = devm_clk_get(&pdev->dev, "pfd1_332m"); |
| pfd2_270m = devm_clk_get(&pdev->dev, "pfd2_270m"); |
| ahb_clk = devm_clk_get(&pdev->dev, "ahb"); |
| axi_clk = devm_clk_get(&pdev->dev, "axi"); |
| if (IS_ERR(osc_clk) || IS_ERR(axi_sel_clk) || IS_ERR(ahb_clk) |
| || IS_ERR(pfd0_392m) || IS_ERR(dram_root) |
| || IS_ERR(dram_alt_sel) || IS_ERR(pll_dram) |
| || IS_ERR(dram_alt_root) || IS_ERR(pfd1_332m) |
| || IS_ERR(ahb_clk) || IS_ERR(axi_clk) |
| || IS_ERR(pfd2_270m)) { |
| dev_err(busfreq_dev, |
| "%s: failed to get busfreq clk\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); |
| if (err) { |
| dev_err(busfreq_dev, |
| "Unable to register sysdev entry for BUSFREQ"); |
| return err; |
| } |
| |
| if (of_property_read_u32(pdev->dev.of_node, "fsl,max_ddr_freq", |
| &ddr_normal_rate)) { |
| dev_err(busfreq_dev, "max_ddr_freq entry missing\n"); |
| return -EINVAL; |
| } |
| |
| high_bus_freq_mode = 1; |
| med_bus_freq_mode = 0; |
| low_bus_freq_mode = 0; |
| audio_bus_freq_mode = 0; |
| ultra_low_bus_freq_mode = 0; |
| cur_bus_freq_mode = BUS_FREQ_HIGH; |
| |
| bus_freq_scaling_is_active = 1; |
| bus_freq_scaling_initialized = 1; |
| |
| ddr_low_rate = LPAPM_CLK; |
| |
| INIT_DELAYED_WORK(&low_bus_freq_handler, reduce_bus_freq_handler); |
| INIT_DELAYED_WORK(&bus_freq_daemon, bus_freq_daemon_handler); |
| register_pm_notifier(&imx_bus_freq_pm_notifier); |
| register_reboot_notifier(&imx_busfreq_reboot_notifier); |
| |
| /* enter low bus mode if no high speed device enabled */ |
| schedule_delayed_work(&bus_freq_daemon, |
| msecs_to_jiffies(10000)); |
| |
| /* |
| * Need to make sure to an entry for the ddr freq change code |
| * address in the IRAM page table. |
| * This is only required if the DDR freq code and suspend/idle |
| * code are in different OCRAM spaces. |
| */ |
| if ((iram_tlb_phys_addr & 0xFFF00000) != |
| (ddr_freq_change_iram_phys & 0xFFF00000)) { |
| unsigned long i; |
| |
| /* |
| * Make sure the ddr_iram virtual address has a mapping |
| * in the IRAM page table. |
| */ |
| i = ((IMX_IO_P2V(ddr_freq_change_iram_phys) >> 20) << 2) / 4; |
| *((unsigned long *)iram_tlb_base_addr + i) = |
| (ddr_freq_change_iram_phys & 0xFFF00000) | |
| TT_ATTRIB_NON_CACHEABLE_1M; |
| } |
| |
| if (cpu_is_imx7d()) { |
| ddr_type = imx_ddrc_get_ddr_type(); |
| /* reduce ddr3 normal rate to 400M due to CKE issue on TO1.1 */ |
| if (imx_get_soc_revision() == IMX_CHIP_REVISION_1_1 && |
| ddr_type == IMX_DDR_TYPE_DDR3) { |
| ddr_normal_rate = 400000000; |
| pr_info("ddr3 normal rate changed to 400MHz for TO1.1.\n"); |
| } |
| busfreq_func.init = &init_ddrc_ddr_settings; |
| busfreq_func.update = &update_ddr_freq_imx_smp; |
| } else if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull() || |
| cpu_is_imx6sll()) { |
| ddr_type = imx_mmdc_get_ddr_type(); |
| if (ddr_type == IMX_DDR_TYPE_DDR3) { |
| busfreq_func.init = &init_mmdc_ddr3_settings_imx6_up; |
| busfreq_func.update = &update_ddr_freq_imx6_up; |
| } else if (ddr_type == IMX_DDR_TYPE_LPDDR2 || |
| ddr_type == IMX_MMDC_DDR_TYPE_LPDDR3) { |
| busfreq_func.init = &init_mmdc_lpddr2_settings; |
| busfreq_func.update = &update_lpddr2_freq; |
| } |
| } else if (cpu_is_imx6q() || cpu_is_imx6dl()) { |
| ddr_type = imx_mmdc_get_ddr_type(); |
| if (ddr_type == MMDC_MDMISC_DDR_TYPE_DDR3) { |
| busfreq_func.init = &init_mmdc_ddr3_settings_imx6_smp; |
| busfreq_func.update = &update_ddr_freq_imx_smp; |
| } else if (ddr_type == MMDC_MDMISC_DDR_TYPE_LPDDR2) { |
| busfreq_func.init = &init_mmdc_lpddr2_settings_mx6q; |
| busfreq_func.update = &update_lpddr2_freq_smp; |
| } |
| } else if (cpu_is_imx6sl()) { |
| busfreq_func.init = &init_mmdc_lpddr2_settings; |
| busfreq_func.update = &update_lpddr2_freq; |
| } |
| |
| #ifdef CONFIG_OPTEE |
| /* |
| * Find the OPTEE node in the DT and look for the |
| * busfreq property. |
| * If property present and set to 1, busfreq is done by |
| * calling the OPTEE OS |
| */ |
| node_optee = of_find_compatible_node(NULL, NULL, "linaro,optee-tz"); |
| |
| if (node_optee) { |
| if (of_property_read_u32(node_optee, "busfreq", |
| &busfreq_val) == 0) { |
| pr_info("OPTEE busfreq %s", |
| (busfreq_val ? "Supported" : "Not Supported")); |
| if (busfreq_val) { |
| busfreq_func.init = &init_freq_optee; |
| busfreq_func.update = &update_freq_optee; |
| } |
| } |
| } |
| #endif |
| |
| if (busfreq_func.init) |
| err = busfreq_func.init(pdev); |
| else |
| err = -EINVAL; |
| |
| if (!err) { |
| if (cpu_is_imx6sx()) { |
| /* |
| * If M4 is enabled and rate > 24MHz, |
| * add high bus count |
| */ |
| if (imx_src_is_m4_enabled() && |
| (clk_get_rate(m4_clk) > LPAPM_CLK)) |
| high_bus_count++; |
| } |
| |
| if (cpu_is_imx7d() && imx_src_is_m4_enabled()) { |
| high_bus_count++; |
| imx_mu_lpm_ready(true); |
| } |
| } |
| |
| if (err) { |
| dev_err(busfreq_dev, "Busfreq init of ddr controller failed\n"); |
| return err; |
| } |
| return 0; |
| } |
| |
| static const struct of_device_id imx_busfreq_ids[] = { |
| { .compatible = "fsl,imx_busfreq", }, |
| { /* sentinel */ } |
| }; |
| |
| static struct platform_driver busfreq_driver = { |
| .driver = { |
| .name = "imx_busfreq", |
| .owner = THIS_MODULE, |
| .of_match_table = imx_busfreq_ids, |
| }, |
| .probe = busfreq_probe, |
| }; |
| |
| /*! |
| * Initialise the busfreq_driver. |
| * |
| * @return The function always returns 0. |
| */ |
| |
| static int __init busfreq_init(void) |
| { |
| #ifndef CONFIG_MX6_VPU_352M |
| if (platform_driver_register(&busfreq_driver) != 0) |
| return -ENODEV; |
| |
| pr_info("Bus freq driver module loaded\n"); |
| #endif |
| return 0; |
| } |
| |
| static void __exit busfreq_cleanup(void) |
| { |
| sysfs_remove_file(&busfreq_dev->kobj, &dev_attr_enable.attr); |
| |
| /* Unregister the device structure */ |
| platform_driver_unregister(&busfreq_driver); |
| bus_freq_scaling_initialized = 0; |
| } |
| |
| module_init(busfreq_init); |
| module_exit(busfreq_cleanup); |
| |
| MODULE_AUTHOR("Freescale Semiconductor, Inc."); |
| MODULE_DESCRIPTION("BusFreq driver"); |
| MODULE_LICENSE("GPL"); |