| /* |
| * arch/arm/plat-ambarella/cortex/cpufreq.c |
| * |
| * Author: Cao Rongrong <rrcao@ambarella.com> |
| * |
| * Copyright (C) 2004-2010, Ambarella, Inc. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/cpufreq.h> |
| #include <mach/hardware.h> |
| #include <hal/hal.h> |
| #include <plat/cpufreq.h> |
| |
| static struct ambarella_cpufreq_config *cpufreq_config = NULL; |
| |
| static int ambarella_verify_speed(struct cpufreq_policy *policy) |
| { |
| return cpufreq_frequency_table_verify(policy, cpufreq_config->freq_table); |
| } |
| |
| static unsigned int ambarella_getspeed(unsigned int cpu) |
| { |
| if (cpu >= NR_CPUS) |
| return 0; |
| |
| return amb_get_cortex_clock_frequency(HAL_BASE_VP) / 1000; |
| } |
| |
| static int ambarella_target(struct cpufreq_policy *policy, |
| unsigned int target_freq, |
| unsigned int relation) |
| { |
| int idx; |
| amb_hal_success_t ret = AMB_HAL_SUCCESS; |
| struct cpufreq_freqs freqs; |
| |
| cpufreq_frequency_table_target(policy, cpufreq_config->freq_table, |
| target_freq, relation, &idx); |
| |
| freqs.old = ambarella_getspeed(0); |
| freqs.new = cpufreq_config->freq_table[idx].frequency; |
| freqs.cpu = policy->cpu; |
| |
| if (freqs.old == freqs.new) |
| return 0; |
| |
| cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); |
| |
| #ifdef CONFIG_CPU_FREQ_DEBUG |
| printk(KERN_DEBUG "cpufreq-ambarella: transition: %u --> %u\n", |
| freqs.old, freqs.new); |
| #endif |
| |
| /* if moving to higher frequency, up the voltage beforehand */ |
| if (cpufreq_config && cpufreq_config->set_voltage && freqs.new > freqs.old) { |
| ret = cpufreq_config->set_voltage(idx); |
| if (ret) |
| goto out; |
| } |
| |
| ret = amb_set_cortex_clock_frequency(HAL_BASE_VP, freqs.new * 1000); |
| if (ret != AMB_HAL_SUCCESS) { |
| pr_err("cpu-ambarella: Failed to set cpu frequency to %d kHz\n", |
| freqs.new); |
| return ret; |
| } |
| |
| out: |
| cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); |
| |
| return 0; |
| } |
| |
| static int ambarella_cpu_init(struct cpufreq_policy *policy) |
| { |
| if (policy->cpu >= NR_CPUS) |
| return -EINVAL; |
| |
| cpufreq_frequency_table_cpuinfo(policy, cpufreq_config->freq_table); |
| cpufreq_frequency_table_get_attr(cpufreq_config->freq_table, policy->cpu); |
| |
| policy->cur = ambarella_getspeed(policy->cpu); |
| /* FIXME: what's the actual transition time? */ |
| policy->cpuinfo.transition_latency = 300 * 1000; |
| policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; |
| |
| /* the frequency cannot be set independently. Each cpu is bound to the |
| * same speed. So the affected cpu is all of the cpus. */ |
| cpumask_setall(policy->cpus); |
| |
| return 0; |
| } |
| |
| static int ambarella_cpu_exit(struct cpufreq_policy *policy) |
| { |
| cpufreq_frequency_table_put_attr(policy->cpu); |
| return 0; |
| } |
| |
| static struct freq_attr *ambarella_cpufreq_attr[] = { |
| &cpufreq_freq_attr_scaling_available_freqs, |
| NULL, |
| }; |
| |
| static struct cpufreq_driver ambarella_driver = { |
| .verify = ambarella_verify_speed, |
| .target = ambarella_target, |
| .get = ambarella_getspeed, |
| .init = ambarella_cpu_init, |
| .exit = ambarella_cpu_exit, |
| .name = "ambarella", |
| .attr = ambarella_cpufreq_attr, |
| }; |
| |
| static int __init ambarella_cpufreq_probe(struct platform_device *pdev) |
| { |
| int rval; |
| |
| cpufreq_config = pdev->dev.platform_data; |
| |
| if (!cpufreq_config) |
| return -EINVAL; |
| if (!cpufreq_config->freq_table) |
| return -EINVAL; |
| |
| if (cpufreq_config->init) { |
| rval = cpufreq_config->init(); |
| if (rval) |
| return rval; |
| } |
| |
| return cpufreq_register_driver(&ambarella_driver); |
| } |
| |
| static int __exit ambarella_cpufreq_remove(struct platform_device *pdev) |
| { |
| if (cpufreq_config && cpufreq_config->exit) |
| cpufreq_config->exit(); |
| |
| return cpufreq_unregister_driver(&ambarella_driver); |
| } |
| |
| static struct platform_driver ambarella_cpufreq_driver = { |
| .driver = { |
| .name = "cpufreq-ambarella", |
| .owner = THIS_MODULE, |
| }, |
| .remove = __exit_p(ambarella_cpufreq_remove), |
| }; |
| |
| static int __init ambarella_cpufreq_init(void) |
| { |
| return platform_driver_probe(&ambarella_cpufreq_driver, |
| ambarella_cpufreq_probe); |
| } |
| late_initcall(ambarella_cpufreq_init); |
| |