summaryrefslogtreecommitdiff
path: root/arch/arm/mach-mx51/bus_freq.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-mx51/bus_freq.c')
-rw-r--r--arch/arm/mach-mx51/bus_freq.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/arch/arm/mach-mx51/bus_freq.c b/arch/arm/mach-mx51/bus_freq.c
new file mode 100644
index 000000000000..a1c4777ede6a
--- /dev/null
+++ b/arch/arm/mach-mx51/bus_freq.c
@@ -0,0 +1,579 @@
+/*
+ * 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 <asm/io.h>
+#include <linux/proc_fs.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <mach/hardware.h>
+#include <mach/clock.h>
+#include <mach/mxc_dvfs.h>
+#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");
+