/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; 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); #ifdef CONFIG_ARM_IMX6Q_CPUFREQ static struct clk *origin_step_parent; static int origin_arm_volt, origin_soc_volt; /* * 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); } } #else static void imx6ull_lower_cpu_rate(bool enter) { /* this stub should never be called. configure with CONFIG_ARM_IMX6Q_CPUFREQ */ (void) enter; BUG(); } #endif /* * 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");