From mboxrd@z Thu Jan 1 00:00:00 1970 From: Daniel Walker Subject: [PATCH 3/7] msm: qsd8x50: add acpuclock code Date: Thu, 20 Jan 2011 12:32:41 -0800 Message-ID: <1295555565-21563-4-git-send-email-dwalker@codeaurora.org> References: <1295555565-21563-1-git-send-email-dwalker@codeaurora.org> Return-path: Received: from wolverine01.qualcomm.com ([199.106.114.254]:31447 "EHLO wolverine01.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754686Ab1ATUdX (ORCPT ); Thu, 20 Jan 2011 15:33:23 -0500 In-Reply-To: <1295555565-21563-1-git-send-email-dwalker@codeaurora.org> Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: linux-arm-msm@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, davidb@codeaurora.org, Daniel Walker This adds acpuclock-qsd8x50.c from Google. This provides control over the cpu frequency for qsd8x50 SoCs. Signed-off-by: Daniel Walker --- arch/arm/mach-msm/Makefile | 3 +- arch/arm/mach-msm/acpuclock-qsd8x50.c | 457 ++++++++++++++++++++++++++++++++ arch/arm/mach-msm/board-mahimahi.c | 24 ++ arch/arm/mach-msm/include/mach/board.h | 1 + 4 files changed, 484 insertions(+), 1 deletions(-) create mode 100644 arch/arm/mach-msm/acpuclock-qsd8x50.c diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 94195c1..9fd3a35 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -1,6 +1,5 @@ obj-y += io.o idle.o timer.o ifndef CONFIG_ARCH_MSM8X60 -obj-y += acpuclock-arm11.o obj-y += dma.o endif @@ -11,6 +10,8 @@ ifndef CONFIG_ARCH_MSM8X60 obj-y += irq.o endif endif +obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock-arm11.o +obj-$(CONFIG_ARCH_QSD8X50) += acpuclock-qsd8x50.o obj-$(CONFIG_ARCH_MSM8X60) += clock-dummy.o iommu.o iommu_dev.o devices-msm8x60-iommu.o obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o clock-pcom.o vreg.o diff --git a/arch/arm/mach-msm/acpuclock-qsd8x50.c b/arch/arm/mach-msm/acpuclock-qsd8x50.c new file mode 100644 index 0000000..c726b54 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-qsd8x50.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2009 Google, Inc. + * Copyright (c) 2008 QUALCOMM Incorporated. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "acpuclock.h" +#include "proc_comm.h" + +#define SHOT_SWITCH 4 +#define HOP_SWITCH 5 +#define SIMPLE_SLEW 6 +#define COMPLEX_SLEW 7 + +#define SPSS_CLK_CNTL_ADDR (MSM_CSR_BASE + 0x100) +#define SPSS_CLK_SEL_ADDR (MSM_CSR_BASE + 0x104) + +/* Scorpion PLL registers */ +#define SCPLL_CTL_ADDR (MSM_SCPLL_BASE + 0x4) +#define SCPLL_STATUS_ADDR (MSM_SCPLL_BASE + 0x18) +#define SCPLL_FSM_CTL_EXT_ADDR (MSM_SCPLL_BASE + 0x10) + +struct clkctl_acpu_speed { + unsigned acpu_khz; + unsigned clk_cfg; + unsigned clk_sel; + unsigned sc_l_value; + unsigned lpj; + int vdd; +}; + +/* clock sources */ +#define CLK_TCXO 0 /* 19.2 MHz */ +#define CLK_GLOBAL_PLL 1 /* 768 MHz */ +#define CLK_MODEM_PLL 4 /* 245 MHz (UMTS) or 235.93 MHz (CDMA) */ + +#define CCTL(src, div) (((src) << 4) | (div - 1)) + +/* core sources */ +#define SRC_RAW 0 /* clock from SPSS_CLK_CNTL */ +#define SRC_SCPLL 1 /* output of scpll 128-998 MHZ */ +#define SRC_AXI 2 /* 128 MHz */ +#define SRC_PLL1 3 /* 768 MHz */ + +struct clkctl_acpu_speed acpu_freq_tbl[] = { + { 19200, CCTL(CLK_TCXO, 1), SRC_RAW, 0, 0, 1050 }, + { 128000, CCTL(CLK_TCXO, 1), SRC_AXI, 0, 0, 1050 }, + { 245000, CCTL(CLK_MODEM_PLL, 1), SRC_RAW, 0, 0, 1050 }, + { 256000, CCTL(CLK_GLOBAL_PLL, 3), SRC_RAW, 0, 0, 1050 }, + { 384000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0A, 0, 1050 }, + { 422400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0B, 0, 1050 }, + { 460800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0C, 0, 1050 }, + { 499200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0D, 0, 1075 }, + { 537600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0E, 0, 1100 }, + { 576000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0F, 0, 1100 }, + { 614400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x10, 0, 1125 }, + { 652800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x11, 0, 1150 }, + { 691200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x12, 0, 1175 }, + { 729600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x13, 0, 1200 }, + { 768000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x14, 0, 1200 }, + { 806400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x15, 0, 1225 }, + { 844800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x16, 0, 1250 }, + { 883200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x17, 0, 1275 }, + { 921600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x18, 0, 1275 }, + { 960000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x19, 0, 1275 }, + { 998400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x1A, 0, 1275 }, + { 0 }, +}; + +/* select the standby clock that is used when switching scpll + * frequencies + * + * Currently: MPLL + */ +struct clkctl_acpu_speed *acpu_stby = &acpu_freq_tbl[2]; +#define IS_ACPU_STANDBY(x) (((x)->clk_cfg == acpu_stby->clk_cfg) && \ + ((x)->clk_sel == acpu_stby->clk_sel)) + +struct clkctl_acpu_speed *acpu_mpll = &acpu_freq_tbl[2]; + +#ifdef CONFIG_CPU_FREQ_TABLE +static struct cpufreq_frequency_table freq_table[ARRAY_SIZE(acpu_freq_tbl)]; + +static void __init acpuclk_init_cpufreq_table(void) +{ + int i; + int vdd; + for (i = 0; acpu_freq_tbl[i].acpu_khz; i++) { + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; + + /* Skip speeds using the global pll */ + if (acpu_freq_tbl[i].acpu_khz == 256000 || + acpu_freq_tbl[i].acpu_khz == 19200) + continue; + + vdd = acpu_freq_tbl[i].vdd; + /* Allow mpll and the first scpll speeds */ + if (acpu_freq_tbl[i].acpu_khz == acpu_mpll->acpu_khz || + acpu_freq_tbl[i].acpu_khz == 384000) { + freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz; + continue; + } + + /* Take the fastest speed available at the specified VDD level */ + if (vdd != acpu_freq_tbl[i + 1].vdd) + freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz; + } + + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_TABLE_END; + + cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); +} +#else +#define acpuclk_init_cpufreq_table() do {} while (0); +#endif + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t max_speed_delta_khz; + uint32_t vdd_switch_time_us; + unsigned long power_collapse_khz; + unsigned long wait_for_irq_khz; + struct regulator *regulator; +}; + +static struct clock_state drv_state = { 0 }; + +static DEFINE_SPINLOCK(acpu_lock); + +#define PLLMODE_POWERDOWN 0 +#define PLLMODE_BYPASS 1 +#define PLLMODE_STANDBY 2 +#define PLLMODE_FULL_CAL 4 +#define PLLMODE_HALF_CAL 5 +#define PLLMODE_STEP_CAL 6 +#define PLLMODE_NORMAL 7 +#define PLLMODE_MASK 7 + +static void scpll_power_down(void) +{ + uint32_t val; + + /* Wait for any frequency switches to finish. */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* put the pll in standby mode */ + val = readl(SCPLL_CTL_ADDR); + val = (val & (~PLLMODE_MASK)) | PLLMODE_STANDBY; + writel(val, SCPLL_CTL_ADDR); + dmb(); + + /* wait to stabilize in standby mode */ + udelay(10); + + val = (val & (~PLLMODE_MASK)) | PLLMODE_POWERDOWN; + writel(val, SCPLL_CTL_ADDR); + dmb(); +} + +static void scpll_set_freq(uint32_t lval) +{ + uint32_t val, ctl; + + if (lval > 33) + lval = 33; + if (lval < 10) + lval = 10; + + /* wait for any calibrations or frequency switches to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x3) + ; + + ctl = readl(SCPLL_CTL_ADDR); + + if ((ctl & PLLMODE_MASK) != PLLMODE_NORMAL) { + /* put the pll in standby mode */ + writel((ctl & (~PLLMODE_MASK)) | PLLMODE_STANDBY, SCPLL_CTL_ADDR); + dmb(); + + /* wait to stabilize in standby mode */ + udelay(10); + + /* switch to 384 MHz */ + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val & (~0x1FF)) | (0x0A << 3) | SHOT_SWITCH; + writel(val, SCPLL_FSM_CTL_EXT_ADDR); + dmb(); + + ctl = readl(SCPLL_CTL_ADDR); + writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR); + dmb(); + + /* wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* completion bit is not reliable for SHOT switch */ + udelay(25); + } + + /* write the new L val and switch mode */ + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val & (~0x1FF)) | (lval << 3) | HOP_SWITCH; + writel(val, SCPLL_FSM_CTL_EXT_ADDR); + dmb(); + + ctl = readl(SCPLL_CTL_ADDR); + writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR); + dmb(); + + /* wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; +} + +/* this is still a bit weird... */ +static void select_clock(unsigned src, unsigned config) +{ + uint32_t val; + + if (src == SRC_RAW) { + uint32_t sel = readl(SPSS_CLK_SEL_ADDR); + unsigned shift = (sel & 1) ? 8 : 0; + + /* set other clock source to the new configuration */ + val = readl(SPSS_CLK_CNTL_ADDR); + val = (val & (~(0x7F << shift))) | (config << shift); + writel(val, SPSS_CLK_CNTL_ADDR); + + /* switch to other clock source */ + writel(sel ^ 1, SPSS_CLK_SEL_ADDR); + + dmb(); /* necessary? */ + } + + /* switch to new source */ + val = readl(SPSS_CLK_SEL_ADDR) & (~6); + writel(val | ((src & 3) << 1), SPSS_CLK_SEL_ADDR); +} + +static int acpuclk_set_vdd_level(int vdd) +{ + if (!drv_state.regulator || IS_ERR(drv_state.regulator)) { + drv_state.regulator = regulator_get(NULL, "acpu_vcore"); + if (IS_ERR(drv_state.regulator)) { + pr_info("acpuclk_set_vdd_level %d no regulator\n", vdd); + /* Assume that the PMIC supports scaling the processor + * to its maximum frequency at its default voltage. + */ + return 0; + } + pr_info("acpuclk_set_vdd_level got regulator\n"); + } + vdd *= 1000; /* mV -> uV */ + return regulator_set_voltage(drv_state.regulator, vdd, vdd); +} + +int acpuclk_set_rate(unsigned long rate, int for_power_collapse) +{ + struct clkctl_acpu_speed *cur, *next; + unsigned long flags; + + cur = drv_state.current_speed; + + /* convert to KHz */ + rate /= 1000; + + pr_debug("acpuclk_set_rate(%d,%d)\n", (int) rate, for_power_collapse); + + if (rate == 0 || rate == cur->acpu_khz) + return 0; + + next = acpu_freq_tbl; + for (;;) { + if (next->acpu_khz == rate) + break; + if (next->acpu_khz == 0) + return -EINVAL; + next++; + } + + if (!for_power_collapse) { + mutex_lock(&drv_state.lock); + /* Increase VDD if needed. */ + if (next->vdd > cur->vdd) { + if (acpuclk_set_vdd_level(next->vdd)) { + pr_err("acpuclock: Unable to increase ACPU VDD.\n"); + mutex_unlock(&drv_state.lock); + return -EINVAL; + } + } + } + + spin_lock_irqsave(&acpu_lock, flags); + + pr_debug("sel=%d cfg=%02x lv=%02x -> sel=%d, cfg=%02x lv=%02x\n", + cur->clk_sel, cur->clk_cfg, cur->sc_l_value, + next->clk_sel, next->clk_cfg, next->sc_l_value); + + if (next->clk_sel == SRC_SCPLL) { + if (!IS_ACPU_STANDBY(cur)) + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + loops_per_jiffy = next->lpj; + scpll_set_freq(next->sc_l_value); + select_clock(SRC_SCPLL, 0); + } else { + loops_per_jiffy = next->lpj; + if (cur->clk_sel == SRC_SCPLL) { + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + select_clock(next->clk_sel, next->clk_cfg); + scpll_power_down(); + } else { + select_clock(next->clk_sel, next->clk_cfg); + } + } + + drv_state.current_speed = next; + + spin_unlock_irqrestore(&acpu_lock, flags); + if (!for_power_collapse) { + /* Drop VDD level if we can. */ + if (next->vdd < cur->vdd) { + if (acpuclk_set_vdd_level(next->vdd)) + pr_err("acpuclock: Unable to drop ACPU VDD.\n"); + } + mutex_unlock(&drv_state.lock); + } + + return 0; +} + +static unsigned __init acpuclk_find_speed(void) +{ + uint32_t sel, val; + + sel = readl(SPSS_CLK_SEL_ADDR); + switch ((sel & 6) >> 1) { + case 1: + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val >> 3) & 0x3f; + return val * 38400; + case 2: + return 128000; + default: + pr_err("acpu_find_speed: failed\n"); + BUG(); + return 0; + } +} + +#define PCOM_MODEM_PLL 0 +static int pll_request(unsigned id, unsigned on) +{ + on = !!on; + return msm_proc_comm(PCOM_CLKCTL_RPC_PLL_REQUEST, &id, &on); +} + +static void __init acpuclk_init(void) +{ + struct clkctl_acpu_speed *speed; + unsigned init_khz; + + init_khz = acpuclk_find_speed(); + + /* request the modem pll, and then drop it. We don't want to keep a + * ref to it, but we do want to make sure that it is initialized at + * this point. The ARM9 will ensure that the MPLL is always on + * once it is fully booted, but it may not be up by the time we get + * to here. So, our pll_request for it will block until the mpll is + * actually up. We want it up because we will want to use it as a + * temporary step during frequency scaling. */ + pll_request(PCOM_MODEM_PLL, 1); + pll_request(PCOM_MODEM_PLL, 0); + + if (!(readl(MSM_CLK_CTL_BASE + 0x300) & 1)) { + pr_err("%s: MPLL IS NOT ON!!! RUN AWAY!!\n", __func__); + BUG(); + } + + /* Move to 768MHz for boot, which is a safe frequency + * for all versions of Scorpion at the moment. + */ + speed = acpu_freq_tbl; + for (;;) { + if (speed->acpu_khz == 768000) + break; + if (speed->acpu_khz == 0) { + pr_err("acpuclk_init: cannot find 768MHz\n"); + BUG(); + } + speed++; + } + + if (init_khz != speed->acpu_khz) { + /* Bootloader needs to have SCPLL operating, but we're + * going to step over to the standby clock and make sure + * we select the right frequency on SCPLL and then + * step back to it, to make sure we're sane here. + */ + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + scpll_power_down(); + scpll_set_freq(speed->sc_l_value); + select_clock(SRC_SCPLL, 0); + } + drv_state.current_speed = speed; + + for (speed = acpu_freq_tbl; speed->acpu_khz; speed++) + speed->lpj = cpufreq_scale(loops_per_jiffy, + init_khz, speed->acpu_khz); + + loops_per_jiffy = drv_state.current_speed->lpj; +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + spin_lock_init(&acpu_lock); + mutex_init(&drv_state.lock); + + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.max_speed_delta_khz = clkdata->max_speed_delta_khz; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + drv_state.power_collapse_khz = clkdata->power_collapse_khz; + drv_state.wait_for_irq_khz = clkdata->wait_for_irq_khz; + + if (clkdata->mpll_khz) + acpu_mpll->acpu_khz = clkdata->mpll_khz; + + acpuclk_init(); + acpuclk_init_cpufreq_table(); +} diff --git a/arch/arm/mach-msm/board-mahimahi.c b/arch/arm/mach-msm/board-mahimahi.c index ef3ebf2..aedb136 100644 --- a/arch/arm/mach-msm/board-mahimahi.c +++ b/arch/arm/mach-msm/board-mahimahi.c @@ -48,8 +48,32 @@ static struct platform_device *devices[] __initdata = { &msm_device_nand, }; +static struct msm_acpu_clock_platform_data mahimahi_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .power_collapse_khz = 245000, + .wait_for_irq_khz = 245000, + .mpll_khz = 245000 +}; + +static struct msm_acpu_clock_platform_data mahimahi_cdma_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .power_collapse_khz = 235930, + .wait_for_irq_khz = 235930, + .mpll_khz = 235930 +}; + + static void __init mahimahi_init(void) { + if (is_cdma_version(system_rev)) + msm_acpu_clock_init(&mahimahi_cdma_clock_data); + else + msm_acpu_clock_init(&mahimahi_clock_data); + platform_add_devices(devices, ARRAY_SIZE(devices)); } diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h index 6abf4a6..44efaf5 100644 --- a/arch/arm/mach-msm/include/mach/board.h +++ b/arch/arm/mach-msm/include/mach/board.h @@ -27,6 +27,7 @@ struct msm_acpu_clock_platform_data uint32_t acpu_switch_time_us; uint32_t max_speed_delta_khz; uint32_t vdd_switch_time_us; + unsigned long mpll_khz; unsigned long power_collapse_khz; unsigned long wait_for_irq_khz; }; -- 1.7.0.4 -- Sent by a consultant of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum. From mboxrd@z Thu Jan 1 00:00:00 1970 From: dwalker@codeaurora.org (Daniel Walker) Date: Thu, 20 Jan 2011 12:32:41 -0800 Subject: [PATCH 3/7] msm: qsd8x50: add acpuclock code In-Reply-To: <1295555565-21563-1-git-send-email-dwalker@codeaurora.org> References: <1295555565-21563-1-git-send-email-dwalker@codeaurora.org> Message-ID: <1295555565-21563-4-git-send-email-dwalker@codeaurora.org> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org This adds acpuclock-qsd8x50.c from Google. This provides control over the cpu frequency for qsd8x50 SoCs. Signed-off-by: Daniel Walker --- arch/arm/mach-msm/Makefile | 3 +- arch/arm/mach-msm/acpuclock-qsd8x50.c | 457 ++++++++++++++++++++++++++++++++ arch/arm/mach-msm/board-mahimahi.c | 24 ++ arch/arm/mach-msm/include/mach/board.h | 1 + 4 files changed, 484 insertions(+), 1 deletions(-) create mode 100644 arch/arm/mach-msm/acpuclock-qsd8x50.c diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 94195c1..9fd3a35 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -1,6 +1,5 @@ obj-y += io.o idle.o timer.o ifndef CONFIG_ARCH_MSM8X60 -obj-y += acpuclock-arm11.o obj-y += dma.o endif @@ -11,6 +10,8 @@ ifndef CONFIG_ARCH_MSM8X60 obj-y += irq.o endif endif +obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock-arm11.o +obj-$(CONFIG_ARCH_QSD8X50) += acpuclock-qsd8x50.o obj-$(CONFIG_ARCH_MSM8X60) += clock-dummy.o iommu.o iommu_dev.o devices-msm8x60-iommu.o obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o clock-pcom.o vreg.o diff --git a/arch/arm/mach-msm/acpuclock-qsd8x50.c b/arch/arm/mach-msm/acpuclock-qsd8x50.c new file mode 100644 index 0000000..c726b54 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-qsd8x50.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2009 Google, Inc. + * Copyright (c) 2008 QUALCOMM Incorporated. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "acpuclock.h" +#include "proc_comm.h" + +#define SHOT_SWITCH 4 +#define HOP_SWITCH 5 +#define SIMPLE_SLEW 6 +#define COMPLEX_SLEW 7 + +#define SPSS_CLK_CNTL_ADDR (MSM_CSR_BASE + 0x100) +#define SPSS_CLK_SEL_ADDR (MSM_CSR_BASE + 0x104) + +/* Scorpion PLL registers */ +#define SCPLL_CTL_ADDR (MSM_SCPLL_BASE + 0x4) +#define SCPLL_STATUS_ADDR (MSM_SCPLL_BASE + 0x18) +#define SCPLL_FSM_CTL_EXT_ADDR (MSM_SCPLL_BASE + 0x10) + +struct clkctl_acpu_speed { + unsigned acpu_khz; + unsigned clk_cfg; + unsigned clk_sel; + unsigned sc_l_value; + unsigned lpj; + int vdd; +}; + +/* clock sources */ +#define CLK_TCXO 0 /* 19.2 MHz */ +#define CLK_GLOBAL_PLL 1 /* 768 MHz */ +#define CLK_MODEM_PLL 4 /* 245 MHz (UMTS) or 235.93 MHz (CDMA) */ + +#define CCTL(src, div) (((src) << 4) | (div - 1)) + +/* core sources */ +#define SRC_RAW 0 /* clock from SPSS_CLK_CNTL */ +#define SRC_SCPLL 1 /* output of scpll 128-998 MHZ */ +#define SRC_AXI 2 /* 128 MHz */ +#define SRC_PLL1 3 /* 768 MHz */ + +struct clkctl_acpu_speed acpu_freq_tbl[] = { + { 19200, CCTL(CLK_TCXO, 1), SRC_RAW, 0, 0, 1050 }, + { 128000, CCTL(CLK_TCXO, 1), SRC_AXI, 0, 0, 1050 }, + { 245000, CCTL(CLK_MODEM_PLL, 1), SRC_RAW, 0, 0, 1050 }, + { 256000, CCTL(CLK_GLOBAL_PLL, 3), SRC_RAW, 0, 0, 1050 }, + { 384000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0A, 0, 1050 }, + { 422400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0B, 0, 1050 }, + { 460800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0C, 0, 1050 }, + { 499200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0D, 0, 1075 }, + { 537600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0E, 0, 1100 }, + { 576000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0F, 0, 1100 }, + { 614400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x10, 0, 1125 }, + { 652800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x11, 0, 1150 }, + { 691200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x12, 0, 1175 }, + { 729600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x13, 0, 1200 }, + { 768000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x14, 0, 1200 }, + { 806400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x15, 0, 1225 }, + { 844800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x16, 0, 1250 }, + { 883200, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x17, 0, 1275 }, + { 921600, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x18, 0, 1275 }, + { 960000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x19, 0, 1275 }, + { 998400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x1A, 0, 1275 }, + { 0 }, +}; + +/* select the standby clock that is used when switching scpll + * frequencies + * + * Currently: MPLL + */ +struct clkctl_acpu_speed *acpu_stby = &acpu_freq_tbl[2]; +#define IS_ACPU_STANDBY(x) (((x)->clk_cfg == acpu_stby->clk_cfg) && \ + ((x)->clk_sel == acpu_stby->clk_sel)) + +struct clkctl_acpu_speed *acpu_mpll = &acpu_freq_tbl[2]; + +#ifdef CONFIG_CPU_FREQ_TABLE +static struct cpufreq_frequency_table freq_table[ARRAY_SIZE(acpu_freq_tbl)]; + +static void __init acpuclk_init_cpufreq_table(void) +{ + int i; + int vdd; + for (i = 0; acpu_freq_tbl[i].acpu_khz; i++) { + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; + + /* Skip speeds using the global pll */ + if (acpu_freq_tbl[i].acpu_khz == 256000 || + acpu_freq_tbl[i].acpu_khz == 19200) + continue; + + vdd = acpu_freq_tbl[i].vdd; + /* Allow mpll and the first scpll speeds */ + if (acpu_freq_tbl[i].acpu_khz == acpu_mpll->acpu_khz || + acpu_freq_tbl[i].acpu_khz == 384000) { + freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz; + continue; + } + + /* Take the fastest speed available at the specified VDD level */ + if (vdd != acpu_freq_tbl[i + 1].vdd) + freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz; + } + + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_TABLE_END; + + cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); +} +#else +#define acpuclk_init_cpufreq_table() do {} while (0); +#endif + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t max_speed_delta_khz; + uint32_t vdd_switch_time_us; + unsigned long power_collapse_khz; + unsigned long wait_for_irq_khz; + struct regulator *regulator; +}; + +static struct clock_state drv_state = { 0 }; + +static DEFINE_SPINLOCK(acpu_lock); + +#define PLLMODE_POWERDOWN 0 +#define PLLMODE_BYPASS 1 +#define PLLMODE_STANDBY 2 +#define PLLMODE_FULL_CAL 4 +#define PLLMODE_HALF_CAL 5 +#define PLLMODE_STEP_CAL 6 +#define PLLMODE_NORMAL 7 +#define PLLMODE_MASK 7 + +static void scpll_power_down(void) +{ + uint32_t val; + + /* Wait for any frequency switches to finish. */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* put the pll in standby mode */ + val = readl(SCPLL_CTL_ADDR); + val = (val & (~PLLMODE_MASK)) | PLLMODE_STANDBY; + writel(val, SCPLL_CTL_ADDR); + dmb(); + + /* wait to stabilize in standby mode */ + udelay(10); + + val = (val & (~PLLMODE_MASK)) | PLLMODE_POWERDOWN; + writel(val, SCPLL_CTL_ADDR); + dmb(); +} + +static void scpll_set_freq(uint32_t lval) +{ + uint32_t val, ctl; + + if (lval > 33) + lval = 33; + if (lval < 10) + lval = 10; + + /* wait for any calibrations or frequency switches to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x3) + ; + + ctl = readl(SCPLL_CTL_ADDR); + + if ((ctl & PLLMODE_MASK) != PLLMODE_NORMAL) { + /* put the pll in standby mode */ + writel((ctl & (~PLLMODE_MASK)) | PLLMODE_STANDBY, SCPLL_CTL_ADDR); + dmb(); + + /* wait to stabilize in standby mode */ + udelay(10); + + /* switch to 384 MHz */ + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val & (~0x1FF)) | (0x0A << 3) | SHOT_SWITCH; + writel(val, SCPLL_FSM_CTL_EXT_ADDR); + dmb(); + + ctl = readl(SCPLL_CTL_ADDR); + writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR); + dmb(); + + /* wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* completion bit is not reliable for SHOT switch */ + udelay(25); + } + + /* write the new L val and switch mode */ + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val & (~0x1FF)) | (lval << 3) | HOP_SWITCH; + writel(val, SCPLL_FSM_CTL_EXT_ADDR); + dmb(); + + ctl = readl(SCPLL_CTL_ADDR); + writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR); + dmb(); + + /* wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; +} + +/* this is still a bit weird... */ +static void select_clock(unsigned src, unsigned config) +{ + uint32_t val; + + if (src == SRC_RAW) { + uint32_t sel = readl(SPSS_CLK_SEL_ADDR); + unsigned shift = (sel & 1) ? 8 : 0; + + /* set other clock source to the new configuration */ + val = readl(SPSS_CLK_CNTL_ADDR); + val = (val & (~(0x7F << shift))) | (config << shift); + writel(val, SPSS_CLK_CNTL_ADDR); + + /* switch to other clock source */ + writel(sel ^ 1, SPSS_CLK_SEL_ADDR); + + dmb(); /* necessary? */ + } + + /* switch to new source */ + val = readl(SPSS_CLK_SEL_ADDR) & (~6); + writel(val | ((src & 3) << 1), SPSS_CLK_SEL_ADDR); +} + +static int acpuclk_set_vdd_level(int vdd) +{ + if (!drv_state.regulator || IS_ERR(drv_state.regulator)) { + drv_state.regulator = regulator_get(NULL, "acpu_vcore"); + if (IS_ERR(drv_state.regulator)) { + pr_info("acpuclk_set_vdd_level %d no regulator\n", vdd); + /* Assume that the PMIC supports scaling the processor + * to its maximum frequency@its default voltage. + */ + return 0; + } + pr_info("acpuclk_set_vdd_level got regulator\n"); + } + vdd *= 1000; /* mV -> uV */ + return regulator_set_voltage(drv_state.regulator, vdd, vdd); +} + +int acpuclk_set_rate(unsigned long rate, int for_power_collapse) +{ + struct clkctl_acpu_speed *cur, *next; + unsigned long flags; + + cur = drv_state.current_speed; + + /* convert to KHz */ + rate /= 1000; + + pr_debug("acpuclk_set_rate(%d,%d)\n", (int) rate, for_power_collapse); + + if (rate == 0 || rate == cur->acpu_khz) + return 0; + + next = acpu_freq_tbl; + for (;;) { + if (next->acpu_khz == rate) + break; + if (next->acpu_khz == 0) + return -EINVAL; + next++; + } + + if (!for_power_collapse) { + mutex_lock(&drv_state.lock); + /* Increase VDD if needed. */ + if (next->vdd > cur->vdd) { + if (acpuclk_set_vdd_level(next->vdd)) { + pr_err("acpuclock: Unable to increase ACPU VDD.\n"); + mutex_unlock(&drv_state.lock); + return -EINVAL; + } + } + } + + spin_lock_irqsave(&acpu_lock, flags); + + pr_debug("sel=%d cfg=%02x lv=%02x -> sel=%d, cfg=%02x lv=%02x\n", + cur->clk_sel, cur->clk_cfg, cur->sc_l_value, + next->clk_sel, next->clk_cfg, next->sc_l_value); + + if (next->clk_sel == SRC_SCPLL) { + if (!IS_ACPU_STANDBY(cur)) + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + loops_per_jiffy = next->lpj; + scpll_set_freq(next->sc_l_value); + select_clock(SRC_SCPLL, 0); + } else { + loops_per_jiffy = next->lpj; + if (cur->clk_sel == SRC_SCPLL) { + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + select_clock(next->clk_sel, next->clk_cfg); + scpll_power_down(); + } else { + select_clock(next->clk_sel, next->clk_cfg); + } + } + + drv_state.current_speed = next; + + spin_unlock_irqrestore(&acpu_lock, flags); + if (!for_power_collapse) { + /* Drop VDD level if we can. */ + if (next->vdd < cur->vdd) { + if (acpuclk_set_vdd_level(next->vdd)) + pr_err("acpuclock: Unable to drop ACPU VDD.\n"); + } + mutex_unlock(&drv_state.lock); + } + + return 0; +} + +static unsigned __init acpuclk_find_speed(void) +{ + uint32_t sel, val; + + sel = readl(SPSS_CLK_SEL_ADDR); + switch ((sel & 6) >> 1) { + case 1: + val = readl(SCPLL_FSM_CTL_EXT_ADDR); + val = (val >> 3) & 0x3f; + return val * 38400; + case 2: + return 128000; + default: + pr_err("acpu_find_speed: failed\n"); + BUG(); + return 0; + } +} + +#define PCOM_MODEM_PLL 0 +static int pll_request(unsigned id, unsigned on) +{ + on = !!on; + return msm_proc_comm(PCOM_CLKCTL_RPC_PLL_REQUEST, &id, &on); +} + +static void __init acpuclk_init(void) +{ + struct clkctl_acpu_speed *speed; + unsigned init_khz; + + init_khz = acpuclk_find_speed(); + + /* request the modem pll, and then drop it. We don't want to keep a + * ref to it, but we do want to make sure that it is initialized at + * this point. The ARM9 will ensure that the MPLL is always on + * once it is fully booted, but it may not be up by the time we get + * to here. So, our pll_request for it will block until the mpll is + * actually up. We want it up because we will want to use it as a + * temporary step during frequency scaling. */ + pll_request(PCOM_MODEM_PLL, 1); + pll_request(PCOM_MODEM_PLL, 0); + + if (!(readl(MSM_CLK_CTL_BASE + 0x300) & 1)) { + pr_err("%s: MPLL IS NOT ON!!! RUN AWAY!!\n", __func__); + BUG(); + } + + /* Move to 768MHz for boot, which is a safe frequency + * for all versions of Scorpion at the moment. + */ + speed = acpu_freq_tbl; + for (;;) { + if (speed->acpu_khz == 768000) + break; + if (speed->acpu_khz == 0) { + pr_err("acpuclk_init: cannot find 768MHz\n"); + BUG(); + } + speed++; + } + + if (init_khz != speed->acpu_khz) { + /* Bootloader needs to have SCPLL operating, but we're + * going to step over to the standby clock and make sure + * we select the right frequency on SCPLL and then + * step back to it, to make sure we're sane here. + */ + select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg); + scpll_power_down(); + scpll_set_freq(speed->sc_l_value); + select_clock(SRC_SCPLL, 0); + } + drv_state.current_speed = speed; + + for (speed = acpu_freq_tbl; speed->acpu_khz; speed++) + speed->lpj = cpufreq_scale(loops_per_jiffy, + init_khz, speed->acpu_khz); + + loops_per_jiffy = drv_state.current_speed->lpj; +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + spin_lock_init(&acpu_lock); + mutex_init(&drv_state.lock); + + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.max_speed_delta_khz = clkdata->max_speed_delta_khz; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + drv_state.power_collapse_khz = clkdata->power_collapse_khz; + drv_state.wait_for_irq_khz = clkdata->wait_for_irq_khz; + + if (clkdata->mpll_khz) + acpu_mpll->acpu_khz = clkdata->mpll_khz; + + acpuclk_init(); + acpuclk_init_cpufreq_table(); +} diff --git a/arch/arm/mach-msm/board-mahimahi.c b/arch/arm/mach-msm/board-mahimahi.c index ef3ebf2..aedb136 100644 --- a/arch/arm/mach-msm/board-mahimahi.c +++ b/arch/arm/mach-msm/board-mahimahi.c @@ -48,8 +48,32 @@ static struct platform_device *devices[] __initdata = { &msm_device_nand, }; +static struct msm_acpu_clock_platform_data mahimahi_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .power_collapse_khz = 245000, + .wait_for_irq_khz = 245000, + .mpll_khz = 245000 +}; + +static struct msm_acpu_clock_platform_data mahimahi_cdma_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .power_collapse_khz = 235930, + .wait_for_irq_khz = 235930, + .mpll_khz = 235930 +}; + + static void __init mahimahi_init(void) { + if (is_cdma_version(system_rev)) + msm_acpu_clock_init(&mahimahi_cdma_clock_data); + else + msm_acpu_clock_init(&mahimahi_clock_data); + platform_add_devices(devices, ARRAY_SIZE(devices)); } diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h index 6abf4a6..44efaf5 100644 --- a/arch/arm/mach-msm/include/mach/board.h +++ b/arch/arm/mach-msm/include/mach/board.h @@ -27,6 +27,7 @@ struct msm_acpu_clock_platform_data uint32_t acpu_switch_time_us; uint32_t max_speed_delta_khz; uint32_t vdd_switch_time_us; + unsigned long mpll_khz; unsigned long power_collapse_khz; unsigned long wait_for_irq_khz; }; -- 1.7.0.4 -- Sent by a consultant of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.