2529 lines
62 KiB
C
2529 lines
62 KiB
C
/*
|
|
* drivers/devfreq/dramfreq/sunxi-ddrfreq.c
|
|
*
|
|
* Copyright(c) 2013-2015 Allwinnertech Co., Ltd.
|
|
*
|
|
* Author: Pan Nan <pannan@allwinnertech.com>
|
|
*
|
|
* SUNXI ddr frequency dynamic scaling driver
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/tlbflush.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/devfreq.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/suspend.h>
|
|
#if defined(CONFIG_ARCH_SUN8IW7P1) && defined(CONFIG_CPU_BUDGET_THERMAL)
|
|
#include <linux/cpu_budget_cooling.h>
|
|
#endif
|
|
#include "sunxi-mdfs.h"
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1)
|
|
#define PLL_DDR0_CLK "pll_ddr0"
|
|
#define PLL_DDR1_CLK "pll_ddr1"
|
|
#define SUNXI_CCM_PBASE (0x01c20000)
|
|
#define SUNXI_CCM_SIZE (0x2dc)
|
|
static void __iomem *sunxi_ccm_vbase;
|
|
#endif
|
|
|
|
#define AHB1_CLK "ahb1"
|
|
#define CCI400_CLK "cci400"
|
|
#define PLL_DDR_CLK "pll_ddr"
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1) || defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
extern char __sram_start, __sram_end, __sram_text_start, __sram_data_end;
|
|
#endif
|
|
|
|
enum {
|
|
DEBUG_FREQ = 1U << 0,
|
|
DEBUG_SUSPEND = 1U << 1,
|
|
};
|
|
static int debug_mask = DEBUG_FREQ | DEBUG_SUSPEND;
|
|
module_param(debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
#define DDRFREQ_DBG(mask, format, args...) \
|
|
do { if (mask & debug_mask) printk("[ddrfreq] "format, ##args); } while (0)
|
|
#define DDRFREQ_ERR(format, args...) \
|
|
printk(KERN_ERR "[ddrfreq] ERR:"format, ##args)
|
|
|
|
#define MDFS_RETRIES (10)
|
|
|
|
static DEFINE_MUTEX(ddrfreq_lock);
|
|
static unsigned int ddrfreq_enable;
|
|
static __dram_para_t dram_para;
|
|
static struct devfreq *this_df;
|
|
static unsigned long long setfreq_time_usecs;
|
|
static unsigned long long getfreq_time_usecs;
|
|
unsigned int sunxi_ddrfreq_max;
|
|
unsigned int sunxi_ddrfreq_min;
|
|
|
|
#ifdef CONFIG_SMP
|
|
static struct cpumask ipi_mask;
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
static struct clk *clk_pll4;
|
|
static struct clk *clk_pll6;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW8P1)
|
|
static struct clk *clk_pll_ddr0;
|
|
static struct clk *clk_pll_ddr1;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1)
|
|
#define SUNXI_DDRFREQ_MAXFREQ_MIN (600000)
|
|
#define SUNXI_DDRFREQ_MINFREQ_MIN (168000)
|
|
static struct clk *clk_pll_ddr0;
|
|
static unsigned int ddrfreq_odt_disable;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW7P1)
|
|
static struct clk *clk_pll_ddr0;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW9P1)
|
|
#define SUNXI_DDRFREQ_MAXFREQ_MIN (600000)
|
|
#define SUNXI_DDRFREQ_MINFREQ_MIN (168000)
|
|
static struct clk *clk_pll_ddr0;
|
|
static struct clk *clk_pll_ddr1;
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
static int dram_freq_table[][2] = {
|
|
{ 672 * 1000, 1 },
|
|
{ 480 * 1000, 0 },
|
|
{ 336 * 1000, 2 },
|
|
{ 240 * 1000, 0 },
|
|
{ 168 * 1000, 4 },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
static unsigned int dram_freq_adjust;
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
unsigned int mdfs_in_cfs;
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
/*
|
|
* 0: normal; 1:enter low-power music; 2:exit low-power music
|
|
*/
|
|
static unsigned int ddrfreq_system_state;
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
#define NEXT_VB_TIME_RETRIES (2)
|
|
#define NEXT_VB_TIME_WAIT_US (2000)
|
|
|
|
struct ddrfreq_vb_time_ops {
|
|
int (*get_vb_time) (void);
|
|
int (*get_next_vb_time) (void);
|
|
int (*is_in_vb) (void);
|
|
};
|
|
|
|
struct ddrfreq_vb_time_ops vbtime_ops;
|
|
|
|
int ddrfreq_set_vb_time_ops(struct ddrfreq_vb_time_ops *ops)
|
|
{
|
|
vbtime_ops.get_vb_time = ops->get_vb_time;
|
|
vbtime_ops.get_next_vb_time = ops->get_next_vb_time;
|
|
vbtime_ops.is_in_vb = ops->is_in_vb;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ddrfreq_set_vb_time_ops);
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
static DEFINE_MUTEX(busfreq_lock);
|
|
|
|
struct busfreq_table {
|
|
char *name;
|
|
struct clk *bus_clk;
|
|
unsigned long normal_freq;
|
|
unsigned long idle_freq;
|
|
};
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
static struct busfreq_table busfreq_tbl[] = {
|
|
{ .name = "gtbus", .normal_freq = 400000000, .idle_freq = 300000000 },
|
|
{ .name = "cci400", .normal_freq = 480000000, .idle_freq = 240000000 },
|
|
{ .name = "ahb0", .normal_freq = 120000000, .idle_freq = 75000000 },
|
|
{ .name = "ahb1", .normal_freq = 240000000, .idle_freq = 120000000 },
|
|
{ .name = "ahb2", .normal_freq = 120000000, .idle_freq = 75000000 },
|
|
};
|
|
|
|
static struct clk *gtbus;
|
|
static struct clk *cci400;
|
|
static struct clk *ahb0;
|
|
static struct clk *ahb1;
|
|
static struct clk *ahb2;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
static struct busfreq_table busfreq_tbl[] = {
|
|
{ .name = "ahb1", .normal_freq = 200000000, .idle_freq = 50000000 },
|
|
};
|
|
|
|
static struct clk *ahb1;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1)
|
|
static struct busfreq_table busfreq_tbl[] = {
|
|
{ .name = "cci400", .normal_freq = 480000000, .idle_freq = 240000000 },
|
|
{ .name = "ahb1", .normal_freq = 200000000, .idle_freq = 100000000 },
|
|
};
|
|
|
|
static struct clk *cci400;
|
|
static struct clk *ahb1;
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW9P1)
|
|
static struct busfreq_table busfreq_tbl[] = {
|
|
{ .name = "cci400", .normal_freq = 400000000, .idle_freq = 300000000 },
|
|
{ .name = "ahb1", .normal_freq = 200000000, .idle_freq = 100000000 },
|
|
};
|
|
|
|
static struct clk *cci400;
|
|
static struct clk *ahb1;
|
|
#endif
|
|
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
#define TABLE_LENGTH (8)
|
|
static unsigned int table_length_syscfg;
|
|
static unsigned int last_vdd;
|
|
static struct regulator *vdd_sys;
|
|
struct ddrfreq_dvfs {
|
|
unsigned int freq; /* ddr frequency, based on KHz */
|
|
unsigned int volt; /* voltage for the frequency, based on mv */
|
|
};
|
|
static struct ddrfreq_dvfs dvfs_table_syscfg[TABLE_LENGTH];
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
static int __set_bus_freq(const char *name, struct clk *clk,
|
|
unsigned long target_freq)
|
|
{
|
|
unsigned long cur_freq;
|
|
|
|
mutex_lock(&busfreq_lock);
|
|
|
|
if (clk_prepare_enable(clk)) {
|
|
DDRFREQ_ERR("try to enable %s output failed!\n", name);
|
|
goto err;
|
|
}
|
|
|
|
cur_freq = clk_get_rate(clk);
|
|
if (cur_freq == target_freq) {
|
|
mutex_unlock(&busfreq_lock);
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
if ((!strcmp(name, "ahb0")) || (!strcmp(name, "ahb2"))) {
|
|
if (target_freq == 75000000) {
|
|
if (clk_set_parent(clk, gtbus)) {
|
|
DDRFREQ_ERR("try to set %s parent failed!\n", name);
|
|
goto err;
|
|
}
|
|
} else if (target_freq == 120000000) {
|
|
if (clk_set_parent(clk, clk_pll4)) {
|
|
DDRFREQ_ERR("try to set %s parent failed!\n", name);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (clk_set_rate(clk, target_freq)) {
|
|
DDRFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq);
|
|
goto err;
|
|
}
|
|
} else {
|
|
if (clk_set_rate(clk, target_freq)) {
|
|
DDRFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (clk_set_rate(clk, target_freq)) {
|
|
DDRFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq);
|
|
goto err;
|
|
}
|
|
#endif
|
|
|
|
cur_freq = clk_get_rate(clk);
|
|
if (cur_freq != target_freq) {
|
|
DDRFREQ_ERR("%s: %lu != %lu\n", name, cur_freq, target_freq);
|
|
goto err;
|
|
}
|
|
|
|
mutex_unlock(&busfreq_lock);
|
|
return 0;
|
|
|
|
err:
|
|
mutex_unlock(&busfreq_lock);
|
|
return -1;
|
|
}
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
static int __init_vftable_syscfg(void)
|
|
{
|
|
int i, ret = 0;
|
|
char name[16] = {0};
|
|
struct device_node *dvfs_np;
|
|
unsigned int val;
|
|
|
|
dvfs_np = of_find_compatible_node(NULL, NULL,
|
|
"allwinner,dram_dvfs_table");
|
|
if (!dvfs_np) {
|
|
DDRFREQ_ERR("Get dram_dvfs_table failed\n");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
if (of_property_read_u32(dvfs_np, "LV_count", &table_length_syscfg)) {
|
|
DDRFREQ_ERR("Get dram LV_count failed!\n");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
/* table_length_syscfg must be < TABLE_LENGTH */
|
|
if (table_length_syscfg > TABLE_LENGTH) {
|
|
DDRFREQ_ERR("LV_count from sysconfig is out of bounder\n");
|
|
ret = -1;
|
|
goto fail;
|
|
}
|
|
|
|
for (i = 1; i <= table_length_syscfg; i++) {
|
|
sprintf(name, "LV%d_freq", i);
|
|
if (of_property_read_u32(dvfs_np, name, &val)) {
|
|
DDRFREQ_ERR("Get dram LV%d_freq failed\n", i);
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
dvfs_table_syscfg[i-1].freq = val / 1000;
|
|
|
|
sprintf(name, "LV%d_volt", i);
|
|
if (of_property_read_u32(dvfs_np, name, &val)) {
|
|
DDRFREQ_ERR("Get dram LV%d_volt failed\n", i);
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
dvfs_table_syscfg[i-1].volt = val;
|
|
}
|
|
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static void __vftable_show(void)
|
|
{
|
|
int i;
|
|
|
|
pr_debug("---------------Dram V-F Table---------------\n");
|
|
for (i = 0; i < table_length_syscfg; i++) {
|
|
pr_debug("voltage = %4dmv \tfrequency = %4dKHz\n",
|
|
dvfs_table_syscfg[i].volt, dvfs_table_syscfg[i].freq);
|
|
}
|
|
pr_debug("--------------------------------------------\n");
|
|
}
|
|
|
|
static unsigned int __get_vddsys_value(unsigned int freq)
|
|
{
|
|
struct ddrfreq_dvfs *dvfs_inf = NULL;
|
|
dvfs_inf = &dvfs_table_syscfg[0];
|
|
|
|
while ((dvfs_inf+1)->freq >= freq)
|
|
dvfs_inf++;
|
|
|
|
return dvfs_inf->volt;
|
|
}
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS */
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
static unsigned long __ddrfreq_get(void)
|
|
{
|
|
unsigned int pll4_rate, pll6_rate, dram_clk_rate, dram_freq = 0;
|
|
int value;
|
|
|
|
value = (readl(SUNXI_CCM_MOD_VBASE + 0x84) >> 12) & 0xF;
|
|
if (value == 0x3) {
|
|
pll6_rate = clk_get_rate(clk_pll6) / 1000;
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "pll6_rate: %d\n", pll6_rate); */
|
|
|
|
value = readl(SUNXI_CCM_MOD_VBASE + 0x84) >> 8;
|
|
value &= 0xF;
|
|
dram_clk_rate = pll6_rate / (value + 1);
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "dram_clk_rate: %d\n", dram_clk_rate); */
|
|
|
|
if ((readl(SUNXI_DRAMPHY0_VBASE + 0x04) >> 17) & 0x1) /* pll bypass mode */
|
|
dram_freq = dram_clk_rate >> 1;
|
|
else /* pll normal mode */
|
|
dram_freq = dram_clk_rate << 1;
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "dram_freq: %d\n", dram_freq); */
|
|
|
|
} else if (value == 0x0) {
|
|
pll4_rate = clk_get_rate(clk_pll4) / 1000;
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "pll4_rate: %d\n", pll4_rate); */
|
|
|
|
value = readl(SUNXI_CCM_MOD_VBASE + 0x84) >> 8;
|
|
value &= 0xF;
|
|
dram_clk_rate = pll4_rate / (value + 1);
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "dram_clk_rate: %d\n", dram_clk_rate); */
|
|
|
|
dram_freq = dram_clk_rate >> 1;
|
|
/* DDRFREQ_DBG(DEBUG_FREQ, "dram_freq: %d\n", dram_freq); */
|
|
}
|
|
|
|
return dram_freq;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW8P1)
|
|
static unsigned long __ddrfreq_get(void)
|
|
{
|
|
unsigned long pll_ddr_rate, dram_freq = 0;
|
|
unsigned int dram_div_m;
|
|
|
|
if ((readl(sunxi_ccm_vbase + 0xF8) >> 16) & 0x1)
|
|
pll_ddr_rate = clk_get_rate(clk_pll_ddr1) / 1000;
|
|
else
|
|
pll_ddr_rate = clk_get_rate(clk_pll_ddr0) / 1000;
|
|
|
|
if (mdfs_in_cfs == 1) {
|
|
if (readl(sunxi_ccm_vbase + 0xF4) & 0xF) /* pll normal mode */
|
|
dram_freq = pll_ddr_rate;
|
|
else /* pll bypass mode */
|
|
dram_freq = pll_ddr_rate / 4;
|
|
} else if (mdfs_in_cfs == 0) {
|
|
dram_div_m = (readl(sunxi_ccm_vbase + 0xF4) & 0xF) + 1;
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
if (ddrfreq_system_state == 1)
|
|
dram_freq = pll_ddr_rate / 4 / dram_div_m;
|
|
else
|
|
dram_freq = pll_ddr_rate * 2 / dram_div_m;
|
|
#else
|
|
dram_freq = pll_ddr_rate * 2 / dram_div_m;
|
|
#endif
|
|
|
|
if ((dram_freq != sunxi_ddrfreq_max) && (dram_freq != sunxi_ddrfreq_min))
|
|
dram_freq = dram_freq >= 360000 ? 360000 : 240000;
|
|
}
|
|
|
|
return dram_freq;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1)
|
|
static unsigned long __ddrfreq_get(void)
|
|
{
|
|
unsigned long pll_ddr_rate, dram_freq = 0;
|
|
unsigned int dram_div_m;
|
|
|
|
pll_ddr_rate = clk_get_rate(clk_pll_ddr0) / 1000;
|
|
dram_div_m = (readl((void *)CCM_DRAM_CFG_REG) & 0xF) + 1;
|
|
dram_freq = pll_ddr_rate / 2 / dram_div_m;
|
|
|
|
return dram_freq;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW9P1)
|
|
static unsigned long __ddrfreq_get(void)
|
|
{
|
|
unsigned long pll_ddr_rate = 0, dram_freq = 0;
|
|
unsigned int dram_div_m;
|
|
unsigned int value;
|
|
|
|
value = (readl(SUNXI_CCM_VBASE + 0xF4) >> 20) & 0x3;
|
|
if (value == 0x0)
|
|
pll_ddr_rate = clk_get_rate(clk_pll_ddr0) / 1000;
|
|
else if (value == 0x1)
|
|
pll_ddr_rate = clk_get_rate(clk_pll_ddr1) / 1000;
|
|
|
|
dram_div_m = (readl(CCM_DRAM_CFG_REG) & 0xF) + 1;
|
|
dram_freq = pll_ddr_rate / 2 / dram_div_m;
|
|
|
|
return dram_freq;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* dramfreq_get - get the current DRAM frequency (in KHz)
|
|
*
|
|
*/
|
|
unsigned long dramfreq_get(void)
|
|
{
|
|
unsigned long freq;
|
|
ktime_t calltime, delta, rettime;
|
|
|
|
calltime = ktime_get();
|
|
freq = __ddrfreq_get();
|
|
rettime = ktime_get();
|
|
delta = ktime_sub(rettime, calltime);
|
|
getfreq_time_usecs = ktime_to_ns(delta) >> 10;
|
|
|
|
return freq;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dramfreq_get);
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
static unsigned int __get_pll6_para(unsigned int pll_clk)
|
|
{
|
|
unsigned int n = 0, div1 = 0, div2 = 0, rval = 0;
|
|
|
|
if ((pll_clk % 24) == 0) {
|
|
n = pll_clk / 24;
|
|
} else if ((pll_clk % 12) == 0) {
|
|
n = pll_clk / 12;
|
|
div1 = 1;
|
|
} else if ((pll_clk % 6) == 0) {
|
|
n = pll_clk / 6;
|
|
div1 = 1;
|
|
div2 = 1;
|
|
}
|
|
|
|
rval = (1U << 31) | (div2 << 18) | (div1 << 16) | (n << 8);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static void __update_target_freq(unsigned long *freq, unsigned int *div)
|
|
{
|
|
int i = 0;
|
|
|
|
if (dram_freq_adjust) {
|
|
if ((*freq == 480000) || (*freq == 240000))
|
|
*freq += 1;
|
|
}
|
|
|
|
while (dram_freq_table[i][0] != 0) {
|
|
if (dram_freq_table[i+1][0] >= *freq)
|
|
i++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
*freq = dram_freq_table[i][0];
|
|
*div = dram_freq_table[i][1];
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
static unsigned int __get_pll_ddr_para_in_cfs(unsigned int pll_clk)
|
|
{
|
|
u32 rval = 0;
|
|
u32 div;
|
|
|
|
div = pll_clk / 24;
|
|
if (div < 12) /* no less than 288M */
|
|
div = 12;
|
|
|
|
rval |= (((div - 1) << 8) | (0x1U << 31));
|
|
|
|
return rval;
|
|
}
|
|
|
|
static unsigned int __get_pll_div_para(unsigned int pll_clk)
|
|
{
|
|
u32 pll_div, present_clk, pll_source;
|
|
|
|
pll_source = (readl(_CCM_PLL_DDR_CFG_REG) >> 16) & 0x1;
|
|
if (pll_source)/* pll_ddr1 */
|
|
present_clk = clk_get_rate(clk_pll_ddr1) / 1000000;
|
|
else
|
|
present_clk = clk_get_rate(clk_pll_ddr0) / 1000000;
|
|
|
|
pll_div = present_clk * 2 / pll_clk;
|
|
if (pll_div > 16)
|
|
pll_div = 16;
|
|
|
|
return pll_div;
|
|
}
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
static unsigned int __get_pll_ddr0_para(unsigned int pll_clk)
|
|
{
|
|
unsigned int n, k, m = 1, rval, div;
|
|
|
|
div = pll_clk / 24;
|
|
m = 2;
|
|
k = 2;
|
|
n = div;
|
|
|
|
|
|
rval = readl(_CCM_PLL_DDR0_REG);
|
|
rval &= ~((0x1f << 8) | (0x3 << 4) | (0x3 << 0));
|
|
rval = (1U << 31) | ((n - 1) << 8) | ((k - 1) << 4) | (m - 1);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static unsigned int __get_pll_ddr1_para(unsigned int pll_clk)
|
|
{
|
|
u32 rval, div;
|
|
|
|
div = pll_clk / 24;
|
|
if (div < 12) /* no less than 288M */
|
|
div = 12;
|
|
|
|
rval = readl(_CCM_PLL_DDR1_REG);
|
|
rval &= ~(0x3f << 8);
|
|
rval |= (((div - 1) << 8) | (0x1U << 31));
|
|
|
|
return rval;
|
|
}
|
|
|
|
static unsigned int __get_pll_ddrx_para(unsigned int pll_clk)
|
|
{
|
|
u32 para, pll_source;
|
|
|
|
pll_source = (readl(_CCM_PLL_DDR_CFG_REG) >> 16) & 0x1;
|
|
if (pll_source)/* pll_ddr1 */
|
|
para = __get_pll_ddr1_para(pll_clk << 1);
|
|
else
|
|
para = __get_pll_ddr0_para(pll_clk);
|
|
|
|
return para;
|
|
}
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW */
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1)
|
|
static unsigned int __get_pll_ddr_para_in_cfs(unsigned int pll_clk)
|
|
{
|
|
unsigned int n = pll_clk / 24;
|
|
unsigned int div1 = 0, div2 = 0, rval = 0;
|
|
|
|
rval = (1U << 31) | (div2 << 18) | (div1 << 16) | ((n - 1) << 8);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static unsigned int __get_pll_div_para(unsigned int pll_clk)
|
|
{
|
|
u32 pll_div, present_clk;
|
|
|
|
present_clk = clk_get_rate(clk_pll_ddr0) / 1000000;
|
|
pll_div = present_clk / 2 / pll_clk;
|
|
|
|
if (pll_div >= 16)
|
|
pll_div = 15;
|
|
|
|
return pll_div;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW7P1)
|
|
static unsigned int __get_pll_ddr_para(unsigned int pll_clk)
|
|
{
|
|
unsigned int n, k = 1, m = 1, rval;
|
|
unsigned int div;
|
|
|
|
div = pll_clk / 24;
|
|
if (div <= 32) {
|
|
n = div;
|
|
k = 1;
|
|
} else {
|
|
if (div <= 64) {
|
|
k = 2;
|
|
} else {
|
|
if (div % 3 == 0)
|
|
k = 3;
|
|
else if (div % 4 == 0)
|
|
k = 4;
|
|
else if (div % 5 == 0)
|
|
k = 5;
|
|
}
|
|
n = div / k;
|
|
}
|
|
|
|
rval = readl(_CCM_PLL_DDR_REG);
|
|
rval &= ~((0x1f << 8) | (0x3 << 4) | (0x3 << 0));
|
|
rval = (1U << 31) | ((n - 1) << 8) | ((k - 1) << 4) | (m-1);
|
|
|
|
return rval;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW8P1)
|
|
static unsigned int __get_pll_ddr_para_in_cfs(unsigned int pll_clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int __get_pll_div_para(unsigned int pll_clk)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
volatile bool __sramdata cpu_pause[NR_CPUS];
|
|
volatile bool __sramdata pause_flag;
|
|
bool __sram is_paused(void)
|
|
{
|
|
smp_rmb();
|
|
return pause_flag;
|
|
}
|
|
void __sram set_paused(bool pause)
|
|
{
|
|
pause_flag = pause;
|
|
smp_wmb();
|
|
}
|
|
bool __sram is_cpuX_paused(unsigned int cpu)
|
|
{
|
|
smp_rmb();
|
|
return cpu_pause[cpu];
|
|
}
|
|
void __sram set_cpuX_paused(unsigned int cpu, bool pause)
|
|
{
|
|
cpu_pause[cpu] = pause;
|
|
smp_wmb();
|
|
}
|
|
void __sram mdfs_pause_cpu(void *info)
|
|
{
|
|
unsigned int cpu = raw_smp_processor_id();
|
|
dsb();
|
|
isb();
|
|
set_cpuX_paused(cpu, true);
|
|
while (is_paused())
|
|
;
|
|
set_cpuX_paused(cpu, false);
|
|
}
|
|
static void mdfs_wait_cpu(void *info)
|
|
{
|
|
}
|
|
#endif /* CONFIG_SMP */
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1)
|
|
static int mdfs_cfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_ddr_para)
|
|
{
|
|
unsigned int reg_val;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* setting ODT configuration,enable before adjust */
|
|
if (para->dram_clk > 400) {
|
|
writel(0x00000201, ODTMAP);
|
|
|
|
if (para->dram_odt_en) {
|
|
reg_val = readl(DXnGCR0(0));
|
|
reg_val |= (0x3 << 9);
|
|
writel(reg_val, DXnGCR0(0));
|
|
|
|
reg_val = readl(DXnGCR0(1));
|
|
reg_val |= (0x3 << 9);
|
|
writel(reg_val, DXnGCR0(1));
|
|
}
|
|
}
|
|
|
|
/* set pll ddr configuration */
|
|
reg_val = readl(_CCM_PLL_DDR_CFG_REG);
|
|
reg_val |= ((0x1 << 12) | (0x9 << 0)); /* continuously */
|
|
writel(reg_val, _CCM_PLL_DDR_CFG_REG);
|
|
/* set PLL-DDR1 without update */
|
|
writel(pll_ddr_para, _CCM_PLL_DDR1_REG);
|
|
|
|
/* setting MDFS configuration */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x1 << 2);
|
|
reg_val |= ((freq_jump & 0x1) << 2); /* 1: increase 0:decrease */
|
|
reg_val |= (0x1 << 1); /* CFS mode */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* start mdfs */
|
|
writel(((reg_val) | 0x1), MC_MDFSCR);
|
|
|
|
/* wait for process finished, bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* setting ODT configuration, disable after adjust */
|
|
if (para->dram_clk < 400) {
|
|
writel(0x0, ODTMAP);
|
|
|
|
reg_val = readl(DXnGCR0(0));
|
|
reg_val &= ~(0x3 << 9);
|
|
writel(reg_val, DXnGCR0(0));
|
|
|
|
reg_val = readl(DXnGCR0(1));
|
|
reg_val &= ~(0x3 << 9);
|
|
writel(reg_val, DXnGCR0(1));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdfs_dfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_div)
|
|
{
|
|
unsigned int reg_val, rank_num, odt_freq;
|
|
unsigned int trefi = 0;
|
|
unsigned int trfc = 0;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* set new refresh timing */
|
|
trefi = (39 * para->dram_clk) / 320;
|
|
trfc = (7 * para->dram_clk) / 40;
|
|
/* masked master ready */
|
|
writel(0xffffffff, MC_MDFSMRMR);
|
|
|
|
/* set pll lock time */
|
|
writel(0x4e200960, PTR1);
|
|
|
|
/* set ODT register buffer for power save */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x3U << 14);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
reg_val = 0;
|
|
reg_val = (trefi << 16) | (trfc << 0);
|
|
writel(reg_val, RFSHTMG);
|
|
|
|
rank_num = readl(MC_WORK_MODE) & 0x1;
|
|
odt_freq = rank_num == 1 ? 360 : 400;
|
|
/* * * * * * * * according to frequency set register buffer value */
|
|
if (para->dram_clk >= odt_freq) {
|
|
/* turn on DRAMC odt */
|
|
reg_val = readl(DXnGCR0(0));
|
|
reg_val |= (0x3 << 9);
|
|
writel(reg_val, DXnGCR0(0));
|
|
|
|
reg_val = readl(DXnGCR0(1));
|
|
reg_val |= (0x3 << 9);
|
|
writel(reg_val, DXnGCR0(1));
|
|
|
|
/* turn on DRAM ODT */
|
|
if (rank_num)
|
|
writel(0x00000303, ODTMAP);
|
|
else
|
|
writel(0x00000201, ODTMAP);
|
|
} else {
|
|
/* turn off DRAMC ODT */
|
|
reg_val = readl(DXnGCR0(0));
|
|
reg_val &= ~(0x3 << 9);
|
|
writel(reg_val, DXnGCR0(0));
|
|
|
|
reg_val = readl(DXnGCR0(1));
|
|
reg_val &= ~(0x3 << 9);
|
|
writel(reg_val, DXnGCR0(1));
|
|
|
|
/* turn off DRAM ODT */
|
|
writel(0x0, ODTMAP);
|
|
}
|
|
|
|
/* set the DRAM_CFG_REG divider in CCMU */
|
|
reg_val = readl(CCM_DRAM_CFG_REG);
|
|
reg_val &= ~(0xf << 0);
|
|
reg_val |= ((pll_div - 1) << 0);
|
|
writel(reg_val, CCM_DRAM_CFG_REG);
|
|
|
|
/* set MDFS register */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1U << 13); /* enable pad hold in the process */
|
|
reg_val |= ((freq_jump & 0x1) << 2); /* increase or decrease */
|
|
reg_val |= (0x0U << 1); /* DFS mode */
|
|
reg_val |= (0x1U << 0); /* start mdfs */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* wait for process finished */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
; /* bit0 must be 0 for new MDFS process */
|
|
|
|
/* close ODT register buffer */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x3U << 14);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1)
|
|
static int mdfs_cfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_ddr_para)
|
|
{
|
|
unsigned int reg_val;
|
|
unsigned int trefi;
|
|
unsigned int trfc;
|
|
unsigned int ctrl_freq;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* calculate new timing */
|
|
ctrl_freq = para->dram_clk / 2;
|
|
trefi = (3900 * ctrl_freq) / 1000 / 32;
|
|
trfc = (210 * ctrl_freq) / 1000;
|
|
/* enable timing double buffer */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1U << 15);
|
|
writel(reg_val, MC_MDFSCR);
|
|
/* set new timing into buffer */
|
|
reg_val = 0;
|
|
reg_val = (trefi << 16) | (trfc << 0);
|
|
writel(reg_val, RFSHTMG);
|
|
/* set pll-pattern control register */
|
|
reg_val = readl(_CCM_PLL_DDR_PATTERN_REG);
|
|
reg_val |= (0x7U << 29);
|
|
writel(reg_val, _CCM_PLL_DDR_PATTERN_REG);
|
|
|
|
/* set pll ddr configuration */
|
|
reg_val = readl(_CCM_PLL_DDR_CFG_REG);
|
|
reg_val |= ((0x1 << 12) | (0x9 << 0)); /* continuously */
|
|
writel(reg_val, _CCM_PLL_DDR_CFG_REG);
|
|
/* set PLL-DDR1 without update */
|
|
writel(pll_ddr_para, _CCM_PLL_DDR_REG);
|
|
|
|
/* setting MDFS configuration */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x1 << 2);
|
|
reg_val |= ((freq_jump & 0x1) << 2); /* 1: increase 0:decrease */
|
|
reg_val |= (0x1 << 1); /* CFS mode */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* start mdfs */
|
|
writel(((reg_val) | 0x1), MC_MDFSCR);
|
|
|
|
/* wait for process finished, bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* set pll-pattern control register */
|
|
reg_val = readl(_CCM_PLL_DDR_PATTERN_REG);
|
|
reg_val &= ~(0x7U << 29);
|
|
writel(reg_val, _CCM_PLL_DDR_PATTERN_REG);
|
|
|
|
/* disable timing double buffer */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x1U << 15);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int mdfs_dfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_div)
|
|
{
|
|
unsigned int reg_val, rank_num;
|
|
unsigned int i = 0;
|
|
unsigned int odt_type = 0;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* masked master ready */
|
|
writel(0xffffffff, MC_MDFSMRMR);
|
|
|
|
/* set ODT register buffer for power save */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x3U << 14);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* according to frequency set register buffer value */
|
|
if (!((para->dram_tpr13 >> 12) & 0x1)) {
|
|
if (para->dram_clk > 400) {
|
|
/* turn on DRAMC odt */
|
|
if (para->dram_odt_en & 0x1) {
|
|
odt_type = (para->dram_odt_en >> 1) & 0x1;
|
|
for (i = 0; i < 11; i++) {
|
|
reg_val = readl(DATX0IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (odt_type<<24);
|
|
writel(reg_val, DATX0IOCR(i));
|
|
|
|
reg_val = readl(DATX1IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (odt_type<<24);
|
|
writel(reg_val, DATX1IOCR(i));
|
|
|
|
reg_val = readl(DATX2IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (odt_type<<24);
|
|
writel(reg_val, DATX2IOCR(i));
|
|
|
|
reg_val = readl(DATX3IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (odt_type<<24);
|
|
writel(reg_val, DATX3IOCR(i));
|
|
}
|
|
}
|
|
/* turn on DRAM ODT */
|
|
rank_num = readl(MC_WORK_MODE) & 0x1;
|
|
if (rank_num)
|
|
writel(0x00000303, ODTMAP);
|
|
else
|
|
writel(0x00000201, ODTMAP);
|
|
} else {
|
|
if (para->dram_odt_en & 0x1) {
|
|
/* for dqs/dqs#,odt always on */
|
|
for (i = 0; i < 11; i++) {
|
|
reg_val = readl(DATX0IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (0x2U<<24);
|
|
writel(reg_val, DATX0IOCR(i));
|
|
|
|
reg_val = readl(DATX1IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (0x2U<<24);
|
|
writel(reg_val, DATX1IOCR(i));
|
|
|
|
reg_val = readl(DATX2IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (0x2U<<24);
|
|
writel(reg_val, DATX2IOCR(i));
|
|
|
|
reg_val = readl(DATX3IOCR(i));
|
|
reg_val &= ~(0x3U<<24);
|
|
reg_val |= (0x2U<<24);
|
|
writel(reg_val, DATX3IOCR(i));
|
|
}
|
|
}
|
|
/* turn off DRAM ODT */
|
|
writel(0x0, ODTMAP);
|
|
}
|
|
}
|
|
|
|
/* set the DRAM_CFG_REG divider in CCMU */
|
|
reg_val = readl(CCM_DRAM_CFG_REG);
|
|
reg_val &= ~(0xf << 0);
|
|
reg_val |= ((pll_div - 1) << 0);
|
|
writel(reg_val, CCM_DRAM_CFG_REG);
|
|
|
|
/* set MDFS register */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1U << 13); /* enable pad hold in the process */
|
|
reg_val |= (0x1 << 4);
|
|
reg_val |= ((freq_jump & 0x1) << 2); /* increase or decrease */
|
|
reg_val |= (0x0U << 1); /* DFS mode */
|
|
reg_val |= (0x1U << 0); /* start mdfs */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* wait for process finished */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
; /* bit0 must be 0 for new MDFS process */
|
|
|
|
/* close ODT register buffer */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x3U << 14);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW8P1)
|
|
static int mdfs_cfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_ddr_para)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int mdfs_dfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int pll_div)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW9P1)
|
|
static int mdfs_cfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int freq)
|
|
{
|
|
unsigned int reg_val, i;
|
|
unsigned int trefi, trfc, ctrl_freq;
|
|
unsigned int rank_num = 0;
|
|
unsigned int n = 4;
|
|
unsigned int div = 0;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/*for CFS only support LPDDR */
|
|
ctrl_freq = freq >> 1;
|
|
if ((para->dram_type == 3) || (para->dram_type == 2)) {
|
|
trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1 : 0)) / 32;
|
|
trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1 : 0);
|
|
} else {
|
|
trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1 : 0)) / 32;
|
|
trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1 : 0);
|
|
}
|
|
|
|
/* set dual buffer for timing change and power save */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1U << 15);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* change refresh timing */
|
|
reg_val = readl(RFSHTMG);
|
|
reg_val &= ~((0xfff<<0)|(0xfff<<16));
|
|
reg_val |= ((trfc<<0)|(trefi<<16));
|
|
writel(reg_val, RFSHTMG);
|
|
|
|
/* make sure clk always on */
|
|
reg_val = readl(PGCR0);
|
|
reg_val &= ~(0xf<<12);
|
|
reg_val |= (0x5<<12);
|
|
writel(reg_val, PGCR0);
|
|
|
|
/* change ODT status for power save */
|
|
if (!((para->dram_tpr13>>12) & 0x1)) {
|
|
if (freq > 400) {
|
|
if ((para->dram_odt_en & 0x1)) {
|
|
for (i = 0; i < n; i++) {
|
|
/* byte 0/byte 1 */
|
|
reg_val = readl(DXnGCR0(i));
|
|
reg_val &= ~(0x3U<<4);
|
|
reg_val |= (0x0<<4);/* ODT dynamic */
|
|
writel(reg_val, DXnGCR0(i));
|
|
}
|
|
|
|
rank_num = readl(MC_WORK_MODE) & 0x1;
|
|
if (rank_num)
|
|
writel(0x00000303, ODTMAP);
|
|
else
|
|
writel(0x00000201, ODTMAP);
|
|
}
|
|
} else {
|
|
if (para->dram_odt_en & 0x1) {
|
|
for (i = 0; i < n; i++) {
|
|
/* byte 0/byte 1 */
|
|
reg_val = readl(DXnGCR0(i));
|
|
reg_val &= ~(0x3U<<4);
|
|
reg_val |= (0x2<<4);
|
|
writel(reg_val, DXnGCR0(i));
|
|
}
|
|
writel(0x0, ODTMAP);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* change pll-ddr N value */
|
|
div = freq / 12;
|
|
reg_val = readl(_CCM_PLL_DDR1_REG);
|
|
reg_val &= ~(0x7f<<8);
|
|
reg_val |= (((div-1)<<8));
|
|
writel(reg_val, _CCM_PLL_DDR1_REG);
|
|
|
|
/* setting MDFS configuration */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x1 << 2);
|
|
reg_val |= ((freq_jump & 0x1) << 2); /* 1: increase 0:decrease */
|
|
reg_val |= (0x1 << 1); /* CFS mode */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* start mdfs,no wait; */
|
|
writel(((reg_val) | 0x1), MC_MDFSCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int mdfs_dfs(unsigned int freq_jump, __dram_para_t *para,
|
|
unsigned int freq)
|
|
{
|
|
unsigned int reg_val, rank_num;
|
|
unsigned int trefi, trfc, ctrl_freq;
|
|
unsigned int i = 0;
|
|
unsigned int n = 4;
|
|
unsigned int div, source;
|
|
unsigned int hdr_clk_status = 0;
|
|
|
|
/* bit0 must be 0 for new MDFS process */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* calculate source and divider */
|
|
if (para->dram_tpr9 != 0) {
|
|
if (((para->dram_clk % freq) == 0) && ((para->dram_tpr9 % freq) == 0)) {
|
|
if ((para->dram_clk/freq) > (para->dram_tpr9/freq)) {
|
|
source = 0;
|
|
div = para->dram_tpr9 / freq;
|
|
} else {
|
|
source = 1;
|
|
div = para->dram_clk / freq;
|
|
}
|
|
} else if ((para->dram_clk % freq) == 0) {
|
|
source = 1;
|
|
div = para->dram_clk / freq;
|
|
} else if ((para->dram_tpr9 % freq) == 0) {
|
|
source = 0;
|
|
div = para->dram_tpr9 / freq;
|
|
} else {
|
|
printk("MDFS fail!\n");
|
|
return 1;
|
|
}
|
|
} else {
|
|
source = 1;
|
|
div = para->dram_clk / freq;
|
|
}
|
|
|
|
ctrl_freq = freq >> 1;
|
|
if ((para->dram_type == 3) || (para->dram_type == 2)) {
|
|
trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1 : 0))/32;
|
|
trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1 : 0);
|
|
} else {
|
|
trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1 : 0))/32;
|
|
trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1 : 0);
|
|
}
|
|
|
|
/* make sure clk always on */
|
|
hdr_clk_status = (readl(PGCR0) >> 12) & 0xf;
|
|
reg_val = readl(PGCR0);
|
|
reg_val &= ~(0xf<<12);
|
|
reg_val |= (0x5<<12);
|
|
writel(reg_val, PGCR0);
|
|
|
|
/* set dual buffer for timing change and power save */
|
|
reg_val = readl(MC_MDFSCR);
|
|
/* VTC dual buffer can not be used */
|
|
reg_val |= (0x1U << 15);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* change refresh timing */
|
|
reg_val = readl(RFSHTMG);
|
|
reg_val &= ~((0xfff<<0)|(0xfff<<16));
|
|
reg_val |= ((trfc<<0)|(trefi<<16));
|
|
writel(reg_val, RFSHTMG);
|
|
|
|
/* change ODT status for power save */
|
|
if (!((para->dram_tpr13 >> 12) & 0x1)) {
|
|
if (freq > 400) {
|
|
if (para->dram_odt_en & 0x1) {
|
|
for (i = 0; i < n; i++) {
|
|
/* byte 0/byte 1 */
|
|
reg_val = readl(DXnGCR0(i));
|
|
reg_val &= ~(0x3U<<4);
|
|
reg_val |= (0x0<<4);/* ODT dynamic */
|
|
writel(reg_val, DXnGCR0(i));
|
|
}
|
|
|
|
rank_num = readl(MC_WORK_MODE) & 0x1;
|
|
if (rank_num)
|
|
writel(0x00000303, ODTMAP);
|
|
else
|
|
writel(0x00000201, ODTMAP);
|
|
}
|
|
} else {
|
|
if (para->dram_odt_en & 0x1) {
|
|
for (i = 0; i < n; i++) {
|
|
/* byte 0/byte 1 */
|
|
reg_val = readl(DXnGCR0(i));
|
|
reg_val &= ~(0x3U<<4);
|
|
reg_val |= (0x2<<4);
|
|
writel(reg_val, DXnGCR0(i));
|
|
}
|
|
writel(0x0, ODTMAP);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set the DRAM_CFG_REG divider in CCMU */
|
|
reg_val = readl(CCM_DRAM_CFG_REG);
|
|
reg_val &= ~(0xf << 0);
|
|
reg_val |= ((div - 1) << 0);
|
|
reg_val &= ~(0x3<<20);
|
|
reg_val |= (source<<20);
|
|
writel(reg_val, CCM_DRAM_CFG_REG);
|
|
|
|
/* set MDFS register */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1<<4); /* bypass */
|
|
reg_val |= (0x1<<13);/* pad hold */
|
|
reg_val &= ~(0x1U << 1); /* DFS mode */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val |= (0x1U << 0); /* start mdfs */
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/* wait for process finished */
|
|
while (readl(MC_MDFSCR) & 0x1)
|
|
;
|
|
|
|
/* turn off dual buffer */
|
|
reg_val = readl(MC_MDFSCR);
|
|
reg_val &= ~(0x1U << 15);
|
|
writel(reg_val, MC_MDFSCR);
|
|
|
|
/*revovery hdr clk status */
|
|
reg_val = readl(PGCR0);
|
|
reg_val &= ~(0xf<<12);
|
|
reg_val |= (hdr_clk_status<<12);
|
|
writel(reg_val, PGCR0);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* freq_target: target frequency
|
|
* df: devfreq
|
|
*/
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
static int __ddrfreq_set(unsigned int jump, struct devfreq *df,
|
|
unsigned int freq_target, unsigned int div)
|
|
#else
|
|
static int __ddrfreq_set(unsigned int jump, struct devfreq *df,
|
|
unsigned int freq_target)
|
|
#endif
|
|
{
|
|
unsigned int pll_para_to_mdfs = 0;
|
|
int ret = 0;
|
|
ktime_t calltime, delta, rettime;
|
|
#ifdef CONFIG_CPU_FREQ
|
|
struct cpufreq_policy policy;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
u32 timeout = 0;
|
|
unsigned int cpu, this_cpu = raw_smp_processor_id();
|
|
|
|
cpumask_clear(&ipi_mask);
|
|
cpumask_copy(&ipi_mask, cpu_online_mask);
|
|
cpumask_clear_cpu(this_cpu, &ipi_mask);
|
|
|
|
DDRFREQ_DBG(DEBUG_FREQ, "current cpu is cpu%u\n", this_cpu);
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 0) {
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
if (!vbtime_ops.is_in_vb) {
|
|
DDRFREQ_ERR("is_in_vb is not initialized\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_CPU_FREQ
|
|
if (cpufreq_get_policy(&policy, 0)) {
|
|
DDRFREQ_ERR("can not get cpu policy\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#ifndef CONFIG_ARCH_SUN8IW9P1
|
|
dram_para.dram_clk = freq_target / 1000;
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
if (dram_para.dram_clk <= 800)
|
|
pll_para_to_mdfs = __get_pll6_para(dram_para.dram_clk << 1);
|
|
else
|
|
pll_para_to_mdfs = __get_pll6_para(dram_para.dram_clk >> 1);
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1)
|
|
if (mdfs_in_cfs == 1) {
|
|
DDRFREQ_DBG(DEBUG_FREQ, "mdfs in cfs mode\n");
|
|
pll_para_to_mdfs = __get_pll_ddr_para_in_cfs(dram_para.dram_clk << 1);
|
|
} else if (mdfs_in_cfs == 0) {
|
|
DDRFREQ_DBG(DEBUG_FREQ, "mdfs in dfs mode\n");
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
if (ddrfreq_system_state != 0)
|
|
pll_para_to_mdfs = __get_pll_ddrx_para(dram_para.dram_clk);
|
|
else
|
|
pll_para_to_mdfs = __get_pll_div_para(dram_para.dram_clk);
|
|
#else
|
|
pll_para_to_mdfs = __get_pll_div_para(dram_para.dram_clk);
|
|
#endif
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW7P1)
|
|
pll_para_to_mdfs = __get_pll_ddr_para(dram_para.dram_clk << 1);
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW9P1)
|
|
pll_para_to_mdfs = freq_target / 1000;
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1) || defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
memcpy(&__sram_text_start, &__sram_start, (&__sram_end - &__sram_start));
|
|
#endif
|
|
|
|
|
|
#ifdef CONFIG_CPU_FREQ
|
|
if (policy.cur < policy.max)
|
|
__cpufreq_driver_target(&policy, policy.max, CPUFREQ_RELATION_H);
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 0) {
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
local_bh_disable();
|
|
set_paused(true);
|
|
|
|
preempt_disable();
|
|
smp_call_function_many(&ipi_mask, (smp_call_func_t)mdfs_pause_cpu, NULL, 0);
|
|
preempt_enable();
|
|
|
|
dsb();
|
|
isb();
|
|
|
|
for_each_online_cpu(cpu) {
|
|
if (cpu == this_cpu)
|
|
continue;
|
|
while (!is_cpuX_paused(cpu) && timeout < 100) {
|
|
udelay(100);
|
|
timeout++;
|
|
}
|
|
|
|
if (timeout >= 100) {
|
|
DDRFREQ_ERR("pause cpu%d timeout\n", cpu);
|
|
ret = -2;
|
|
goto out;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
local_irq_disable();
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
while (!vbtime_ops.is_in_vb())
|
|
;
|
|
#endif
|
|
calltime = ktime_get();
|
|
flush_tlb_all();
|
|
dmb();
|
|
dsb();
|
|
isb();
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
mdfs_main(jump, &dram_para, pll_para_to_mdfs, div);
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 1) {
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_CFS_SW
|
|
if (!ddrfreq_odt_disable) {
|
|
if (dram_para.dram_clk > 200)
|
|
mdfs_main(jump, &dram_para, 0, 0);
|
|
}
|
|
#endif
|
|
|
|
mdfs_cfs(jump, &dram_para, pll_para_to_mdfs);
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_CFS_SW
|
|
if (!ddrfreq_odt_disable) {
|
|
if (dram_para.dram_clk < 200)
|
|
mdfs_main(jump, &dram_para, 0, 0);
|
|
}
|
|
#endif
|
|
} else if (mdfs_in_cfs == 0) {
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
if (ddrfreq_system_state == 0)
|
|
mdfs_dfs(jump, &dram_para, pll_para_to_mdfs);
|
|
else
|
|
mdfs_main(jump, &dram_para, pll_para_to_mdfs, 0);
|
|
#else
|
|
mdfs_dfs(jump, &dram_para, pll_para_to_mdfs);
|
|
#endif
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW7P1)
|
|
mdfs_main(jump, &dram_para, pll_para_to_mdfs, 0);
|
|
#endif
|
|
|
|
rettime = ktime_get();
|
|
delta = ktime_sub(rettime, calltime);
|
|
setfreq_time_usecs = ktime_to_ns(delta) >> 10;
|
|
local_irq_enable();
|
|
|
|
DDRFREQ_DBG(DEBUG_FREQ, "elapsed: %lluus\n", setfreq_time_usecs);
|
|
|
|
#ifdef CONFIG_SMP
|
|
out:
|
|
set_paused(false);
|
|
local_bh_enable();
|
|
preempt_disable();
|
|
smp_call_function_many(&ipi_mask, mdfs_wait_cpu, NULL, true);
|
|
preempt_enable();
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ddrfreq_target(struct device *dev, unsigned long *freq, u32 flags)
|
|
{
|
|
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
|
|
struct devfreq *df = platform_get_drvdata(pdev);
|
|
int ret = 0, mdfs_retries = 0;
|
|
int jump = 0;
|
|
int cur_freq = 0;
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
unsigned int div = 0;
|
|
#endif
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
int next_vb_time_us = 0, next_vb_retries = 0;
|
|
#endif
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
int i;
|
|
#endif
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
unsigned int new_vdd;
|
|
#endif
|
|
|
|
mutex_lock(&ddrfreq_lock);
|
|
get_online_cpus();
|
|
|
|
if (!ddrfreq_enable)
|
|
goto unlock;
|
|
|
|
if (df == NULL) {
|
|
ret = -1;
|
|
goto unlock;
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
__update_target_freq(freq, &div);
|
|
#endif
|
|
|
|
cur_freq = __ddrfreq_get();
|
|
if (df->previous_freq != cur_freq)
|
|
df->previous_freq = cur_freq;
|
|
|
|
if (*freq == df->previous_freq) {
|
|
ret = -1;
|
|
goto unlock;
|
|
}
|
|
|
|
jump = (*freq > df->previous_freq) ? 1 : 0;
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_LOW_POWER_SW
|
|
if ((df->previous_freq == df->max_freq) && (*freq == df->min_freq))
|
|
ddrfreq_system_state = 1;
|
|
else if ((df->previous_freq == df->min_freq) && (*freq == df->max_freq))
|
|
ddrfreq_system_state = 2;
|
|
else
|
|
ddrfreq_system_state = 0;
|
|
|
|
DDRFREQ_DBG(DEBUG_FREQ, "DDR: %luKHz->%luKHz start: %u\n", df->previous_freq, *freq, ddrfreq_system_state);
|
|
#endif
|
|
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
vdd_sys = regulator_get(NULL, "axp22_dcdc4");
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
vdd_sys = regulator_get(NULL, "axp22_dcdc2");
|
|
#endif
|
|
|
|
if (IS_ERR(vdd_sys)) {
|
|
DDRFREQ_ERR("some error happen, fail to get regulator!");
|
|
vdd_sys = NULL;
|
|
}
|
|
|
|
new_vdd = __get_vddsys_value(*freq);
|
|
if ((df->previous_freq == df->min_freq) && (*freq == df->max_freq)) {
|
|
if (vdd_sys && (new_vdd > last_vdd)) {
|
|
ret = regulator_set_voltage(vdd_sys, new_vdd * 1000, new_vdd * 1000);
|
|
if (ret != 0) {
|
|
DDRFREQ_ERR("fail to set regulator voltage!\n");
|
|
regulator_put(vdd_sys);
|
|
goto unlock;
|
|
}
|
|
DDRFREQ_DBG(DEBUG_FREQ, "VDD_SYS: %dmv->%dmv ok!\n", last_vdd, new_vdd);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
if ((df->previous_freq == df->min_freq) && (*freq == df->max_freq)) {
|
|
for (i = ARRAY_SIZE(busfreq_tbl) - 1; i >= 0; i--) {
|
|
__set_bus_freq(busfreq_tbl[i].name,
|
|
busfreq_tbl[i].bus_clk, busfreq_tbl[i].normal_freq);
|
|
}
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "GTBUS:%lu, CCI400:%lu, AHB0:%lu, AHB1:%lu, AHB2:%lu\n",
|
|
clk_get_rate(gtbus) / 1000000, clk_get_rate(cci400) / 1000000,
|
|
clk_get_rate(ahb0) / 1000000, clk_get_rate(ahb1) / 1000000,
|
|
clk_get_rate(ahb2) / 1000000);
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(ahb1) / 1000000);
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "CCI400:%lu, AHB1:%lu\n",
|
|
clk_get_rate(cci400) / 1000000, clk_get_rate(ahb1) / 1000000);
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 0) {
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
if (!vbtime_ops.get_next_vb_time) {
|
|
DDRFREQ_ERR("get_next_vb_time is not initialized\n");
|
|
ret = -1;
|
|
goto unlock;
|
|
}
|
|
|
|
do {
|
|
next_vb_time_us = vbtime_ops.get_next_vb_time();
|
|
if (next_vb_time_us < NEXT_VB_TIME_WAIT_US) {
|
|
next_vb_retries++;
|
|
DDRFREQ_DBG(DEBUG_FREQ, "next_vb_time_us: %dus\n", next_vb_time_us);
|
|
} else {
|
|
break;
|
|
}
|
|
} while (next_vb_retries < NEXT_VB_TIME_RETRIES);
|
|
|
|
if (next_vb_retries >= NEXT_VB_TIME_RETRIES) {
|
|
DDRFREQ_ERR("next vb retrying failed, next time\n");
|
|
ret = -1;
|
|
goto unlock;
|
|
} else {
|
|
usleep_range(next_vb_time_us - NEXT_VB_TIME_WAIT_US,
|
|
next_vb_time_us - NEXT_VB_TIME_WAIT_US);
|
|
}
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC */
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
}
|
|
#endif
|
|
|
|
do {
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
ret = __ddrfreq_set(jump, df, *freq, div);
|
|
#else
|
|
ret = __ddrfreq_set(jump, df, *freq);
|
|
#endif
|
|
if (ret == -1)
|
|
goto unlock;
|
|
else if (ret == -2)
|
|
mdfs_retries++;
|
|
else if (ret == 0)
|
|
break;
|
|
|
|
} while (mdfs_retries < MDFS_RETRIES);
|
|
|
|
if (mdfs_retries >= MDFS_RETRIES) {
|
|
DDRFREQ_ERR("mdfs retrying failed, next time\n");
|
|
ret = -1;
|
|
goto unlock;
|
|
}
|
|
|
|
DDRFREQ_DBG(DEBUG_FREQ, "DDR: %luKHz->%luKHz ok!\n",
|
|
df->previous_freq, __ddrfreq_get());
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
if ((df->previous_freq == df->max_freq) && (*freq == df->min_freq)) {
|
|
for (i = 0; i < ARRAY_SIZE(busfreq_tbl); i++) {
|
|
__set_bus_freq(busfreq_tbl[i].name,
|
|
busfreq_tbl[i].bus_clk, busfreq_tbl[i].idle_freq);
|
|
}
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "GTBUS:%lu, CCI400:%lu, AHB0:%lu, AHB1:%lu, AHB2:%lu\n",
|
|
clk_get_rate(gtbus) / 1000000, clk_get_rate(cci400) / 1000000,
|
|
clk_get_rate(ahb0) / 1000000, clk_get_rate(ahb1) / 1000000,
|
|
clk_get_rate(ahb2) / 1000000);
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(ahb1) / 1000000);
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
DDRFREQ_DBG(DEBUG_FREQ, "CCI400:%lu, AHB1:%lu\n",
|
|
clk_get_rate(cci400) / 1000000, clk_get_rate(ahb1) / 1000000);
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
if ((df->previous_freq == df->max_freq) && (*freq == df->min_freq)) {
|
|
if (vdd_sys && (new_vdd < last_vdd)) {
|
|
ret = regulator_set_voltage(vdd_sys, new_vdd * 1000, new_vdd * 1000);
|
|
if (ret != 0) {
|
|
/* without care, ddr freq scaling is ok */
|
|
ret = 0;
|
|
DDRFREQ_ERR("fail to set regulator voltage!\n");
|
|
regulator_put(vdd_sys);
|
|
goto unlock;
|
|
}
|
|
DDRFREQ_DBG(DEBUG_FREQ, "VDD_SYS: %dmv->%dmv ok!\n", last_vdd, new_vdd);
|
|
}
|
|
}
|
|
last_vdd = new_vdd;
|
|
|
|
if (vdd_sys) {
|
|
regulator_put(vdd_sys);
|
|
vdd_sys = NULL;
|
|
}
|
|
#endif
|
|
|
|
unlock:
|
|
put_online_cpus();
|
|
mutex_unlock(&ddrfreq_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ddrfreq_get_dev_status(struct device *dev,
|
|
struct devfreq_dev_status *stat)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ddrfreq_get_cur_freq(struct device *dev, unsigned long *freq)
|
|
{
|
|
*freq = dramfreq_get();
|
|
return 0;
|
|
}
|
|
|
|
static struct devfreq_dev_profile ddrfreq_profile = {
|
|
.target = ddrfreq_target,
|
|
.get_dev_status = ddrfreq_get_dev_status,
|
|
.get_cur_freq = ddrfreq_get_cur_freq,
|
|
};
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW5P1
|
|
#if defined(CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ) && defined(CONFIG_CPU_FREQ)
|
|
#if defined(CONFIG_DEVFREQ_GOV_DSM) && defined(CONFIG_EARLYSUSPEND)
|
|
extern atomic_t ddrfreq_dsm_suspend_lock;
|
|
|
|
static int ddrfreq_cpu_freq_transition(struct notifier_block *nb,
|
|
unsigned long val, void *data)
|
|
{
|
|
struct cpufreq_freqs *freqs = (struct cpufreq_freqs *)data;
|
|
|
|
switch (val) {
|
|
case CPUFREQ_POSTCHANGE:
|
|
if (!atomic_read(&ddrfreq_dsm_suspend_lock)) {
|
|
if (freqs->new < 100000) {
|
|
if (__set_bus_freq(busfreq_tbl[0].name, busfreq_tbl[0].bus_clk,
|
|
100000000)) {
|
|
DDRFREQ_ERR("set %s to 100MHz failed!\n", busfreq_tbl[0].name);
|
|
}
|
|
} else {
|
|
if (__set_bus_freq(busfreq_tbl[0].name, busfreq_tbl[0].bus_clk,
|
|
200000000)) {
|
|
DDRFREQ_ERR("set %s to 200MHz failed!\n", busfreq_tbl[0].name);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct notifier_block ddrfreq_cpu_freq_transition_notifier = {
|
|
.notifier_call = ddrfreq_cpu_freq_transition,
|
|
};
|
|
#endif
|
|
#endif
|
|
#endif /* CONFIG_ARCH_SUN8IW5P1 */
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW7P1) && defined(CONFIG_CPU_BUDGET_THERMAL)
|
|
struct platform_device sunxi_ddrfreq_device;
|
|
extern int ths_read_data(int value);
|
|
|
|
static int ddrfreq_budget_cooling_notifier_call(struct notifier_block *nfb, unsigned long mode, void *cmd)
|
|
{
|
|
int temperature;
|
|
|
|
temperature = ths_read_data(4);
|
|
if (temperature >= 100) {
|
|
DDRFREQ_DBG(DEBUG_FREQ, "temperature=%d C, ddr freq down\n", temperature);
|
|
ddrfreq_target(&sunxi_ddrfreq_device.dev, &this_df->min_freq, 0);
|
|
goto out;
|
|
} else if (temperature < 90) {
|
|
DDRFREQ_DBG(DEBUG_FREQ, "temperature=%d C, ddr freq up\n", temperature);
|
|
ddrfreq_target(&sunxi_ddrfreq_device.dev, &this_df->max_freq, 0);
|
|
goto out;
|
|
} else {
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
out:
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block ddrfreq_budget_cooling_notifier = {
|
|
.notifier_call = ddrfreq_budget_cooling_notifier_call,
|
|
};
|
|
#endif
|
|
|
|
static int sunxi_ddrfreq_probe(struct platform_device *pdev)
|
|
{
|
|
int err = 0;
|
|
struct device_node *dram_np;
|
|
|
|
dram_np = of_find_node_by_path("/dram");
|
|
if (!dram_np) {
|
|
DDRFREQ_ERR("Get dram node failed!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_clk",
|
|
&dram_para.dram_clk)) {
|
|
DDRFREQ_ERR("Get dram_clk failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
sunxi_ddrfreq_max = dram_para.dram_clk * 1000;
|
|
#if defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (sunxi_ddrfreq_max < SUNXI_DDRFREQ_MAXFREQ_MIN) {
|
|
printk("[ddrfreq] warning: dram clk is too low to support\n");
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
pr_debug("sunxi_ddrfreq_max=%u\n", sunxi_ddrfreq_max);
|
|
|
|
if (of_property_read_u32(dram_np, "dram_type",
|
|
&dram_para.dram_type)) {
|
|
DDRFREQ_ERR("Get dram_type failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_zq",
|
|
&dram_para.dram_zq)) {
|
|
DDRFREQ_ERR("Get dram_zq failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_odt_en",
|
|
&dram_para.dram_odt_en)) {
|
|
DDRFREQ_ERR("Get dram_odt_en failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_para1",
|
|
&dram_para.dram_para1)) {
|
|
DDRFREQ_ERR("Get dram_para1 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_para2",
|
|
&dram_para.dram_para2)) {
|
|
DDRFREQ_ERR("Get dram_para2 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_mr0",
|
|
&dram_para.dram_mr0)) {
|
|
DDRFREQ_ERR("Get dram_mr0 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_mr1",
|
|
&dram_para.dram_mr1)) {
|
|
DDRFREQ_ERR("Get dram_mr1 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_mr2",
|
|
&dram_para.dram_mr2)) {
|
|
DDRFREQ_ERR("Get dram_mr2 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_mr3",
|
|
&dram_para.dram_mr3)) {
|
|
DDRFREQ_ERR("Get dram_mr3 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr0",
|
|
&dram_para.dram_tpr0)) {
|
|
DDRFREQ_ERR("Get dram_tpr0 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr1",
|
|
&dram_para.dram_tpr1)) {
|
|
DDRFREQ_ERR("Get dram_tpr1 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr2",
|
|
&dram_para.dram_tpr2)) {
|
|
DDRFREQ_ERR("Get dram_tpr2 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr3",
|
|
&dram_para.dram_tpr3)) {
|
|
DDRFREQ_ERR("Get dram_tpr3 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr4",
|
|
&dram_para.dram_tpr4)) {
|
|
DDRFREQ_ERR("Get dram_tpr4 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr5",
|
|
&dram_para.dram_tpr5)) {
|
|
DDRFREQ_ERR("Get dram_tpr5 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr6",
|
|
&dram_para.dram_tpr6)) {
|
|
DDRFREQ_ERR("Get dram_tpr6 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr7",
|
|
&dram_para.dram_tpr7)) {
|
|
DDRFREQ_ERR("Get dram_tpr7 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr8",
|
|
&dram_para.dram_tpr8)) {
|
|
DDRFREQ_ERR("Get dram_tpr8 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr9",
|
|
&dram_para.dram_tpr9)) {
|
|
DDRFREQ_ERR("Get dram_tpr9 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr10",
|
|
&dram_para.dram_tpr10)) {
|
|
DDRFREQ_ERR("Get dram_tpr10 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr11",
|
|
&dram_para.dram_tpr11)) {
|
|
DDRFREQ_ERR("Get dram_tpr11 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
sunxi_ddrfreq_min = sunxi_ddrfreq_max / 4;
|
|
if (sunxi_ddrfreq_min < SUNXI_DDRFREQ_MINFREQ_MIN)
|
|
sunxi_ddrfreq_min = sunxi_ddrfreq_max / 3;
|
|
#elif defined(CONFIG_ARCH_SUN8IW7P1)
|
|
sunxi_ddrfreq_min = 408000;
|
|
#else
|
|
if (of_property_read_u32(dram_np, "dram_tpr12",
|
|
&dram_para.dram_tpr12)) {
|
|
DDRFREQ_ERR("Get dram_tpr12 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
sunxi_ddrfreq_min = dram_para.dram_tpr12 * 1000;
|
|
#endif
|
|
|
|
if (of_property_read_u32(dram_np, "dram_tpr13",
|
|
&dram_para.dram_tpr13)) {
|
|
DDRFREQ_ERR("Get dram_tpr13 failed!\n");
|
|
err = -ENODEV;
|
|
goto err_put_node;
|
|
}
|
|
|
|
ddrfreq_enable = (dram_para.dram_tpr13 >> 11) & 0x1;
|
|
if (!ddrfreq_enable)
|
|
printk("[ddrfreq] warning: disabled!\n");
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW6P1
|
|
ddrfreq_odt_disable = (dram_para.dram_tpr13 >> 12) & 0x1;
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
dram_freq_adjust = (dram_para.dram_tpr13 >> 18) & 0x1F;
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
mdfs_in_cfs = (dram_para.dram_tpr13 >> 10) & 0x1;
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
ahb2 = clk_get(NULL, AHB2_CLK);
|
|
if (!ahb2 || IS_ERR(ahb2)) {
|
|
DDRFREQ_ERR("try to get AHB2 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_ahb2;
|
|
}
|
|
|
|
ahb1 = clk_get(NULL, AHB1_CLK);
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("try to get AHB1 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_ahb1;
|
|
}
|
|
|
|
ahb0 = clk_get(NULL, AHB0_CLK);
|
|
if (!ahb0 || IS_ERR(ahb0)) {
|
|
DDRFREQ_ERR("try to get AHB0 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_ahb0;
|
|
}
|
|
|
|
cci400 = clk_get(NULL, CCI400_CLK);
|
|
if (!cci400 || IS_ERR(cci400)) {
|
|
DDRFREQ_ERR("try to get CCI400 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_cci400;
|
|
}
|
|
|
|
gtbus = clk_get(NULL, GT_CLK);
|
|
if (!gtbus || IS_ERR(gtbus)) {
|
|
DDRFREQ_ERR("try to get GTBUS failed!\n");
|
|
err = -ENOENT;
|
|
goto err_gtbus;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
sunxi_ccm_vbase = ioremap(SUNXI_CCM_PBASE, SUNXI_CCM_SIZE);
|
|
|
|
ahb1 = clk_get(NULL, AHB1_CLK);
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("try to get AHB1 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_ahb1;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
ahb1 = clk_get(NULL, AHB1_CLK);
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("try to get AHB1 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_ahb1;
|
|
}
|
|
|
|
cci400 = clk_get(NULL, CCI400_CLK);
|
|
if (!cci400 || IS_ERR(cci400)) {
|
|
DDRFREQ_ERR("try to get CCI400 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_cci400;
|
|
}
|
|
#endif
|
|
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
clk_pll4 = clk_get(NULL, PLL4_CLK);
|
|
if (!clk_pll4 || IS_ERR(clk_pll4)) {
|
|
DDRFREQ_ERR("try to get PLL4 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_pll4;
|
|
}
|
|
|
|
clk_pll6 = clk_get(NULL, PLL6_CLK);
|
|
if (!clk_pll6 || IS_ERR(clk_pll6)) {
|
|
DDRFREQ_ERR("try to get PLL6 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_pll6;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
clk_pll_ddr0 = clk_get(NULL, PLL_DDR0_CLK);
|
|
if (!clk_pll_ddr0 || IS_ERR(clk_pll_ddr0)) {
|
|
DDRFREQ_ERR("try to get clk_pll_ddr0 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_pll_ddr0;
|
|
}
|
|
|
|
clk_pll_ddr1 = clk_get(NULL, PLL_DDR1_CLK);
|
|
if (!clk_pll_ddr1 || IS_ERR(clk_pll_ddr1)) {
|
|
DDRFREQ_ERR("try to get clk_pll_ddr1 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_pll_ddr1;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1)
|
|
clk_pll_ddr0 = clk_get(NULL, PLL_DDR_CLK);
|
|
if (!clk_pll_ddr0 || IS_ERR(clk_pll_ddr0)) {
|
|
DDRFREQ_ERR("try to get clk_pll_ddr0 failed!\n");
|
|
err = -ENOENT;
|
|
goto err_pll_ddr0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
busfreq_tbl[0].bus_clk = gtbus;
|
|
busfreq_tbl[1].bus_clk = cci400;
|
|
busfreq_tbl[2].bus_clk = ahb0;
|
|
busfreq_tbl[3].bus_clk = ahb1;
|
|
busfreq_tbl[4].bus_clk = ahb2;
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
busfreq_tbl[0].bus_clk = ahb1;
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
busfreq_tbl[0].bus_clk = cci400;
|
|
busfreq_tbl[1].bus_clk = ahb1;
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 1)
|
|
sunxi_ddrfreq_min = 168000;
|
|
#endif
|
|
|
|
ddrfreq_profile.initial_freq = __ddrfreq_get();
|
|
#ifdef CONFIG_ARCH_SUN8IW7P1
|
|
this_df = devfreq_add_device(&pdev->dev, &ddrfreq_profile, &devfreq_userspace, NULL);
|
|
#else
|
|
this_df = devfreq_add_device(&pdev->dev, &ddrfreq_profile, "dsm", NULL);
|
|
#endif
|
|
if (IS_ERR(this_df)) {
|
|
DDRFREQ_ERR("add devfreq device failed!\n");
|
|
err = PTR_ERR(this_df);
|
|
goto err_devfreq;
|
|
}
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
err = __init_vftable_syscfg();
|
|
if (err) {
|
|
DDRFREQ_ERR("init V-F Table failed\n");
|
|
goto err_vftable;
|
|
}
|
|
|
|
__vftable_show();
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
vdd_sys = regulator_get(NULL, "axp22_dcdc4");
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
vdd_sys = regulator_get(NULL, "axp22_dcdc2");
|
|
#endif
|
|
|
|
if (IS_ERR(vdd_sys)) {
|
|
DDRFREQ_ERR("some error happen, fail to get regulator!");
|
|
goto err_vftable;
|
|
} else {
|
|
last_vdd = regulator_get_voltage(vdd_sys) / 1000;
|
|
pr_debug("last_vdd=%d\n", last_vdd);
|
|
}
|
|
|
|
if (vdd_sys) {
|
|
regulator_put(vdd_sys);
|
|
vdd_sys = NULL;
|
|
}
|
|
#endif
|
|
|
|
this_df->min_freq = sunxi_ddrfreq_min;
|
|
this_df->max_freq = sunxi_ddrfreq_max;
|
|
|
|
platform_set_drvdata(pdev, this_df);
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (mdfs_in_cfs == 0) {
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
|
|
memset(&vbtime_ops, 0, sizeof(struct ddrfreq_vb_time_ops));
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW5P1
|
|
#if defined(CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ) && defined(CONFIG_CPU_FREQ)
|
|
#if defined(CONFIG_DEVFREQ_GOV_DSM) && defined(CONFIG_EARLYSUSPEND)
|
|
cpufreq_register_notifier(&ddrfreq_cpu_freq_transition_notifier,
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
|
#endif
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW7P1) && defined(CONFIG_CPU_BUDGET_THERMAL)
|
|
register_budget_cooling_notifier(&ddrfreq_budget_cooling_notifier);
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1) || defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
pr_debug("__sram_start: 0x%0x, __sram_end: 0x%08x, __sram_text_start: 0x%08x, __sram_data_end: 0x%08x\n",
|
|
(unsigned int)&__sram_start, (unsigned int)&__sram_end,
|
|
(unsigned int)&__sram_text_start, (unsigned int)&__sram_data_end);
|
|
#endif
|
|
of_node_put(dram_np);
|
|
|
|
return 0;
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_VDDSYS
|
|
err_vftable:
|
|
devfreq_remove_device(this_df);
|
|
#endif
|
|
|
|
err_devfreq:
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
clk_put(clk_pll6);
|
|
clk_pll6 = NULL;
|
|
err_pll6:
|
|
clk_put(clk_pll4);
|
|
clk_pll4 = NULL;
|
|
err_pll4:
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
clk_put(clk_pll_ddr1);
|
|
clk_pll_ddr1 = NULL;
|
|
err_pll_ddr1:
|
|
clk_put(clk_pll_ddr0);
|
|
clk_pll_ddr0 = NULL;
|
|
err_pll_ddr0:
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1)
|
|
clk_put(clk_pll_ddr0);
|
|
clk_pll_ddr0 = NULL;
|
|
err_pll_ddr0:
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
clk_put(gtbus);
|
|
gtbus = NULL;
|
|
err_gtbus:
|
|
clk_put(cci400);
|
|
cci400 = NULL;
|
|
err_cci400:
|
|
clk_put(ahb0);
|
|
ahb0 = NULL;
|
|
err_ahb0:
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
err_ahb1:
|
|
clk_put(ahb2);
|
|
ahb2 = NULL;
|
|
err_ahb2:
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
err_ahb1:
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
clk_put(cci400);
|
|
cci400 = NULL;
|
|
err_cci400:
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
err_ahb1:
|
|
#endif
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */
|
|
|
|
err_put_node:
|
|
of_node_put(dram_np);
|
|
return err;
|
|
}
|
|
|
|
static int sunxi_ddrfreq_remove(struct platform_device *pdev)
|
|
{
|
|
struct devfreq *df = platform_get_drvdata(pdev);
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW5P1
|
|
#if defined(CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ) && defined(CONFIG_CPU_FREQ)
|
|
#if defined(CONFIG_DEVFREQ_GOV_DSM) && defined(CONFIG_EARLYSUSPEND)
|
|
cpufreq_unregister_notifier(&ddrfreq_cpu_freq_transition_notifier,
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
devfreq_remove_device(df);
|
|
|
|
#ifdef CONFIG_ARCH_SUN9IW1P1
|
|
if (!clk_pll6 || IS_ERR(clk_pll6)) {
|
|
DDRFREQ_ERR("clk_pll6 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(clk_pll6);
|
|
clk_pll6 = NULL;
|
|
}
|
|
|
|
if (!clk_pll4 || IS_ERR(clk_pll4)) {
|
|
DDRFREQ_ERR("clk_pll4 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(clk_pll4);
|
|
clk_pll4 = NULL;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1) || defined(CONFIG_ARCH_SUN8IW8P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (!clk_pll_ddr0 || IS_ERR(clk_pll_ddr0)) {
|
|
DDRFREQ_ERR("clk_pll_ddr0 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(clk_pll_ddr0);
|
|
clk_pll_ddr0 = NULL;
|
|
}
|
|
|
|
if (!clk_pll_ddr1 || IS_ERR(clk_pll_ddr1)) {
|
|
DDRFREQ_ERR("clk_pll_ddr1 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(clk_pll_ddr1);
|
|
clk_pll_ddr1 = NULL;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW7P1)
|
|
if (!clk_pll_ddr0 || IS_ERR(clk_pll_ddr0)) {
|
|
DDRFREQ_ERR("clk_pll_ddr0 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(clk_pll_ddr0);
|
|
clk_pll_ddr0 = NULL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
|
|
#if defined(CONFIG_ARCH_SUN9IW1P1)
|
|
if (!gtbus || IS_ERR(gtbus)) {
|
|
DDRFREQ_ERR("gtbus handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(gtbus);
|
|
gtbus = NULL;
|
|
}
|
|
|
|
if (!cci400 || IS_ERR(cci400)) {
|
|
DDRFREQ_ERR("cci400 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(cci400);
|
|
cci400 = NULL;
|
|
}
|
|
|
|
if (!ahb0 || IS_ERR(ahb0)) {
|
|
DDRFREQ_ERR("ahb0 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(ahb0);
|
|
ahb0 = NULL;
|
|
}
|
|
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("ahb1 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
}
|
|
|
|
if (!ahb2 || IS_ERR(ahb2)) {
|
|
DDRFREQ_ERR("ahb2 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(ahb2);
|
|
ahb2 = NULL;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW5P1)
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("ahb1 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
}
|
|
|
|
#elif defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1)
|
|
if (!cci400 || IS_ERR(cci400)) {
|
|
DDRFREQ_ERR("cci400 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(cci400);
|
|
cci400 = NULL;
|
|
}
|
|
|
|
if (!ahb1 || IS_ERR(ahb1)) {
|
|
DDRFREQ_ERR("ahb1 handle is invalid, just return!\n");
|
|
return -EINVAL;
|
|
} else {
|
|
clk_put(ahb1);
|
|
ahb1 = NULL;
|
|
}
|
|
#endif
|
|
|
|
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
#ifndef CONFIG_ARCH_SUN8IW6P1
|
|
static int sunxi_ddrfreq_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct devfreq *df = platform_get_drvdata(pdev);
|
|
int cur_freq;
|
|
|
|
DDRFREQ_DBG(DEBUG_SUSPEND, "%s: enter!\n", __func__);
|
|
|
|
cur_freq = __ddrfreq_get();
|
|
if (cur_freq != df->max_freq) {
|
|
if (!ddrfreq_target(&pdev->dev, &df->max_freq, 0))
|
|
df->previous_freq = df->max_freq;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_ddrfreq_resume(struct platform_device *pdev)
|
|
{
|
|
struct devfreq *df = platform_get_drvdata(pdev);
|
|
int cur_freq;
|
|
|
|
DDRFREQ_DBG(DEBUG_SUSPEND, "%s: enter!\n", __func__);
|
|
|
|
cur_freq = __ddrfreq_get();
|
|
if (df->previous_freq != cur_freq)
|
|
df->previous_freq = cur_freq;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
static struct platform_driver sunxi_ddrfreq_driver = {
|
|
.probe = sunxi_ddrfreq_probe,
|
|
.remove = sunxi_ddrfreq_remove,
|
|
#ifdef CONFIG_PM
|
|
#ifndef CONFIG_ARCH_SUN8IW6P1
|
|
.suspend = sunxi_ddrfreq_suspend,
|
|
.resume = sunxi_ddrfreq_resume,
|
|
#endif
|
|
#endif
|
|
.driver = {
|
|
.name = "sunxi-ddrfreq",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
struct platform_device sunxi_ddrfreq_device = {
|
|
.name = "sunxi-ddrfreq",
|
|
.id = -1,
|
|
};
|
|
|
|
static int __init sunxi_ddrfreq_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_device_register(&sunxi_ddrfreq_device);
|
|
if (ret) {
|
|
DDRFREQ_ERR("dramfreq device init failed!\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = platform_driver_register(&sunxi_ddrfreq_driver);
|
|
if (ret) {
|
|
DDRFREQ_ERR("dramfreq driver init failed!\n");
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
fs_initcall(sunxi_ddrfreq_init);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static struct dentry *ddrfreq_root;
|
|
|
|
static int set_time_show(struct seq_file *s, void *data)
|
|
{
|
|
seq_printf(s, "%llu\n", setfreq_time_usecs);
|
|
return 0;
|
|
}
|
|
|
|
static int set_time_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, set_time_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations set_time_fops = {
|
|
.open = set_time_open,
|
|
.read = seq_read,
|
|
};
|
|
|
|
static int get_time_show(struct seq_file *s, void *data)
|
|
{
|
|
seq_printf(s, "%llu\n", getfreq_time_usecs);
|
|
return 0;
|
|
}
|
|
|
|
static int get_time_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, get_time_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations get_time_fops = {
|
|
.open = get_time_open,
|
|
.read = seq_read,
|
|
};
|
|
|
|
static int __init debug_init(void)
|
|
{
|
|
int err = 0;
|
|
|
|
ddrfreq_root = debugfs_create_dir("ddrfreq", 0);
|
|
if (!ddrfreq_root)
|
|
return -ENOMEM;
|
|
|
|
if (!debugfs_create_file("get_time", 0444, ddrfreq_root,
|
|
NULL, &get_time_fops)) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (!debugfs_create_file("set_time", 0444, ddrfreq_root,
|
|
NULL, &set_time_fops)) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out:
|
|
debugfs_remove_recursive(ddrfreq_root);
|
|
return err;
|
|
}
|
|
|
|
static void __exit debug_exit(void)
|
|
{
|
|
debugfs_remove_recursive(ddrfreq_root);
|
|
}
|
|
|
|
late_initcall(debug_init);
|
|
module_exit(debug_exit);
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("SUNXI ddrfreq driver with devfreq framework");
|
|
MODULE_AUTHOR("pannan");
|