blob: 20b5c0837e9a7c01d253723675d448527919c409 [file] [log] [blame]
/*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* EXYNOS - MIF clock frequency scaling support in DEVFREQ framework
* This version supports EXYNOS5250 only. This changes bus frequencies.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/suspend.h>
#include <linux/opp.h>
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/module.h>
#include <linux/pm_qos.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/asv-exynos.h>
#include <mach/abb-exynos.h>
#include <mach/smc.h>
#include <mach/regs-mem.h>
#include <mach/exynos5_bus.h>
#include "governor.h"
#define MAX_SAFEVOLT 1200000 /* 1.2V */
#define MIF_ABB_CONTROL_FREQUENCY 667000000
#define MIF_UPPER_FREQUENCY 667000
/* Assume that the bus is saturated if the utilization is 20% */
#define MIF_BUS_SATURATION_RATIO 20
enum mif_level_idx {
LV_0,
LV_1,
LV_2,
LV_3,
LV_4,
_LV_END
};
struct busfreq_data_mif {
struct device *dev;
struct devfreq *devfreq;
bool disabled;
struct regulator *vdd_mif;
unsigned long curr_freq;
unsigned long curr_volt;
unsigned long suspend_freq;
struct mutex lock;
struct clk *mif_clk;
struct clk *mclk_cdrex;
struct clk *mout_mpll;
struct clk *mout_bpll;
};
struct mif_bus_opp_table {
unsigned int idx;
unsigned long clk;
unsigned long volt;
unsigned long dmc_timingrow;
};
static struct mif_bus_opp_table exynos5_mif_opp_table[] = {
{LV_0, 800000, 1000000, 0x34498692},
{LV_1, 667000, 1000000, 0x2c48758f},
{LV_2, 400000, 1000000, 0x1A255349},
{LV_3, 160000, 1000000, 0x1A255349},
{LV_4, 100000, 1000000, 0x1A255349},
{0, 0, 0, 0},
};
struct exynos5_bus_mif_handle {
struct list_head node;
unsigned long min;
};
struct exynos5_bus_mif_handle *mif_min_hd;
static struct busfreq_data_mif *exynos5_bus_mif_data;
static DEFINE_MUTEX(exynos5_bus_mif_data_lock);
static LIST_HEAD(exynos5_bus_mif_requests);
static DEFINE_MUTEX(exynos5_bus_mif_requests_lock);
static DEFINE_MUTEX(exynos5_bus_mif_upper_freq_lock);
static bool multiple_windows = false;
static unsigned int used_dev_cnt = 0;
static void exynos5_mif_check_upper_freq(void)
{
if (multiple_windows) {
if (!mif_min_hd) {
mif_min_hd = exynos5_bus_mif_min(MIF_UPPER_FREQUENCY);
if (!mif_min_hd)
pr_err("%s: Failed to request min_freq\n", __func__);
}
} else {
if (mif_min_hd) {
exynos5_bus_mif_put(mif_min_hd);
mif_min_hd = NULL;
}
}
}
void exynos5_mif_multiple_windows(bool state)
{
mutex_lock(&exynos5_bus_mif_upper_freq_lock);
multiple_windows = state;
exynos5_mif_check_upper_freq();
mutex_unlock(&exynos5_bus_mif_upper_freq_lock);
}
void exynos5_mif_used_dev(bool power_on)
{
mutex_lock(&exynos5_bus_mif_upper_freq_lock);
if (power_on)
used_dev_cnt++;
else if (used_dev_cnt > 0)
used_dev_cnt--;
exynos5_mif_check_upper_freq();
mutex_unlock(&exynos5_bus_mif_upper_freq_lock);
}
static int exynos5_mif_setvolt(struct busfreq_data_mif *data, unsigned long volt)
{
return regulator_set_voltage(data->vdd_mif, volt, MAX_SAFEVOLT);
}
static int exynos5_mif_set_dmc_timing(unsigned int freq)
{
int index;
unsigned int timing0 = 0;
unsigned int timing1 = 0;
for (index = LV_0; index < _LV_END; index++) {
if (freq == exynos5_mif_opp_table[index].clk)
break;
}
if (index == _LV_END)
return -EINVAL;
#ifdef CONFIG_ARM_TRUSTZONE
exynos_smc_read_sfr(SMC_CMD_REG,
SMC_REG_ID_SFR_R(EXYNOS5_PA_DREXII +
EXYNOS_DMC_TIMINGROW_OFFSET),
&timing0, 0);
timing0 |= exynos5_mif_opp_table[index].dmc_timingrow;
timing1 = exynos5_mif_opp_table[index].dmc_timingrow;
exynos_smc(SMC_CMD_REG,
SMC_REG_ID_SFR_W(EXYNOS5_PA_DREXII +
EXYNOS_DMC_TIMINGROW_OFFSET),
timing0, 0);
exynos_smc(SMC_CMD_REG,
SMC_REG_ID_SFR_W(EXYNOS5_PA_DREXII +
EXYNOS_DMC_TIMINGROW_OFFSET),
timing1, 0);
#else
timing0 = __raw_readl(S5P_VA_DREXII +
EXYNOS_DMC_TIMINGROW_OFFSET);
timing0 |= exynos5_mif_opp_table[index].dmc_timingrow;
timing1 = exynos5_mif_opp_table[index].dmc_timingrow;
__raw_writel(timing0, S5P_VA_DREXII + EXYNOS_DMC_TIMINGROW_OFFSET);
__raw_writel(timing1, S5P_VA_DREXII + EXYNOS_DMC_TIMINGROW_OFFSET);
#endif
return 0;
}
static int exynos5_mif_setclk(struct busfreq_data_mif *data,
unsigned long new_freq)
{
unsigned err = 0;
struct clk *old_p;
struct clk *new_p;
unsigned long old_p_rate;
unsigned long new_p_rate;
int div;
/*
* Dynamic ABB control according to MIF frequency
* MIF frquency > 667 MHz : ABB_MODE_130V
* MIF frquency <= 667 MHz : ABB_MODE_BYPASS
*/
if (new_freq > MIF_ABB_CONTROL_FREQUENCY)
set_abb_member(ABB_MIF, ABB_MODE_130V);
old_p = clk_get_parent(data->mclk_cdrex);
if (IS_ERR(old_p))
return PTR_ERR(old_p);
old_p_rate = clk_get_rate(old_p);
div = DIV_ROUND_CLOSEST(old_p_rate, new_freq);
if (abs(DIV_ROUND_UP(old_p_rate, div) - new_freq) > 1000000) {
new_p = (old_p == data->mout_bpll) ? data->mout_mpll :
data->mout_bpll;
new_p_rate = clk_get_rate(new_p);
if (new_p_rate > old_p_rate) {
/*
* Needs to change to faster pll. Change the divider
* first, then switch to the new pll. This only works
* because the set_rate op on mif_clk doesn't know
* anything about its current parent, it just applies
* the dividers assuming the right pll has been selected
* for the requested frequency.
*/
err = clk_set_rate(data->mif_clk, new_freq);
if (err) {
pr_info("clk_set_rate error %d\n", err);
goto out;
}
err = clk_set_parent(data->mclk_cdrex, new_p);
if (err) {
pr_info("clk_set_parent error %d\n", err);
goto out;
}
} else {
/*
* Needs to change to a slower pll. Switch to the new
* pll first, then apply the new divider.
*/
err = clk_set_parent(data->mclk_cdrex, new_p);
if (err) {
pr_info("clk_set_parent error %d\n", err);
goto out;
}
err = clk_set_rate(data->mif_clk, new_freq);
if (err) {
pr_info("clk_set_rate error %d\n", err);
goto out;
}
}
} else {
/* No need to change pll */
err = clk_set_rate(data->mif_clk, new_freq);
}
if (new_freq <= MIF_ABB_CONTROL_FREQUENCY)
set_abb_member(ABB_MIF, ABB_MODE_BYPASS);
out:
return err;
}
static int exynos5_busfreq_mif_target(struct device *dev, unsigned long *_freq,
u32 flags)
{
int err = 0;
struct platform_device *pdev = container_of(dev, struct platform_device,
dev);
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
struct opp *opp;
unsigned long old_freq, freq;
unsigned long volt;
struct exynos5_bus_mif_handle *handle;
mutex_lock(&exynos5_bus_mif_requests_lock);
list_for_each_entry(handle, &exynos5_bus_mif_requests, node) {
if (handle->min > *_freq) {
*_freq = handle->min;
flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND;
}
}
mutex_unlock(&exynos5_bus_mif_requests_lock);
mutex_lock(&data->lock);
if (data->devfreq->max_freq && *_freq > data->devfreq->max_freq) {
*_freq = data->devfreq->max_freq;
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND;
}
rcu_read_lock();
opp = devfreq_recommended_opp(dev, _freq, flags);
if (IS_ERR(opp)) {
rcu_read_unlock();
dev_err(dev, "%s: Invalid OPP.\n", __func__);
err = PTR_ERR(opp);
goto out;
}
freq = opp_get_freq(opp);
volt = opp_get_voltage(opp);
rcu_read_unlock();
old_freq = data->curr_freq;
if (old_freq == freq)
goto out;
dev_dbg(dev, "targetting %lukHz %luuV\n", freq, volt);
if (data->disabled)
goto out;
if (old_freq < freq) {
err = exynos5_mif_setvolt(data, volt);
if (err)
goto out;
err = exynos5_mif_set_dmc_timing(freq);
if (err)
goto out;
}
err = exynos5_mif_setclk(data, freq * 1000);
if (err)
goto out;
if (old_freq > freq) {
err = exynos5_mif_set_dmc_timing(freq);
if (err)
goto out;
err = exynos5_mif_setvolt(data, volt);
if (err)
goto out;
}
data->curr_freq = freq;
data->curr_volt = volt;
out:
mutex_unlock(&data->lock);
return err;
}
int exynos5_bus_mif_update(struct exynos5_bus_mif_handle *handle,
unsigned long min_freq)
{
mutex_lock(&exynos5_bus_mif_requests_lock);
handle->min = min_freq;
mutex_unlock(&exynos5_bus_mif_requests_lock);
mutex_lock(&exynos5_bus_mif_data_lock);
if (exynos5_bus_mif_data) {
mutex_lock(&exynos5_bus_mif_data->devfreq->lock);
update_devfreq(exynos5_bus_mif_data->devfreq);
mutex_unlock(&exynos5_bus_mif_data->devfreq->lock);
}
mutex_unlock(&exynos5_bus_mif_data_lock);
return 0;
}
struct exynos5_bus_mif_handle *exynos5_bus_mif_get(unsigned long min_freq)
{
struct exynos5_bus_mif_handle *handle;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return NULL;
handle->min = min_freq;
mutex_lock(&exynos5_bus_mif_requests_lock);
list_add_tail(&handle->node, &exynos5_bus_mif_requests);
mutex_unlock(&exynos5_bus_mif_requests_lock);
mutex_lock(&exynos5_bus_mif_data_lock);
if (exynos5_bus_mif_data) {
mutex_lock(&exynos5_bus_mif_data->devfreq->lock);
update_devfreq(exynos5_bus_mif_data->devfreq);
mutex_unlock(&exynos5_bus_mif_data->devfreq->lock);
}
mutex_unlock(&exynos5_bus_mif_data_lock);
return handle;
}
int exynos5_bus_mif_put(struct exynos5_bus_mif_handle *handle)
{
int ret = 0;
mutex_lock(&exynos5_bus_mif_requests_lock);
list_del(&handle->node);
mutex_unlock(&exynos5_bus_mif_requests_lock);
mutex_lock(&exynos5_bus_mif_data_lock);
if (exynos5_bus_mif_data) {
mutex_lock(&exynos5_bus_mif_data->devfreq->lock);
update_devfreq(exynos5_bus_mif_data->devfreq);
mutex_unlock(&exynos5_bus_mif_data->devfreq->lock);
}
mutex_unlock(&exynos5_bus_mif_data_lock);
kfree(handle);
return ret;
}
static void exynos5_mif_exit(struct device *dev)
{
struct platform_device *pdev = container_of(dev, struct platform_device,
dev);
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
devfreq_unregister_opp_notifier(dev, data->devfreq);
}
static struct devfreq_dev_profile exynos5_devfreq_mif_profile = {
.initial_freq = 400000,
.target = exynos5_busfreq_mif_target,
.exit = exynos5_mif_exit,
};
static int exynos5250_init_mif_tables(struct busfreq_data_mif *data)
{
int i, err = 0;
for (i = LV_0; i < _LV_END; i++) {
exynos5_mif_opp_table[i].volt = asv_get_volt(ID_MIF, exynos5_mif_opp_table[i].clk);
if (exynos5_mif_opp_table[i].volt == 0) {
dev_err(data->dev, "Invalid value for frequency %lu\n",
exynos5_mif_opp_table[i].clk);
continue;
}
err = opp_add(data->dev, exynos5_mif_opp_table[i].clk,
exynos5_mif_opp_table[i].volt);
if (err) {
dev_err(data->dev, "Cannot add opp entries.\n");
return err;
}
}
return 0;
}
static int exynos5_bus_mif_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct opp *max_opp;
struct opp *opp;
unsigned long maxfreq = ULONG_MAX;
unsigned long volt;
unsigned long freq;
int err = 0;
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
/*
* Set the frequency to the maximum enabled frequency, but set the
* voltage to the maximum possible voltage in case the bootloader
* sets the frequency to maximum during resume. Frequency can only
* go up, so set voltage and timing before clock.
*/
mutex_lock(&data->lock);
data->disabled = true;
rcu_read_lock();
max_opp = opp_find_freq_floor(data->dev, &maxfreq);
if (IS_ERR(max_opp)) {
rcu_read_unlock();
err = PTR_ERR(max_opp);
goto unlock;
}
maxfreq = ULONG_MAX;
if (data->devfreq->max_freq)
maxfreq = data->devfreq->max_freq;
opp = opp_find_freq_floor(data->dev, &maxfreq);
if (IS_ERR(opp)) {
rcu_read_unlock();
err = PTR_ERR(opp);
goto unlock;
}
freq = opp_get_freq(opp);
volt = opp_get_voltage(max_opp);
rcu_read_unlock();
err = exynos5_mif_setvolt(data, volt);
if (err)
goto unlock;
err = exynos5_mif_set_dmc_timing(freq);
if (err)
goto unlock;
err = exynos5_mif_setclk(data, freq * 1000);
if (err)
goto unlock;
data->suspend_freq = freq;
unlock:
mutex_unlock(&data->lock);
return err;
}
static int exynos5_bus_mif_resume_noirq(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
int err = 0;
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
/*
* Set the frequency to the maximum enabled frequency in case the
* bootloader raised it during resume. Frequency can only go down,
* so set timing after updating clock.
*/
mutex_lock(&data->lock);
err = exynos5_mif_set_dmc_timing(data->suspend_freq);
if (err)
goto unlock;
err = exynos5_mif_setclk(data, data->suspend_freq * 1000);
if (err)
goto unlock;
unlock:
mutex_unlock(&data->lock);
return err;
}
static int exynos5_bus_mif_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
int err = 0;
/*
* Restore the frequency and voltage to the values when suspend was
* started. Frequency can only go down, so set timing and voltage
* after updating clock.
*/
mutex_lock(&data->lock);
data->disabled = false;
err = exynos5_mif_setclk(data, data->curr_freq * 1000);
if (err)
goto unlock;
err = exynos5_mif_set_dmc_timing(data->curr_freq);
if (err)
goto unlock;
err = exynos5_mif_setvolt(data, data->curr_volt);
if (err)
goto unlock;
unlock:
mutex_unlock(&data->lock);
return err;
}
static struct devfreq_pm_qos_data exynos5_devfreq_mif_pm_qos_data = {
.bytes_per_sec_per_hz = 8,
.pm_qos_class = PM_QOS_MEMORY_THROUGHPUT,
};
static __devinit int exynos5_busfreq_mif_probe(struct platform_device *pdev)
{
struct busfreq_data_mif *data;
struct opp *opp;
struct device *dev = &pdev->dev;
unsigned long initial_freq;
unsigned long initial_volt;
int err = 0;
struct exynos5_bus_mif_platform_data *pdata = pdev->dev.platform_data;
data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_mif), GFP_KERNEL);
if (data == NULL) {
dev_err(dev, "Cannot allocate memory.\n");
return -ENOMEM;
}
data->dev = dev;
mutex_init(&data->lock);
err = exynos5250_init_mif_tables(data);
if (err)
goto err_regulator;
data->vdd_mif = regulator_get(dev, "vdd_mif");
if (IS_ERR(data->vdd_mif)) {
dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n");
err = PTR_ERR(data->vdd_mif);
goto err_regulator;
}
data->mif_clk = clk_get(dev, "mif_clk");
if (IS_ERR(data->mif_clk)) {
dev_err(dev, "Cannot get clock \"mif_clk\"\n");
err = PTR_ERR(data->mif_clk);
goto err_clock;
}
data->mclk_cdrex = clk_get(dev, "mclk_cdrex");
if (IS_ERR(data->mclk_cdrex)) {
dev_err(dev, "Cannot get clock \"mclk_crex\"\n");
err = PTR_ERR(data->mclk_cdrex);
goto err_mclk_cdrex;
}
data->mout_mpll = clk_get(dev, "mout_mpll");
if (IS_ERR(data->mout_mpll)) {
dev_err(dev, "Cannot get clock \"mout_mpll\"\n");
err = PTR_ERR(data->mout_mpll);
goto err_mout_mpll;
}
data->mout_bpll = clk_get(dev, "mout_bpll");
if (IS_ERR(data->mout_bpll)) {
dev_err(dev, "Cannot get clock \"mout_bpll\"\n");
err = PTR_ERR(data->mout_bpll);
goto err_mout_bpll;
}
if (pdata && pdata->max_freq)
exynos5_devfreq_mif_profile.initial_freq = pdata->max_freq;
rcu_read_lock();
opp = opp_find_freq_floor(dev, &exynos5_devfreq_mif_profile.initial_freq);
if (IS_ERR(opp)) {
rcu_read_unlock();
dev_err(dev, "Invalid initial frequency %lu kHz.\n",
exynos5_devfreq_mif_profile.initial_freq);
err = PTR_ERR(opp);
goto err_opp_add;
}
initial_freq = opp_get_freq(opp);
initial_volt = opp_get_voltage(opp);
rcu_read_unlock();
data->curr_freq = initial_freq;
data->curr_volt = initial_volt;
err = exynos5_mif_setclk(data, initial_freq * 1000);
if (err) {
dev_err(dev, "Failed to set initial frequency\n");
goto err_opp_add;
}
err = exynos5_mif_set_dmc_timing(initial_freq);
if (err) {
dev_err(dev, "Failed to set dmc timingrow\n");
goto err_opp_add;
}
err = exynos5_mif_setvolt(data, initial_volt);
if (err)
goto err_opp_add;
platform_set_drvdata(pdev, data);
data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_mif_profile,
&devfreq_pm_qos,
&exynos5_devfreq_mif_pm_qos_data);
if (IS_ERR(data->devfreq)) {
err = PTR_ERR(data->devfreq);
goto err_devfreq_add;
}
if (pdata && pdata->max_freq)
data->devfreq->max_freq = pdata->max_freq;
devfreq_register_opp_notifier(dev, data->devfreq);
mutex_lock(&exynos5_bus_mif_data_lock);
exynos5_bus_mif_data = data;
mutex_unlock(&exynos5_bus_mif_data_lock);
return 0;
err_devfreq_add:
devfreq_remove_device(data->devfreq);
platform_set_drvdata(pdev, NULL);
err_opp_add:
clk_put(data->mout_bpll);
err_mout_bpll:
clk_put(data->mout_mpll);
err_mout_mpll:
clk_put(data->mclk_cdrex);
err_mclk_cdrex:
clk_put(data->mif_clk);
err_clock:
regulator_put(data->vdd_mif);
err_regulator:
return err;
}
static __devexit int exynos5_busfreq_mif_remove(struct platform_device *pdev)
{
struct busfreq_data_mif *data = platform_get_drvdata(pdev);
mutex_lock(&exynos5_bus_mif_data_lock);
exynos5_bus_mif_data = NULL;
mutex_unlock(&exynos5_bus_mif_data_lock);
devfreq_remove_device(data->devfreq);
regulator_put(data->vdd_mif);
clk_put(data->mif_clk);
clk_put(data->mclk_cdrex);
clk_put(data->mout_mpll);
clk_put(data->mout_bpll);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct dev_pm_ops exynos5_bus_mif_pm_ops = {
.suspend = exynos5_bus_mif_suspend,
.resume_noirq = exynos5_bus_mif_resume_noirq,
.resume = exynos5_bus_mif_resume,
};
static struct platform_driver exynos5_busfreq_mif_driver = {
.probe = exynos5_busfreq_mif_probe,
.remove = __devexit_p(exynos5_busfreq_mif_remove),
.driver = {
.name = "exynos5-bus-mif",
.owner = THIS_MODULE,
.pm = &exynos5_bus_mif_pm_ops,
},
};
static int __init exynos5_busfreq_mif_init(void)
{
return platform_driver_register(&exynos5_busfreq_mif_driver);
}
late_initcall(exynos5_busfreq_mif_init);
static void __exit exynos5_busfreq_mif_exit(void)
{
platform_driver_unregister(&exynos5_busfreq_mif_driver);
}
module_exit(exynos5_busfreq_mif_exit);