/* * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @file bus_freq.c * * @brief A common API for the Freescale Semiconductor i.MXC CPUfreq module * and DVFS CORE module. * * The APIs are for setting bus frequency to low or high. * * @ingroup PM */ #include #include #include #include #include #include #include #include #include #include "crm_regs.h" #define LP_NORMAL_CLK 133000000 #define LP_MED_CLK 83125000 #define LP_APM_CLK 24000000 #define NAND_LP_APM_CLK 12000000 #define DDR_LOW_FREQ_CLK 133000000 #define DDR_NORMAL_CLK 200000000 #define AXI_A_NORMAL_CLK 166250000 #define AXI_A_CLK_NORMAL_DIV 4 #define AXI_B_CLK_NORMAL_DIV 5 #define AHB_CLK_NORMAL_DIV AXI_B_CLK_NORMAL_DIV #define EMI_SLOW_CLK_NORMAL_DIV AXI_B_CLK_NORMAL_DIV #define NFC_CLK_NORMAL_DIV 4 static struct clk *ddr_clk; static struct clk *pll2; static struct clk *main_bus_clk; static struct clk *axi_a_clk; static struct clk *axi_b_clk; static struct clk *cpu_clk; static struct clk *ddr_hf_clk; static struct clk *nfc_clk; static struct clk *ahb_clk; static struct clk *vpu_clk; static struct clk *vpu_core_clk; static struct clk *emi_slow_clk; static struct clk *ddr_clk; static struct clk *ipu_clk; static struct clk *periph_apm_clk; static struct clk *lp_apm; static struct clk *osc; static struct clk *gpc_dvfs_clk; static struct clk *mipi_hsp_clk; struct regulator *lp_regulator; int low_bus_freq_mode; int high_bus_freq_mode; int bus_freq_scaling_is_active; char *gp_reg_id = "SW1"; char *lp_reg_id = "SW2"; static struct cpu_wp *cpu_wp_tbl; static struct device *busfreq_dev; static int busfreq_suspended; int sdram_autogating_paused; extern int lp_high_freq; extern int lp_med_freq; extern int dvfs_core_is_active; extern struct cpu_wp *(*get_cpu_wp)(int *wp); extern int cpu_wp_nr; extern int sdram_autogating_is_active; extern void enable_sdram_autogating(void); extern void disable_sdram_autogating(void); struct dvfs_wp dvfs_core_setpoint[] = { {33, 8, 33, 10, 10, 0x08}, {26, 0, 33, 20, 10, 0x08}, {28, 8, 33, 20, 30, 0x08}, {29, 0, 33, 20, 10, 0x08},}; #define DISABLE_PLL1 int set_low_bus_freq(void) { struct clk *tclk; u32 reg; if (bus_freq_scaling_is_active) { if (busfreq_suspended) return 0; if (clk_get_rate(cpu_clk) != cpu_wp_tbl[cpu_wp_nr - 1].cpu_rate) return 0; if (sdram_autogating_is_active) { disable_sdram_autogating(); sdram_autogating_paused = 1; } #ifdef DISABLE_PLL1 tclk = clk_get(NULL, "ddr_clk"); clk_set_parent(tclk, clk_get(NULL, "axi_a_clk")); /* Set CPU clock to be derived from PLL2 instead of PLL1 */ tclk = clk_get(NULL, "pll1_sw_clk"); clk_set_parent(tclk, clk_get(NULL, "pll2")); clk_enable(tclk); tclk = clk_get(NULL, "ddr_clk"); clk_set_parent(tclk, clk_get(NULL, "ddr_hf_clk")); #endif /*Change the DDR freq to 133Mhz. */ clk_set_rate(ddr_hf_clk, clk_round_rate(ddr_hf_clk, DDR_LOW_FREQ_CLK)); clk_enable(gpc_dvfs_clk); /* Setup the GPC. */ reg = __raw_readl(MXC_GPC_VCR); reg &= ~(MXC_GPCVCR_VINC_MASK | MXC_GPCVCR_VCNTU_MASK | MXC_GPCVCR_VCNT_MASK); reg |= (1 << MXC_GPCVCR_VCNTU_OFFSET) | (0 << MXC_GPCVCR_VCNT_OFFSET); __raw_writel(reg, MXC_GPC_VCR); reg = __raw_readl(MXC_GPC_CNTR); reg &= ~(MXC_GPCCNTR_ADU_MASK | MXC_GPCCNTR_FUPD_MASK); reg |= MXC_GPCCNTR_FUPD; __raw_writel(reg, MXC_GPC_CNTR); /* Enable DVFS-PER */ reg = __raw_readl(MXC_DVFSPER_PMCR0); reg &= ~(MXC_DVFSPER_PMCR0_UDCS_MASK | MXC_DVFSPER_PMCR0_ENABLE_MASK); reg |= MXC_DVFSPER_PMCR0_ENABLE; __raw_writel(reg, MXC_DVFSPER_PMCR0); /* Set the dvfs-podf to divide by 4. */ reg = __raw_readl(MXC_CCM_CDCR); reg &= ~MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_MASK; reg |= 3 << MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_OFFSET; __raw_writel(reg, MXC_CCM_CDCR); /* Setup the GPC */ reg = __raw_readl(MXC_GPC_VCR) & ~MXC_GPCVCR_VINC_MASK; /* Set VINC to 0. */ reg |= 0 << MXC_GPCVCR_VINC_OFFSET; __raw_writel(reg, MXC_GPC_VCR); reg = __raw_readl(MXC_GPC_CNTR); reg |= MXC_GPCCNTR_STRT; __raw_writel(reg, MXC_GPC_CNTR); while (__raw_readl(MXC_GPC_CNTR) & MXC_GPCCNTR_STRT) udelay(10); /* Disable DVFS-PER */ reg = __raw_readl(MXC_DVFSPER_PMCR0); reg &= ~MXC_DVFSPER_PMCR0_ENABLE; __raw_writel(reg, MXC_DVFSPER_PMCR0); /* Set the dividers to be close to 24Mhz from 166.25MHz*/ reg = __raw_readl(MXC_CCM_CBCDR); reg &= ~(MXC_CCM_CBCDR_AXI_A_PODF_MASK | MXC_CCM_CBCDR_AXI_B_PODF_MASK | MXC_CCM_CBCDR_AHB_PODF_MASK | MXC_CCM_CBCDR_EMI_PODF_MASK | MXC_CCM_CBCDR_NFC_PODF_OFFSET); reg |= (6 << MXC_CCM_CBCDR_AXI_A_PODF_OFFSET | 6 << MXC_CCM_CBCDR_AXI_B_PODF_OFFSET | 6 << MXC_CCM_CBCDR_AHB_PODF_OFFSET | 6 << MXC_CCM_CBCDR_EMI_PODF_OFFSET | 3 << MXC_CCM_CBCDR_NFC_PODF_OFFSET); __raw_writel(reg, MXC_CCM_CBCDR); while (__raw_readl(MXC_CCM_CDHIPR) & 0x1F) udelay(10); clk_set_parent(main_bus_clk, pll2); low_bus_freq_mode = 1; high_bus_freq_mode = 0; } return 0; } int set_high_bus_freq(int high_bus_freq) { u32 dvfs_podf = __raw_readl(MXC_CCM_CDCR) & 0x3; u32 reg; struct clk *tclk; if (bus_freq_scaling_is_active) { if (sdram_autogating_is_active) { disable_sdram_autogating(); sdram_autogating_paused = 1; } if (dvfs_podf > 1) { reg = __raw_readl(MXC_CCM_CBCDR); reg &= ~(MXC_CCM_CBCDR_AXI_A_PODF_MASK | MXC_CCM_CBCDR_AXI_B_PODF_MASK | MXC_CCM_CBCDR_AHB_PODF_MASK | MXC_CCM_CBCDR_EMI_PODF_MASK | MXC_CCM_CBCDR_NFC_PODF_OFFSET); reg |= (3 << MXC_CCM_CBCDR_AXI_A_PODF_OFFSET | 4 << MXC_CCM_CBCDR_AXI_B_PODF_OFFSET | 4 << MXC_CCM_CBCDR_AHB_PODF_OFFSET | 4 << MXC_CCM_CBCDR_EMI_PODF_OFFSET | 3 << MXC_CCM_CBCDR_NFC_PODF_OFFSET); __raw_writel(reg, MXC_CCM_CBCDR); while (__raw_readl(MXC_CCM_CDHIPR) & 0x1F) udelay(10); /* Setup the GPC. */ reg = __raw_readl(MXC_GPC_VCR); reg &= ~(MXC_GPCVCR_VINC_MASK | MXC_GPCVCR_VCNTU_MASK | MXC_GPCVCR_VCNT_MASK); reg |= (1 << MXC_GPCVCR_VCNTU_OFFSET) | (0 << MXC_GPCVCR_VCNT_OFFSET); __raw_writel(reg, MXC_GPC_VCR); reg = __raw_readl(MXC_GPC_CNTR); reg &= ~(MXC_GPCCNTR_ADU_MASK | MXC_GPCCNTR_FUPD_MASK); reg |= MXC_GPCCNTR_FUPD; __raw_writel(reg, MXC_GPC_CNTR); /* Enable DVFS-PER */ reg = __raw_readl(MXC_DVFSPER_PMCR0); reg |= MXC_DVFSPER_PMCR0_UDCS; reg |= MXC_DVFSPER_PMCR0_ENABLE; __raw_writel(reg, MXC_DVFSPER_PMCR0); /* Set the dvfs-podf to divide by 1. */ reg = __raw_readl(MXC_CCM_CDCR); reg &= ~MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_MASK; reg |= 0 << MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_OFFSET; __raw_writel(reg, MXC_CCM_CDCR); /* Setup the GPC */ reg = __raw_readl(MXC_GPC_VCR) & ~MXC_GPCVCR_VINC_MASK; /* Set VINC to 1. */ reg |= 1 << MXC_GPCVCR_VINC_OFFSET; __raw_writel(reg, MXC_GPC_VCR); reg = __raw_readl(MXC_GPC_CNTR); reg |= MXC_GPCCNTR_STRT; __raw_writel(reg, MXC_GPC_CNTR); while (__raw_readl(MXC_GPC_CNTR) & MXC_GPCCNTR_STRT) udelay(10); /* Disable DVFS-PER */ reg = __raw_readl(MXC_DVFSPER_PMCR0); reg &= ~MXC_DVFSPER_PMCR0_ENABLE; __raw_writel(reg, MXC_DVFSPER_PMCR0); clk_set_parent(main_bus_clk, pll2); clk_disable(gpc_dvfs_clk); #ifdef DISABLE_PLL1 tclk = clk_get(NULL, "ddr_clk"); clk_set_parent(tclk, clk_get(NULL, "axi_a_clk")); /* Set CPU clock to be derived from PLL1 instead of PLL2 */ tclk = clk_get(NULL, "pll1_sw_clk"); clk_set_parent(tclk, clk_get(NULL, "pll1_main_clk")); clk_disable(tclk); tclk = clk_get(NULL, "ddr_clk"); clk_set_parent(tclk, clk_get(NULL, "ddr_hf_clk")); #endif /*Change the DDR freq to 200MHz*/ clk_set_rate(ddr_hf_clk, clk_round_rate(ddr_hf_clk, DDR_NORMAL_CLK)); low_bus_freq_mode = 0; } /* * If the CPU freq is 800MHz, set the bus to the high setpoint * (133MHz) and DDR to 200MHz. */ if (clk_get_rate(cpu_clk) != cpu_wp_tbl[cpu_wp_nr - 1].cpu_rate) high_bus_freq = 1; if (((clk_get_rate(ahb_clk) == LP_MED_CLK) && lp_high_freq) || high_bus_freq) { /* Set to the high setpoint. */ high_bus_freq_mode = 1; clk_set_rate(ahb_clk, clk_round_rate(ahb_clk, LP_NORMAL_CLK)); clk_set_rate(ddr_hf_clk, clk_round_rate(ddr_hf_clk, DDR_NORMAL_CLK)); } if (!lp_high_freq && !high_bus_freq) { /* Set to the medium setpoint. */ high_bus_freq_mode = 0; low_bus_freq_mode = 0; clk_set_rate(ddr_hf_clk, clk_round_rate(ddr_hf_clk, DDR_LOW_FREQ_CLK)); clk_set_rate(ahb_clk, clk_round_rate(ahb_clk, LP_MED_CLK)); } if (sdram_autogating_paused) { enable_sdram_autogating(); sdram_autogating_paused = 0; } } return 0; } int low_freq_bus_used(void) { if ((clk_get_usecount(ipu_clk) == 0) && (clk_get_usecount(vpu_clk) == 0) && (lp_high_freq == 0) && (lp_med_freq == 0)) return 1; else return 0; } void setup_pll(void) { } 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 (strstr(buf, "1") != NULL) bus_freq_scaling_is_active = 1; else if (strstr(buf, "0") != NULL) { if (bus_freq_scaling_is_active) set_high_bus_freq(1); bus_freq_scaling_is_active = 0; } return size; } static int busfreq_suspend(struct platform_device *pdev, pm_message_t message) { if (low_bus_freq_mode) set_high_bus_freq(1); busfreq_suspended = 1; return 0; } static int busfreq_resume(struct platform_device *pdev) { busfreq_suspended = 0; return 0; } 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 __devinit busfreq_probe(struct platform_device *pdev) { int err = 0; u32 reg; busfreq_dev = &pdev->dev; main_bus_clk = clk_get(NULL, "main_bus_clk"); if (IS_ERR(main_bus_clk)) { printk(KERN_DEBUG "%s: failed to get main_bus_clk\n", __func__); return PTR_ERR(main_bus_clk); } pll2 = clk_get(NULL, "pll2"); if (IS_ERR(pll2)) { printk(KERN_DEBUG "%s: failed to get pll2\n", __func__); return PTR_ERR(pll2); } axi_a_clk = clk_get(NULL, "axi_a_clk"); if (IS_ERR(axi_a_clk)) { printk(KERN_DEBUG "%s: failed to get axi_a_clk\n", __func__); return PTR_ERR(axi_a_clk); } axi_b_clk = clk_get(NULL, "axi_b_clk"); if (IS_ERR(axi_b_clk)) { printk(KERN_DEBUG "%s: failed to get axi_b_clk\n", __func__); return PTR_ERR(axi_b_clk); } ddr_hf_clk = clk_get(NULL, "ddr_hf_clk"); if (IS_ERR(ddr_hf_clk)) { printk(KERN_DEBUG "%s: failed to get ddr_hf_clk\n", __func__); return PTR_ERR(ddr_hf_clk); } emi_slow_clk = clk_get(NULL, "emi_slow_clk"); if (IS_ERR(emi_slow_clk)) { printk(KERN_DEBUG "%s: failed to get emi_slow_clk\n", __func__); return PTR_ERR(emi_slow_clk); } nfc_clk = clk_get(NULL, "nfc_clk"); if (IS_ERR(nfc_clk)) { printk(KERN_DEBUG "%s: failed to get nfc_clk\n", __func__); return PTR_ERR(nfc_clk); } ahb_clk = clk_get(NULL, "ahb_clk"); if (IS_ERR(ahb_clk)) { printk(KERN_DEBUG "%s: failed to get ahb_clk\n", __func__); return PTR_ERR(ahb_clk); } vpu_core_clk = clk_get(NULL, "vpu_core_clk"); if (IS_ERR(vpu_core_clk)) { printk(KERN_DEBUG "%s: failed to get vpu_core_clk\n", __func__); return PTR_ERR(vpu_core_clk); } ddr_clk = clk_get(NULL, "ddr_clk"); if (IS_ERR(ddr_clk)) { printk(KERN_DEBUG "%s: failed to get ddr_clk\n", __func__); return PTR_ERR(ddr_clk); } cpu_clk = clk_get(NULL, "cpu_clk"); if (IS_ERR(cpu_clk)) { printk(KERN_DEBUG "%s: failed to get cpu_clk\n", __func__); return PTR_ERR(cpu_clk); } ipu_clk = clk_get(NULL, "ipu_clk"); if (IS_ERR(ipu_clk)) { printk(KERN_DEBUG "%s: failed to get ipu_clk\n", __func__); return PTR_ERR(ipu_clk); } mipi_hsp_clk = clk_get(NULL, "mipi_hsp_clk"); if (IS_ERR(mipi_hsp_clk)) { printk(KERN_DEBUG "%s: failed to get mipi_hsp_clk\n", __func__); return PTR_ERR(mipi_hsp_clk); } vpu_clk = clk_get(NULL, "vpu_clk"); if (IS_ERR(vpu_clk)) { printk(KERN_DEBUG "%s: failed to get vpu_clk\n", __func__); return PTR_ERR(vpu_clk); } periph_apm_clk = clk_get(NULL, "periph_apm_clk"); if (IS_ERR(periph_apm_clk)) { printk(KERN_DEBUG "%s: failed to get periph_apm_clk\n", __func__); return PTR_ERR(periph_apm_clk); } lp_apm = clk_get(NULL, "lp_apm"); if (IS_ERR(lp_apm)) { printk(KERN_DEBUG "%s: failed to get lp_apm\n", __func__); return PTR_ERR(lp_apm); } osc = clk_get(NULL, "osc"); if (IS_ERR(osc)) { printk(KERN_DEBUG "%s: failed to get osc\n", __func__); return PTR_ERR(osc); } gpc_dvfs_clk = clk_get(NULL, "gpc_dvfs_clk"); if (IS_ERR(gpc_dvfs_clk)) { printk(KERN_DEBUG "%s: failed to get gpc_dvfs_clk\n", __func__); return PTR_ERR(gpc_dvfs_clk); } err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); if (err) { printk(KERN_ERR "Unable to register sysdev entry for BUSFREQ"); return err; } /* Initialize DVFS-PODF to 0. */ reg = __raw_readl(MXC_CCM_CDCR); reg &= ~MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_MASK; __raw_writel(reg, MXC_CCM_CDCR); clk_set_parent(main_bus_clk, pll2); cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); low_bus_freq_mode = 0; high_bus_freq_mode = 1; bus_freq_scaling_is_active = 0; return 0; } static struct platform_driver busfreq_driver = { .driver = { .name = "busfreq", }, .probe = busfreq_probe, .suspend = busfreq_suspend, .resume = busfreq_resume, }; /*! * Initialise the busfreq_driver. * * @return The function always returns 0. */ static int __init busfreq_init(void) { if (platform_driver_register(&busfreq_driver) != 0) { printk(KERN_ERR "busfreq_driver register failed\n"); return -ENODEV; } printk(KERN_INFO "Bus freq driver module loaded\n"); 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); } module_init(busfreq_init); module_exit(busfreq_cleanup); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("BusFreq driver"); MODULE_LICENSE("GPL");