| /* |
| * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/mutex.h> |
| #include <linux/spinlock.h> |
| #include <linux/errno.h> |
| #include <linux/cpufreq.h> |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| |
| #include <asm/cpu.h> |
| |
| #include <mach/board.h> |
| #include <mach/msm_iomap.h> |
| #include <mach/msm_bus.h> |
| #include <mach/msm_bus_board.h> |
| #include <mach/rpm-regulator.h> |
| |
| #include "acpuclock.h" |
| |
| #define REG_CLKSEL_0 (MSM_APCS_GLB_BASE + 0x08) |
| #define REG_CLKDIV_0 (MSM_APCS_GLB_BASE + 0x0C) |
| #define REG_CLKSEL_1 (MSM_APCS_GLB_BASE + 0x10) |
| #define REG_CLKDIV_1 (MSM_APCS_GLB_BASE + 0x14) |
| #define REG_CLKOUTSEL (MSM_APCS_GLB_BASE + 0x18) |
| |
| #define MAX_VDD_MEM 1150000 |
| |
| enum clk_src { |
| SRC_CXO, |
| SRC_PLL0, |
| SRC_PLL8, |
| SRC_PLL9, |
| NUM_SRC, |
| }; |
| |
| struct src_clock { |
| struct clk *clk; |
| const char *name; |
| }; |
| |
| static struct src_clock clocks[NUM_SRC] = { |
| [SRC_PLL0].name = "pll0", |
| [SRC_PLL8].name = "pll8", |
| [SRC_PLL9].name = "pll9", |
| }; |
| |
| struct clkctl_acpu_speed { |
| bool use_for_scaling; |
| unsigned int khz; |
| int src; |
| unsigned int src_sel; |
| unsigned int src_div; |
| unsigned int vdd_cpu; |
| unsigned int vdd_mem; |
| unsigned int bw_level; |
| }; |
| |
| struct acpuclk_state { |
| struct mutex lock; |
| struct clkctl_acpu_speed *current_speed; |
| }; |
| |
| static struct acpuclk_state drv_state = { |
| .current_speed = &(struct clkctl_acpu_speed){ 0 }, |
| }; |
| |
| /* Instantaneous bandwidth requests in MB/s. */ |
| #define BW_MBPS(_bw) \ |
| { \ |
| .vectors = &(struct msm_bus_vectors){ \ |
| .src = MSM_BUS_MASTER_AMPSS_M0, \ |
| .dst = MSM_BUS_SLAVE_EBI_CH0, \ |
| .ib = (_bw) * 1000000UL, \ |
| .ab = 0, \ |
| }, \ |
| .num_paths = 1, \ |
| } |
| static struct msm_bus_paths bw_level_tbl[] = { |
| [0] = BW_MBPS(152), /* At least 19 MHz on bus. */ |
| [1] = BW_MBPS(368), /* At least 46 MHz on bus. */ |
| [2] = BW_MBPS(552), /* At least 69 MHz on bus. */ |
| [3] = BW_MBPS(736), /* At least 92 MHz on bus. */ |
| [4] = BW_MBPS(1064), /* At least 133 MHz on bus. */ |
| [5] = BW_MBPS(1536), /* At least 192 MHz on bus. */ |
| }; |
| |
| static struct msm_bus_scale_pdata bus_client_pdata = { |
| .usecase = bw_level_tbl, |
| .num_usecases = ARRAY_SIZE(bw_level_tbl), |
| .active_only = 1, |
| .name = "acpuclock", |
| }; |
| |
| static uint32_t bus_perf_client; |
| |
| static struct clkctl_acpu_speed acpu_freq_tbl[] = { |
| { 0, 19200, SRC_CXO, 0, 0, RPM_VREG_CORNER_LOW, 1050000, 0 }, |
| { 1, 138000, SRC_PLL0, 6, 1, RPM_VREG_CORNER_LOW, 1050000, 2 }, |
| { 1, 276000, SRC_PLL0, 6, 0, RPM_VREG_CORNER_NOMINAL, 1050000, 2 }, |
| { 1, 384000, SRC_PLL8, 3, 0, RPM_VREG_CORNER_HIGH, 1150000, 4 }, |
| /* The row below may be changed at runtime depending on hw rev. */ |
| { 1, 440000, SRC_PLL9, 2, 0, RPM_VREG_CORNER_HIGH, 1150000, 4 }, |
| { 0 } |
| }; |
| |
| static void select_clk_source_div(struct clkctl_acpu_speed *s) |
| { |
| static void * __iomem const sel_reg[] = {REG_CLKSEL_0, REG_CLKSEL_1}; |
| static void * __iomem const div_reg[] = {REG_CLKDIV_0, REG_CLKDIV_1}; |
| uint32_t next_bank; |
| |
| next_bank = !(readl_relaxed(REG_CLKOUTSEL) & 1); |
| writel_relaxed(s->src_sel, sel_reg[next_bank]); |
| writel_relaxed(s->src_div, div_reg[next_bank]); |
| writel_relaxed(next_bank, REG_CLKOUTSEL); |
| |
| /* Wait for switch to complete. */ |
| mb(); |
| udelay(1); |
| } |
| |
| /* Update the bus bandwidth request. */ |
| static void set_bus_bw(unsigned int bw) |
| { |
| int ret; |
| |
| /* Bounds check. */ |
| if (bw >= ARRAY_SIZE(bw_level_tbl)) { |
| pr_err("invalid bandwidth request (%d)\n", bw); |
| return; |
| } |
| |
| /* Update bandwidth if request has changed. This may sleep. */ |
| ret = msm_bus_scale_client_update_request(bus_perf_client, bw); |
| if (ret) |
| pr_err("bandwidth request failed (%d)\n", ret); |
| |
| return; |
| } |
| |
| /* Apply any per-cpu voltage increases. */ |
| static int increase_vdd(unsigned int vdd_cpu, unsigned int vdd_mem) |
| { |
| int rc = 0; |
| |
| /* |
| * Increase vdd_mem active-set before vdd_cpu. |
| * vdd_mem should be >= vdd_cpu. |
| */ |
| rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1, |
| vdd_mem, MAX_VDD_MEM, 0); |
| if (rc) { |
| pr_err("vdd_mem increase failed (%d)\n", rc); |
| return rc; |
| } |
| |
| rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_VDD_DIG_CORNER, |
| RPM_VREG_VOTER1, vdd_cpu, RPM_VREG_CORNER_HIGH, 0); |
| if (rc) |
| pr_err("vdd_cpu increase failed (%d)\n", rc); |
| |
| return rc; |
| } |
| |
| /* Apply any per-cpu voltage decreases. */ |
| static void decrease_vdd(unsigned int vdd_cpu, unsigned int vdd_mem) |
| { |
| int ret; |
| |
| /* Update CPU voltage. */ |
| ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_VDD_DIG_CORNER, |
| RPM_VREG_VOTER1, vdd_cpu, RPM_VREG_CORNER_HIGH, 0); |
| |
| if (ret) { |
| pr_err("vdd_cpu decrease failed (%d)\n", ret); |
| return; |
| } |
| |
| /* |
| * Decrease vdd_mem active-set after vdd_cpu. |
| * vdd_mem should be >= vdd_cpu. |
| */ |
| ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1, |
| vdd_mem, MAX_VDD_MEM, 0); |
| if (ret) |
| pr_err("vdd_mem decrease failed (%d)\n", ret); |
| } |
| |
| static int acpuclk_9615_set_rate(int cpu, unsigned long rate, |
| enum setrate_reason reason) |
| { |
| struct clkctl_acpu_speed *tgt_s, *strt_s; |
| int rc = 0; |
| |
| if (reason == SETRATE_CPUFREQ) |
| mutex_lock(&drv_state.lock); |
| |
| strt_s = drv_state.current_speed; |
| |
| /* Return early if rate didn't change. */ |
| if (rate == strt_s->khz) |
| goto out; |
| |
| /* Find target frequency. */ |
| for (tgt_s = acpu_freq_tbl; tgt_s->khz != 0; tgt_s++) |
| if (tgt_s->khz == rate) |
| break; |
| if (tgt_s->khz == 0) { |
| rc = -EINVAL; |
| goto out; |
| } |
| |
| /* Increase VDD levels if needed. */ |
| if ((reason == SETRATE_CPUFREQ || reason == SETRATE_INIT) |
| && (tgt_s->khz > strt_s->khz)) { |
| rc = increase_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem); |
| if (rc) |
| goto out; |
| } |
| |
| pr_debug("Switching from CPU rate %u KHz -> %u KHz\n", |
| strt_s->khz, tgt_s->khz); |
| |
| /* Switch CPU speed. */ |
| clk_enable(clocks[tgt_s->src].clk); |
| select_clk_source_div(tgt_s); |
| clk_disable(clocks[strt_s->src].clk); |
| |
| drv_state.current_speed = tgt_s; |
| pr_debug("CPU speed change complete\n"); |
| |
| /* Nothing else to do for SWFI or power-collapse. */ |
| if (reason == SETRATE_SWFI || reason == SETRATE_PC) |
| goto out; |
| |
| /* Update bus bandwith request. */ |
| set_bus_bw(tgt_s->bw_level); |
| |
| /* Drop VDD levels if we can. */ |
| if (tgt_s->khz < strt_s->khz) |
| decrease_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem); |
| |
| out: |
| if (reason == SETRATE_CPUFREQ) |
| mutex_unlock(&drv_state.lock); |
| return rc; |
| } |
| |
| static unsigned long acpuclk_9615_get_rate(int cpu) |
| { |
| return drv_state.current_speed->khz; |
| } |
| |
| #ifdef CONFIG_CPU_FREQ_MSM |
| static struct cpufreq_frequency_table freq_table[30]; |
| |
| static void __init cpufreq_table_init(void) |
| { |
| int i, freq_cnt = 0; |
| |
| /* Construct the freq_table tables from acpu_freq_tbl. */ |
| for (i = 0; acpu_freq_tbl[i].khz != 0 |
| && freq_cnt < ARRAY_SIZE(freq_table); i++) { |
| if (acpu_freq_tbl[i].use_for_scaling) { |
| freq_table[freq_cnt].index = freq_cnt; |
| freq_table[freq_cnt].frequency |
| = acpu_freq_tbl[i].khz; |
| freq_cnt++; |
| } |
| } |
| /* freq_table not big enough to store all usable freqs. */ |
| BUG_ON(acpu_freq_tbl[i].khz != 0); |
| |
| freq_table[freq_cnt].index = freq_cnt; |
| freq_table[freq_cnt].frequency = CPUFREQ_TABLE_END; |
| |
| pr_info("CPU: %d scaling frequencies supported.\n", freq_cnt); |
| |
| /* Register table with CPUFreq. */ |
| cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); |
| } |
| #else |
| static void __init cpufreq_table_init(void) {} |
| #endif |
| |
| static struct acpuclk_data acpuclk_9615_data = { |
| .set_rate = acpuclk_9615_set_rate, |
| .get_rate = acpuclk_9615_get_rate, |
| .power_collapse_khz = 19200, |
| .wait_for_irq_khz = 19200, |
| }; |
| |
| static int __init acpuclk_9615_probe(struct platform_device *pdev) |
| { |
| unsigned long max_cpu_khz = 0; |
| int i; |
| |
| mutex_init(&drv_state.lock); |
| |
| bus_perf_client = msm_bus_scale_register_client(&bus_client_pdata); |
| if (!bus_perf_client) { |
| pr_err("Unable to register bus client\n"); |
| BUG(); |
| } |
| |
| for (i = 0; i < NUM_SRC; i++) { |
| if (clocks[i].name) { |
| clocks[i].clk = clk_get_sys("acpu", clocks[i].name); |
| BUG_ON(IS_ERR(clocks[i].clk)); |
| /* |
| * Prepare the PLLs because we enable/disable them |
| * in atomic context during power collapse/restore. |
| */ |
| BUG_ON(clk_prepare(clocks[i].clk)); |
| } |
| } |
| |
| /* Determine the rate of PLL9 and fixup tables accordingly */ |
| if (clk_get_rate(clocks[SRC_PLL9].clk) == 550000000) { |
| for (i = 0; i < ARRAY_SIZE(acpu_freq_tbl); i++) |
| if (acpu_freq_tbl[i].src == SRC_PLL9) { |
| acpu_freq_tbl[i].khz = 550000; |
| acpu_freq_tbl[i].bw_level = 5; |
| } |
| } |
| |
| /* Improve boot time by ramping up CPU immediately. */ |
| for (i = 0; acpu_freq_tbl[i].khz != 0; i++) |
| max_cpu_khz = acpu_freq_tbl[i].khz; |
| acpuclk_9615_set_rate(smp_processor_id(), max_cpu_khz, SETRATE_INIT); |
| |
| acpuclk_register(&acpuclk_9615_data); |
| cpufreq_table_init(); |
| |
| return 0; |
| } |
| |
| static struct platform_driver acpuclk_9615_driver = { |
| .driver = { |
| .name = "acpuclk-9615", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init acpuclk_9615_init(void) |
| { |
| return platform_driver_probe(&acpuclk_9615_driver, acpuclk_9615_probe); |
| } |
| device_initcall(acpuclk_9615_init); |