SmartAudio/lichee/linux-4.9/drivers/devfreq/sunxi-dramfreq.c

1758 lines
44 KiB
C
Executable File

/*
* drivers/devfreq/ddrfreq/sunxi_dramfreq.c
*
* Copyright(c) 2013-2015 Allwinnertech Co., Ltd.
*
* Author: Pan Nan <pannan@allwinnertech.com>
*
* SUNXI dram 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/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/interrupt.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/sunxi_dramfreq.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
enum {
DEBUG_NONE = 0,
DEBUG_FREQ = 1,
};
static int debug_mask = DEBUG_FREQ;
module_param(debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
#define DRAMFREQ_DBG(mask, format, args...) \
do { if (mask & debug_mask) printk("[dramfreq] "format, ##args); } while (0)
#define DRAMFREQ_ERR(format, args...) \
printk(KERN_ERR "[dramfreq] ERR:"format, ##args)
#define SUNXI_DRAMFREQ_MDFS_RETRIES (3)
static unsigned long sunxi_dramfreq_table[LV_END] = {
672000, /*LV_0*/
480000, /*LV_1*/
336000, /*LV_2*/
240000, /*LV_3*/
168000, /*LV_4*/
};
/**
* @key_masters_int_idx
* the key_masters_int_idx:
* first dimension that is master what need to interrupt.
* second dimension that is property what we care.
* eg:
* master master_access_bit in_which_list_num
*/
#if defined(CONFIG_ARCH_SUN8IW10)
static int key_masters_int_idx[MASTER_MAX][3] = {
#ifdef CONFIG_EINK_PANEL_USED
{ MASTER_EINK0, 5, 0 },
{ MASTER_EDMA, 6, 0 },
{ MASTER_EINK1, 7, 0 },
#else
{ MASTER_DE, 9, 0 },
#endif
{ MASTER_CSI, 3, 0 },
};
#elif defined(CONFIG_ARCH_SUN8IW11)
static int key_masters_int_idx[MASTER_MAX][3] = {
{ MASTER_GPU, 1, 0 },
{ MASTER_CSI, 5, 0 },
{ MASTER_DE, 13, 0 },
};
#elif defined(CONFIG_ARCH_SUN50IW2)
static int key_masters_int_idx[MASTER_MAX][3] = {
{ MASTER_GPU, 1, 0 },
{ MASTER_CSI, 5, 0 },
{ MASTER_DE, 10, 0 },
};
#elif defined(CONFIG_ARCH_SUN50IW3)
static int key_masters_int_idx[MASTER_MAX][3] = {
{ MASTER_GPU, 1, 0 },
{ MASTER_VE, 4, 0 },
{ MASTER_DE, 16, 0 },
};
#elif defined(CONFIG_ARCH_SUN50IW6)
/* the sun50iw6 doesn't support dramfreq, but need to offer cur_freq attr */
static int key_masters_int_idx[MASTER_MAX][3] = {
{ MASTER_NULL, -1, -1 },
};
#endif
struct sunxi_dramfreq *dramfreq;
struct task_struct *sunxi_dramfreq_task;
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
#define MDFS_LEAST_TIME_RETRIES (5)
#define MDFS_LEAST_TIME_US (2000)
struct dramfreq_vb_time_ops {
int (*get_vb_time) (void);
int (*get_next_vb_time) (void);
int (*is_in_vb) (void);
} dramfreq_vbtime_ops;
int dramfreq_set_vb_time_ops(struct dramfreq_vb_time_ops *ops)
{
dramfreq_vbtime_ops.get_vb_time = ops->get_vb_time;
dramfreq_vbtime_ops.get_next_vb_time = ops->get_next_vb_time;
dramfreq_vbtime_ops.is_in_vb = ops->is_in_vb;
return 0;
}
EXPORT_SYMBOL(dramfreq_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;
};
static struct busfreq_table busfreq_tbl[] = {
{ .name = "ahb1", .normal_freq = 200000000, .idle_freq = 50000000 },
};
static struct clk *clk_ahb1;
static int sunxi_dramfreq_busfreq_target(const char *name, struct clk *clk,
unsigned long target_freq)
{
unsigned long cur_freq;
mutex_lock(&busfreq_lock);
if (clk_prepare_enable(clk)) {
DRAMFREQ_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 (clk_set_rate(clk, target_freq)) {
DRAMFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq);
goto err;
}
cur_freq = clk_get_rate(clk);
if (cur_freq != target_freq) {
DRAMFREQ_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_SMP
struct cpumask dramfreq_ipi_mask;
static volatile bool cpu_pause[NR_CPUS];
static volatile bool pause_flag;
static bool mdfs_is_paused(void)
{
smp_rmb();
return pause_flag;
}
static void mdfs_set_paused(bool pause)
{
pause_flag = pause;
smp_wmb();
}
static bool mdfs_cpu_is_paused(unsigned int cpu)
{
smp_rmb();
return cpu_pause[cpu];
}
static void mdfs_cpu_set_paused(unsigned int cpu, bool pause)
{
cpu_pause[cpu] = pause;
smp_wmb();
}
static void mdfs_cpu_pause(void *info)
{
unsigned int cpu = raw_smp_processor_id();
dsb(sy);
isb();
mdfs_cpu_set_paused(cpu, true);
while (mdfs_is_paused())
;
mdfs_cpu_set_paused(cpu, false);
}
static void mdfs_cpu_wait(void *info) {}
#endif /* CONFIG_SMP */
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
int dramfreq_master_access(enum DRAM_KEY_MASTER master, bool access)
{
if (dramfreq == NULL)
return -EINVAL;
dramfreq->key_masters[master] = access ? 1 : 0;
if (!dramfreq->pause)
wake_up_process(sunxi_dramfreq_task);
return 0;
}
EXPORT_SYMBOL(dramfreq_master_access);
#endif
static int mdfs_cfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq,
unsigned int freq)
{
volatile unsigned int reg_val;
unsigned int i, div, n = 4;
unsigned int rank_num, trefi, trfc, ctrl_freq;
unsigned int timeout = 1000000000;
struct dram_para_t *para = &dramfreq->dram_para;
/* wait for process finished */
do {
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1;
} while (reg_val && timeout--);
if (timeout == 0)
return -EBUSY;
/*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(dramfreq->dramcom_base + MC_MDFSCR);
reg_val |= (0x1U<<15);
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
/* change refresh timing */
reg_val = readl(dramfreq->dramctl_base + RFSHTMG);
reg_val &= ~((0xfff<<0)|(0xfff<<16));
reg_val |= ((trfc<<0)|(trefi<<16));
writel(reg_val, dramfreq->dramctl_base + 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(dramfreq->dramctl_base + DXnGCR0(i));
reg_val &= ~(0x3U<<4);
reg_val |= (0x0<<4);/*ODT dynamic*/
writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
}
rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1;
if (rank_num)
writel(0x00000303, dramfreq->dramctl_base + ODTMAP);
else
writel(0x00000201, dramfreq->dramctl_base + ODTMAP);
}
} else {
if ((para->dram_odt_en & 0x1)) {
for (i = 0; i < n; i++) {
/*byte 0/byte 1*/
reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
reg_val &= ~(0x3U<<4);
reg_val |= (0x2<<4);
writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
}
writel(0x0, dramfreq->dramctl_base + ODTMAP);
}
}
}
/* change pll-ddr N value */
div = freq / 12;
reg_val = readl(dramfreq->ccu_base + CCM_PLL_DDR1_REG);
reg_val &= ~(0x7f<<8);
reg_val |= (((div-1)<<8));
writel(reg_val, dramfreq->ccu_base + CCM_PLL_DDR1_REG);
/* setting MDFS configuration */
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
reg_val &= ~(0x1 << 2);
reg_val |= ((freq_jump & 0x1) << 2); /*1: increase 0:decrease*/
reg_val |= (0x1 << 1); /*CFS mode*/
reg_val |= (0x1 << 11); /*vtf enable*/
reg_val |= (0x1 << 4); /*bypass*/
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
reg_val |= (0x1U<<0);
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
return 0;
}
static int mdfs_dfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq,
unsigned int freq)
{
volatile unsigned int reg_val;
unsigned int trefi, trfc, ctrl_freq;
unsigned int i, n = 4;
unsigned int div, source;
unsigned int vtf_status;
unsigned int timeout = 1000000;
struct dram_para_t *para = &dramfreq->dram_para;
#if !defined(CONFIG_ARCH_SUN50IW3)
unsigned int rank_num;
#endif
/* 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 = !((para->dram_tpr13 >> 6) & 0x1);
div = para->dram_tpr9 / freq ;
} else {
source = (para->dram_tpr13 >> 6) & 0x1;
div = para->dram_clk / freq ;
}
} else if ((para->dram_clk % freq) == 0) {
source = (para->dram_tpr13 >> 6) & 0x1;
div = para->dram_clk / freq ;
} else if ((para->dram_tpr9 % freq) == 0) {
source = !((para->dram_tpr13 >> 6) & 0x1);
div = para->dram_tpr9 / freq ;
} else {
DRAMFREQ_ERR("unsupported freq!\n");
return 1;
}
} else {
#if defined(CONFIG_ARCH_SUN50IW1)
source = 1;
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3)
source = (para->dram_tpr13 >> 6) & 0x1;
#endif
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);
}
/* turn off vtf */
reg_val = readl(dramfreq->dramctl_base + VTFCR);
#if defined(CONFIG_ARCH_SUN50IW1)
vtf_status = (reg_val & (0x1<<8));
if (vtf_status) {
reg_val &= ~(0x1<<8);
writel(reg_val, dramfreq->dramctl_base + VTFCR);
}
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3)
vtf_status = (reg_val & (0x3<<8));
if (vtf_status) {
reg_val &= ~(0x3<<8);
writel(reg_val, dramfreq->dramctl_base + VTFCR);
}
#endif
/* set dual buffer for timing change and power save */
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
/* VTC dual buffer can not be used */
reg_val |= (0x1U << 15);
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
/* change refresh timing */
reg_val = readl(dramfreq->dramctl_base + RFSHTMG);
reg_val &= ~((0xfff<<0)|(0xfff<<16));
reg_val |= ((trfc<<0)|(trefi<<16));
writel(reg_val, dramfreq->dramctl_base + RFSHTMG);
#if defined(CONFIG_ARCH_SUN50IW3)
reg_val = ((trefi << 15) & 0x0fff0000);
writel(reg_val, dramfreq->dramctl_base + RFSHCTL1);
#endif
/* change ODT status for power save */
if (!((para->dram_tpr13 >> 12) & 0x1)) {
#if !defined(CONFIG_ARCH_SUN50IW3)
if (freq > 400) {
#else
if (freq > 600) {
#endif
if ((para->dram_odt_en & 0x1)) {
for (i = 0; i < n; i++) {
/* byte 0/byte 1 */
reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
reg_val &= ~(0x3U << 4);
reg_val |= (0x0 << 4);/*ODT dynamic*/
writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
}
#if !defined(CONFIG_ARCH_SUN50IW3)
rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1;
if (rank_num)
writel(0x00000303, dramfreq->dramctl_base + ODTMAP);
else
writel(0x00000201, dramfreq->dramctl_base + ODTMAP);
#endif
}
} else {
if ((para->dram_odt_en & 0x1)) {
for (i = 0; i < n; i++) {
/* byte 0/byte 1*/
reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
reg_val &= ~(0x3U<<4);
reg_val |= (0x2<<4);
writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
}
#if !defined(CONFIG_ARCH_SUN50IW3)
writel(0x0, dramfreq->dramctl_base + ODTMAP);
#endif
}
}
}
/* set the DRAM_CFG_REG divider in CCMU */
reg_val = readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG);
reg_val &= ~(0xf<<0);
reg_val |= ((div-1)<<0);
writel(reg_val, dramfreq->ccu_base + CCM_DRAM_CFG_REG);
/* set clock source in CCMU */
reg_val &= ~(0x3<<CCM_DRAM_CFG_REG_PLL0_1_BIT);
reg_val |= (source<<CCM_DRAM_CFG_REG_PLL0_1_BIT);
writel(reg_val, dramfreq->ccu_base + CCM_DRAM_CFG_REG);
/* set MDFS register */
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
reg_val |= (0x1 << 4); /*bypass*/
#if !defined(CONFIG_ARCH_SUN50IW3)
reg_val |= (0x1 << 13); /*pad hold*/
#endif
reg_val &= ~(0x1U << 1); /*DFS mode*/
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
reg_val |= (0x1U << 0); /* start mdfs */
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
/* wait for process finished */
while (timeout--) {
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1;
if (!reg_val)
break;
}
if (timeout == 0)
return -EBUSY;
/* turn off dual buffer */
reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
reg_val &= ~(0x1U<<15);
writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);
/* turn on vtf */
if (vtf_status) {
#if defined(CONFIG_ARCH_SUN50IW1)
reg_val = readl(dramfreq->dramctl_base + VTFCR);
vtf_status |= (0x1<<8);
writel(reg_val, dramfreq->dramctl_base + VTFCR);
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
defined(CONFIG_ARCH_SUN50IW2) || defined(CONFIG_ARCH_SUN50IW3)
reg_val = readl(dramfreq->dramctl_base + VTFCR);
vtf_status |= (0x3<<8);
writel(reg_val, dramfreq->dramctl_base + VTFCR);
#endif
}
return 0;
}
static int sunxi_dramfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
unsigned long pll_ddr_rate;
unsigned int dram_div_m;
#ifdef CONFIG_DEBUG_FS
ktime_t calltime = ktime_get();
#endif
if ((readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG) >> CCM_DRAM_CFG_REG_PLL0_1_BIT) & 0x1)
pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr1) / 1000;
else
pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr0) / 1000;
dram_div_m = (readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG) & 0x3) + 1;
*freq = pll_ddr_rate / 2 / dram_div_m;
#ifdef CONFIG_DEBUG_FS
dramfreq->dramfreq_get_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
#endif
return 0;
}
unsigned long dramfreq_get(void)
{
unsigned long cur_freq;
if (dramfreq == NULL)
return 0;
if (dramfreq->ccu_base == NULL
|| dramfreq->clk_pll_ddr0 == NULL
#if !defined(CONFIG_ARCH_SUN50IW6)
|| dramfreq->clk_pll_ddr1 == NULL
#endif
)
return 0;
sunxi_dramfreq_get_cur_freq(NULL, &cur_freq);
return cur_freq;
}
EXPORT_SYMBOL_GPL(dramfreq_get);
static int sunxi_dramfreq_get_max_freq(void)
{
return sunxi_dramfreq_table[0];
}
static int sunxi_dramfreq_get_min_freq(void)
{
unsigned int i, min = UINT_MAX;
for (i = 0; i < LV_END; i++) {
if (sunxi_dramfreq_table[i] == 0)
continue;
if (sunxi_dramfreq_table[i] < min)
min = sunxi_dramfreq_table[i];
}
return min;
}
static int sunxi_dramfreq_get_valid_freq(unsigned long freq)
{
unsigned long *valid_freq = &sunxi_dramfreq_table[0];
int i = 0;
while (*(valid_freq+1) >= freq) {
i++;
if (i >= LV_END)
break;
valid_freq++;
}
return *valid_freq;
}
static int sunxi_dramfreq_set_rate(unsigned int jump, unsigned int freq_target,
struct sunxi_dramfreq *dramfreq, unsigned int *masters_access)
{
int i, ret = 0;
s64 mdfs_time_us = 0;
ktime_t calltime;
#ifdef CONFIG_SMP
unsigned int cpu, cur_cpu, timeout = 0;
#endif
#ifdef CONFIG_SMP
cpumask_clear(&dramfreq_ipi_mask);
cpumask_copy(&dramfreq_ipi_mask, cpu_online_mask);
cur_cpu = raw_smp_processor_id();
cpumask_clear_cpu(cur_cpu, &dramfreq_ipi_mask);
local_bh_disable();
mdfs_set_paused(true);
preempt_disable();
smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_pause, NULL, 0);
preempt_enable();
dsb(sy);
isb();
for_each_online_cpu(cpu) {
if (cpu == cur_cpu)
continue;
while (!mdfs_cpu_is_paused(cpu) && timeout < 100) {
udelay(100);
timeout++;
}
if (timeout >= 100) {
DRAMFREQ_ERR("Pause cpu%d time out!\n", cpu);
ret = -EAGAIN;
goto out;
}
}
#endif
if (jump == FREQ_DOWN) {
for (i = 0; i < MASTER_MAX; i++) {
if (masters_access[i] < dramfreq->key_masters[i]) {
ret = -EINVAL;
goto out;
}
}
}
local_irq_disable();
if (dramfreq->mode == DFS_MODE) {
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
while (!dramfreq_vbtime_ops.is_in_vb())
;
#endif
}
calltime = ktime_get();
flush_tlb_all();
isb();
if (dramfreq->mode == CFS_MODE)
ret = mdfs_cfs(jump, dramfreq, freq_target / 1000);
else if (dramfreq->mode == DFS_MODE)
ret = mdfs_dfs(jump, dramfreq, freq_target / 1000);
mdfs_time_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
local_irq_enable();
#ifdef CONFIG_SMP
DRAMFREQ_DBG(DEBUG_FREQ, "[cpu%d] elapsed:%lldus\n", cur_cpu, mdfs_time_us);
#else
DRAMFREQ_DBG(DEBUG_FREQ, "[cpu0] elapsed:%lldus\n", mdfs_time_us);
#endif
#ifdef CONFIG_DEBUG_FS
dramfreq->dramfreq_set_us = mdfs_time_us;
#endif
out:
#ifdef CONFIG_SMP
mdfs_set_paused(false);
local_bh_enable();
preempt_disable();
smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_wait, NULL, true);
preempt_enable();
#endif
return ret;
}
static int sunxi_dramfreq_target(struct device *dev,
unsigned long *freq, u32 flags)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
int i, ret = 0, retries = 0, jump = 0;
unsigned int masters_access[MASTER_MAX];
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
int next_vbtime_us = 0, next_vb_retries = 0;
#endif
unsigned long cur_freq, valid_freq;
#ifdef CONFIG_CPU_FREQ
struct cpufreq_policy *policy;
#endif
#if defined(CONFIG_ARCH_SUN50IW6)
/* the sun50iw6 doesn't support dramfreq, but need to offer cur_freq attr */
return 0;
#endif
for (i = 0; i < MASTER_MAX; i++)
masters_access[i] = dramfreq->key_masters[i];
if ((dramfreq == NULL) || (dramfreq->devfreq == NULL))
return -EINVAL;
mutex_lock(&dramfreq->lock);
get_online_cpus();
valid_freq = sunxi_dramfreq_get_valid_freq(*freq);
if (valid_freq == dramfreq->devfreq->previous_freq) {
if (*freq != valid_freq)
*freq = valid_freq;
goto unlock;
}
jump = (valid_freq > dramfreq->devfreq->previous_freq) ? FREQ_UP : FREQ_DOWN;
DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz start\n",
dramfreq->devfreq->previous_freq, valid_freq);
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
if ((dramfreq->devfreq->previous_freq == dramfreq->devfreq->min_freq) &&
(jump == FREQ_UP)) {
for (i = ARRAY_SIZE(busfreq_tbl) - 1; i >= 0; i--) {
sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name,
busfreq_tbl[i].bus_clk, busfreq_tbl[i].normal_freq);
}
DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000);
}
#endif
if (dramfreq->mode == DFS_MODE) {
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
if (!(dramfreq_vbtime_ops.get_next_vb_time
&& dramfreq_vbtime_ops.is_in_vb)) {
DRAMFREQ_ERR("dramfreq_vbtime_ops is not initialized!\n");
ret = -EINVAL;
goto unlock;
}
do {
next_vbtime_us = dramfreq_vbtime_ops.get_next_vb_time();
if (next_vbtime_us < MDFS_LEAST_TIME_US) {
next_vb_retries++;
msleep(1);
} else {
break;
}
} while (next_vb_retries < MDFS_LEAST_TIME_RETRIES);
if (next_vb_retries >= MDFS_LEAST_TIME_RETRIES) {
DRAMFREQ_ERR("Retrying next vb time failed, next time!\n");
ret = -EINVAL;
goto unlock;
} else {
usleep_range(next_vbtime_us - MDFS_LEAST_TIME_US,
next_vbtime_us - MDFS_LEAST_TIME_US);
}
#endif
}
#ifdef CONFIG_CPU_FREQ
policy = cpufreq_cpu_get(0);
if (policy && (policy->cur < policy->max)) {
cpufreq_driver_target(policy, policy->max, CPUFREQ_RELATION_H);
cpufreq_cpu_put(policy);
}
#endif
do {
ret = sunxi_dramfreq_set_rate(jump, valid_freq, dramfreq, masters_access);
if (ret == -EAGAIN)
retries++;
else
break;
} while (retries < SUNXI_DRAMFREQ_MDFS_RETRIES);
if (retries >= SUNXI_DRAMFREQ_MDFS_RETRIES) {
DRAMFREQ_ERR("Retrying mdfs failed, next time!\n");
ret = -EINVAL;
goto unlock;
}
if (ret == -EBUSY) {
DRAMFREQ_ERR("mdfs timeout\n");
goto unlock;
} else if (ret == -EINVAL) {
DRAMFREQ_DBG(DEBUG_FREQ, "no need for freq down\n");
goto unlock;
}
sunxi_dramfreq_get_cur_freq(dev, &cur_freq);
if (cur_freq != valid_freq) {
DRAMFREQ_ERR("current freq is %lu != %lu\n", cur_freq, valid_freq);
goto unlock;
}
if (*freq != cur_freq)
*freq = cur_freq;
DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz ok\n",
dramfreq->devfreq->previous_freq, cur_freq);
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
if ((cur_freq == dramfreq->devfreq->min_freq) && (jump == FREQ_DOWN)) {
for (i = 0; i < ARRAY_SIZE(busfreq_tbl); i++) {
sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name,
busfreq_tbl[i].bus_clk, busfreq_tbl[i].idle_freq);
}
DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000);
}
#endif
unlock:
put_online_cpus();
mutex_unlock(&dramfreq->lock);
return ret;
}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
extern int sunxi_get_sensor_temp(u32 sensor_num, long *temperature);
bool temptrigger_initialized;
static int sunxi_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat)
{
int i, ret;
long temp = 0, max_temp = 0, std_temp = 0;
for (i = 0; i < 3; i++) {
ret = sunxi_get_sensor_temp(i, &temp);
if (ret == 0)
max_temp = max(max_temp, temp);
else
break;
}
stat->private_data = temptrigger_initialized ? &max_temp : &std_temp;
return 0;
}
#endif
static struct devfreq_dev_profile sunxi_dramfreq_profile = {
.get_cur_freq = sunxi_dramfreq_get_cur_freq,
.target = sunxi_dramfreq_target,
.freq_table = sunxi_dramfreq_table,
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
.get_dev_status = sunxi_get_dev_status,
#endif
.max_state = LV_END,
};
extern int update_devfreq(struct devfreq *devfreq);
static int sunxi_dramfreq_task_func(void *data)
{
struct devfreq *df = (struct devfreq *)data;
while (1) {
mutex_lock(&df->lock);
update_devfreq(df);
mutex_unlock(&df->lock);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
if (kthread_should_stop())
break;
set_current_state(TASK_RUNNING);
}
return 0;
}
static void sunxi_dramfreq_masters_state_init(struct sunxi_dramfreq *dramfreq)
{
int i;
for (i = 0; i < MASTER_MAX; i++)
#if defined(CONFIG_ARCH_SUN50IW3)
dramfreq->key_masters[i] = (i == MASTER_DE) ? 1 : 0;
#else
dramfreq->key_masters[i] = (i == MASTER_CSI) ? 0 : 1;
#endif
}
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
static void sunxi_dramfreq_irq_clear_flag(struct sunxi_dramfreq *dramfreq)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++) {
writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(i));
writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(i));
}
}
static void sunxi_dramfreq_read_irq_status(struct sunxi_dramfreq *dramfreq)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++) {
dramfreq->irq_access_status[i] =
readl(dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(i));
dramfreq->irq_idle_status[i] =
readl(dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(i));
}
}
static void sunxi_dramfreq_read_irq_mask_status(struct sunxi_dramfreq *dramfreq)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++) {
dramfreq->irq_access_mask_sta[i] =
readl(dramfreq->dramcom_base + MDFS_IRQ_ACCESS_MASK_STA(i));
dramfreq->irq_idle_mask_sta[i] =
readl(dramfreq->dramcom_base + MDFS_IRQ_IDLE_MASK_STA(i));
}
}
static void sunxi_dramfreq_write_irq_mask_status(struct sunxi_dramfreq *dramfreq)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++) {
writel(dramfreq->irq_access_mask_sta[i],
dramfreq->dramcom_base + MDFS_IRQ_ACCESS_MASK_STA(i));
writel(dramfreq->irq_idle_mask_sta[i],
dramfreq->dramcom_base + MDFS_IRQ_IDLE_MASK_STA(i));
}
}
static void sunxi_dramfreq_read_irq_enable_status(struct sunxi_dramfreq *dramfreq,
unsigned int *reg_value)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++)
*(reg_value + i) = readl(dramfreq->dramcom_base + MDFS_MASTER_ENABLE(i));
}
static void sunxi_dramfreq_write_irq_enable_status(struct sunxi_dramfreq *dramfreq,
unsigned int *reg_value)
{
int i;
for (i = 0; i < dramfreq->master_reg_num; i++)
writel(*(reg_value + i), dramfreq->dramcom_base + MDFS_MASTER_ENABLE(i));
}
static void sunxi_dramfreq_irq_mask_control(struct sunxi_dramfreq *dramfreq,
bool valid)
{
unsigned int i;
int idx, reg_list_num;
sunxi_dramfreq_read_irq_mask_status(dramfreq);
for (i = 0; i < MASTER_MAX; i++) {
idx = key_masters_int_idx[i][1];
reg_list_num = key_masters_int_idx[i][2];
if (idx < 0 || reg_list_num < 0) {
pr_err("%s: the key master doesn't exit", __func__);
return;
}
if (valid) {
dramfreq->irq_access_mask_sta[reg_list_num] |= (0x1 << idx);
dramfreq->irq_idle_mask_sta[reg_list_num] |= (0x1 << idx);
} else {
dramfreq->irq_access_mask_sta[reg_list_num] &= (~(0x1 << idx));
dramfreq->irq_idle_mask_sta[reg_list_num] &= (~(0x1 << idx));
}
}
sunxi_dramfreq_write_irq_mask_status(dramfreq);
}
static void sunxi_dramfreq_irq_enable_control(struct sunxi_dramfreq *dramfreq,
bool enable)
{
unsigned int i;
int idx, reg_list_num;
unsigned int reg_value[MASTER_REG_NUM] = {0};
sunxi_dramfreq_read_irq_enable_status(dramfreq, reg_value);
for (i = 0; i < MASTER_MAX; i++) {
idx = key_masters_int_idx[i][1];
reg_list_num = key_masters_int_idx[i][2];
if (idx < 0 || reg_list_num < 0) {
pr_err("%s: the key master doesn't exit", __func__);
return;
}
if (enable)
reg_value[reg_list_num] |= (0x1 << idx);
else
reg_value[reg_list_num] &= (~(0x1 << idx));
}
sunxi_dramfreq_write_irq_enable_status(dramfreq, reg_value);
}
static void
sunxi_dramfreq_irq_judgment_and_clear(struct sunxi_dramfreq *dramfreq, bool *phandled)
{
int idx, reg_list_num;
unsigned int i;
for (i = 0; i < MASTER_MAX; i++) {
idx = key_masters_int_idx[i][1];
reg_list_num = key_masters_int_idx[i][2];
if (idx < 0 || reg_list_num < 0) {
pr_err("%s: the key master doesn't exit", __func__);
return;
}
if ((dramfreq->irq_access_status[reg_list_num] >> idx) & 0x1) {
dramfreq->key_masters[i] = 1;
dramfreq->irq_access_status[reg_list_num] |= (0x1 << idx);
writel(dramfreq->irq_access_status[reg_list_num],
dramfreq->dramcom_base + MDFS_IRQ_ACCESS_STATUS(reg_list_num));
*phandled = true;
} else if ((dramfreq->irq_idle_status[reg_list_num] >> idx) & 0x1) {
dramfreq->key_masters[i] = 0;
dramfreq->irq_idle_status[reg_list_num] |= (0x1 << idx);
writel(dramfreq->irq_idle_status[reg_list_num],
dramfreq->dramcom_base + MDFS_IRQ_IDLE_STATUS(reg_list_num));
*phandled = true;
}
}
}
static irqreturn_t sunxi_dramfreq_irq_handler(int irq, void *data)
{
struct sunxi_dramfreq *dramfreq = (struct sunxi_dramfreq *)data;
bool handled = false;
sunxi_dramfreq_read_irq_status(dramfreq);
sunxi_dramfreq_irq_judgment_and_clear(dramfreq, &handled);
if (!handled) {
int i;
for (i = 0; i < dramfreq->master_reg_num; i++) {
DRAMFREQ_ERR("(IRQ) irq_access_sta%d=0x%x, irq_idle_sta%d=0x%x\n",
i, dramfreq->irq_access_status[i],
i, dramfreq->irq_idle_status[i]);
}
return IRQ_NONE;
}
if (!dramfreq->pause)
wake_up_process(sunxi_dramfreq_task);
return IRQ_HANDLED;
}
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY */
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
struct delayed_work sunxi_temp_work;
struct workqueue_struct *dramfreq_temp_workqueue;
static void sunxi_dramfreq_temp_work_func(struct work_struct *work)
{
if ((dramfreq == NULL) || (dramfreq->devfreq == NULL)) {
DRAMFREQ_ERR("%s: para error\n", __func__);
return;
}
mutex_lock(&dramfreq->devfreq->lock);
update_devfreq(dramfreq->devfreq);
mutex_unlock(&dramfreq->devfreq->lock);
queue_delayed_work_on(0, dramfreq_temp_workqueue,
&sunxi_temp_work, msecs_to_jiffies(100));
}
#endif
static int
sunxi_dramfreq_governor_state_update(char *name, enum GOVERNOR_STATE type)
{
if (!strcmp(name, "adaptive")) {
switch (type) {
case STATE_INIT:
dramfreq->pause = 1;
sunxi_dramfreq_task =
kthread_create(sunxi_dramfreq_task_func,
dramfreq->devfreq, "dramfreq_task");
if (IS_ERR(sunxi_dramfreq_task))
return PTR_ERR(sunxi_dramfreq_task);
get_task_struct(sunxi_dramfreq_task);
wake_up_process(sunxi_dramfreq_task);
break;
case STATE_RUNNING:
dramfreq->pause = 0;
wake_up_process(sunxi_dramfreq_task);
/* set master access init state */
sunxi_dramfreq_masters_state_init(dramfreq);
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq mask invalid */
sunxi_dramfreq_irq_mask_control(dramfreq, false);
/* clear irq flag */
sunxi_dramfreq_irq_clear_flag(dramfreq);
/* set irq enable */
sunxi_dramfreq_irq_enable_control(dramfreq, true);
#endif
break;
case STATE_PAUSE:
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq disable */
sunxi_dramfreq_irq_enable_control(dramfreq, false);
/* set irq mask valid */
sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
dramfreq->pause = 1;
wake_up_process(sunxi_dramfreq_task);
break;
case STATE_EXIT:
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq disable */
sunxi_dramfreq_irq_enable_control(dramfreq, false);
/* set irq mask valid */
sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
dramfreq->pause = 1;
mutex_lock(&dramfreq->devfreq->lock);
update_devfreq(dramfreq->devfreq);
mutex_unlock(&dramfreq->devfreq->lock);
kthread_stop(sunxi_dramfreq_task);
put_task_struct(sunxi_dramfreq_task);
break;
}
}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
else if (!strcmp(name, "temptrigger")) {
switch (type) {
case STATE_INIT:
temptrigger_initialized = true;
dramfreq_temp_workqueue =
create_singlethread_workqueue("dramfreq_temp");
INIT_DELAYED_WORK(&sunxi_temp_work,
sunxi_dramfreq_temp_work_func);
queue_delayed_work_on(0, dramfreq_temp_workqueue,
&sunxi_temp_work, msecs_to_jiffies(100));
break;
case STATE_RUNNING:
case STATE_PAUSE:
break;
case STATE_EXIT:
temptrigger_initialized = false;
mutex_lock(&dramfreq->devfreq->lock);
update_devfreq(dramfreq->devfreq);
mutex_unlock(&dramfreq->devfreq->lock);
cancel_delayed_work_sync(&sunxi_temp_work);
destroy_workqueue(dramfreq_temp_workqueue);
break;
}
}
#endif
return 0;
}
static int sunxi_dramfreq_paras_init(struct sunxi_dramfreq *dramfreq)
{
struct device_node *dram_np;
int ret = 0;
dram_np = of_find_node_by_path("/dram");
if (!dram_np) {
DRAMFREQ_ERR("Get dram node failed!\n");
return -ENODEV;
}
if (of_property_read_u32(dram_np, "dram_clk",
&dramfreq->dram_para.dram_clk)) {
DRAMFREQ_ERR("Get dram_clk failed!\n");
ret = -ENODEV;
goto out_put_node;
}
if (of_property_read_u32(dram_np, "dram_type",
&dramfreq->dram_para.dram_type)) {
DRAMFREQ_ERR("Get dram_type failed!\n");
ret = -ENODEV;
goto out_put_node;
}
if (of_property_read_u32(dram_np, "dram_odt_en",
&dramfreq->dram_para.dram_odt_en)) {
DRAMFREQ_ERR("Get dram_odt_en failed!\n");
ret = -ENODEV;
goto out_put_node;
}
if (of_property_read_u32(dram_np, "dram_tpr9",
&dramfreq->dram_para.dram_tpr9)) {
DRAMFREQ_ERR("Get dram_tpr9 failed!\n");
ret = -ENODEV;
goto out_put_node;
}
if (of_property_read_u32(dram_np, "dram_tpr13",
&dramfreq->dram_para.dram_tpr13)) {
DRAMFREQ_ERR("Get dram_tpr13 failed!\n");
ret = -ENODEV;
goto out_put_node;
}
if (!((dramfreq->dram_para.dram_tpr13 >> 11) & 0x1)) {
ret = -EINVAL;
goto out_put_node;
}
dramfreq->mode = (dramfreq->dram_para.dram_tpr13 >> 10) & 0x1;
out_put_node:
of_node_put(dram_np);
return ret;
}
static int sunxi_dramfreq_resource_init(struct platform_device *pdev,
struct sunxi_dramfreq *dramfreq)
{
int ret = 0;
dramfreq->dramcom_base = of_iomap(pdev->dev.of_node, 0);
if (!dramfreq->dramcom_base) {
DRAMFREQ_ERR("Map dramcom_base failed!\n");
ret = -EBUSY;
goto out;
}
dramfreq->dramctl_base = of_iomap(pdev->dev.of_node, 1);
if (!dramfreq->dramctl_base) {
DRAMFREQ_ERR("Map dramctl_base failed!\n");
ret = -EBUSY;
goto out;
}
dramfreq->ccu_base = of_iomap(pdev->dev.of_node, 2);
if (!dramfreq->ccu_base) {
DRAMFREQ_ERR("Map ccu_base failed!\n");
ret = -EBUSY;
goto out;
}
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
dramfreq->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (!dramfreq->irq) {
DRAMFREQ_ERR("Map IRQ failed!\n");
ret = -EBUSY;
goto out;
}
#endif
dramfreq->clk_pll_ddr0 = of_clk_get(pdev->dev.of_node, 0);
if (IS_ERR(dramfreq->clk_pll_ddr0)) {
DRAMFREQ_ERR("Get clk_pll_ddr0 failed!\n");
ret = -EINVAL;
goto out;
}
#if !defined(CONFIG_ARCH_SUN50IW6)
dramfreq->clk_pll_ddr1 = of_clk_get(pdev->dev.of_node, 1);
if (IS_ERR(dramfreq->clk_pll_ddr1)) {
DRAMFREQ_ERR("Get clk_pll_ddr1 failed!\n");
ret = -EINVAL;
goto out;
}
#endif
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
clk_ahb1 = of_clk_get(pdev->dev.of_node, 2);
if (IS_ERR(clk_ahb1)) {
DRAMFREQ_ERR("Get clk_ahb1 failed!\n");
ret = -EINVAL;
goto out;
}
busfreq_tbl[0].bus_clk = clk_ahb1;
#endif
out:
return ret;
}
static void __array_insert_sort(unsigned int *array, int count)
{
unsigned int i, j, temp;
for (i = 1; i < count; i++) {
temp = array[i];
j = i-1;
while (array[j] < temp && j >= 0) {
array[j+1] = array[j];
j--;
}
if (j != (i-1))
array[j+1] = temp;
}
}
static void __array_delete_repeat(unsigned int *array, int count)
{
int i, j;
for (i = 0, j = 0; i < count; i++) {
while (array[i] == array[i+1])
i++;
array[j++] = array[i];
}
for (i = j; i < count; i++)
array[i] = 0;
}
static int sunxi_dramfreq_opp_init(struct platform_device *pdev,
struct sunxi_dramfreq *dramfreq)
{
int i, j, ret = 0;
unsigned int tmp_table[LV_END], tmp_max_state = 0;
for (i = 0; i < 3; i++)
tmp_table[i] = dramfreq->dram_para.dram_clk * 1000 / (i + 1);
if ((dramfreq->dram_para.dram_clk * 1000 / (i + 1)) >= SUNXI_DRAMFREQ_IDLE)
tmp_table[i-1] = dramfreq->dram_para.dram_clk * 1000 / (i + 1);
for (j = 0; i < LV_END; i++, j++) {
if ((dramfreq->dram_para.dram_tpr9 / (j + 1))
> dramfreq->dram_para.dram_clk) {
tmp_table[i] = 0;
} else {
tmp_table[i] = dramfreq->dram_para.dram_tpr9 * 1000 / (j + 1);
}
}
if (dramfreq->mode == CFS_MODE) {
for (i = 0; i < LV_END; i++) {
if ((tmp_table[i] % 12) != 0)
tmp_table[i] = ((tmp_table[i] / 1000) / 12) * 12 * 1000;
}
}
for (i = 0; i < LV_END; i++) {
if (tmp_table[i] < SUNXI_DRAMFREQ_IDLE)
tmp_table[i] = 0;
}
__array_insert_sort(tmp_table, LV_END);
__array_delete_repeat(tmp_table, LV_END);
memcpy(sunxi_dramfreq_table, tmp_table, sizeof(tmp_table));
for (i = 0; i < LV_END; i++) {
if (sunxi_dramfreq_table[i] == 0)
continue;
ret = dev_pm_opp_add(&pdev->dev,
sunxi_dramfreq_table[i] * 1000, 0);
if (ret) {
DRAMFREQ_ERR("Failed to add OPP[%d]\n", i);
goto out;
}
tmp_max_state++;
}
if (tmp_max_state < sunxi_dramfreq_profile.max_state)
sunxi_dramfreq_profile.max_state = tmp_max_state;
out:
return ret;
}
static int sunxi_dramfreq_reboot(struct notifier_block *this,
unsigned long code, void *_cmd)
{
dramfreq->pause = 1;
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq disable */
sunxi_dramfreq_irq_enable_control(dramfreq, false);
/* set irq mask valid */
sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
printk("%s:%s: stop dramfreq done\n", __FILE__, __func__);
return NOTIFY_OK;
}
static struct notifier_block reboot_notifier = {
.notifier_call = sunxi_dramfreq_reboot,
};
static void sunxi_dramfreq_hw_init(struct sunxi_dramfreq *dramfreq)
{
volatile unsigned int reg_val;
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
writel(0x18f, dramfreq->dramcom_base + MC_TIME_MEASUREMENT);
/* set master idle period: 1000ms */
writel(0x3e7, dramfreq->dramcom_base + MDFS_BWC_PRD);
#endif
if (dramfreq->mode == DFS_MODE) {
writel(0xFFFFFFFF, dramfreq->dramcom_base + MC_MDFSMRMR);
/* set DFS time */
reg_val = readl(dramfreq->dramctl_base + PTR2);
reg_val &= ~0x7fff;
reg_val |= (0x7<<10 | 0x7<<5 | 0x7<<0);
writel(reg_val, dramfreq->dramctl_base + PTR2);
}
}
static int sunxi_dramfreq_probe(struct platform_device *pdev)
{
int ret = 0;
dramfreq = kzalloc(sizeof(struct sunxi_dramfreq), GFP_KERNEL);
if (dramfreq == NULL) {
DRAMFREQ_ERR("Allocate memory failed!\n");
return -ENOMEM;
}
ret = sunxi_dramfreq_paras_init(dramfreq);
if (ret == -ENODEV) {
DRAMFREQ_ERR("Init dram para failed!\n");
goto err;
} else if (ret == -EINVAL) {
#if defined(CONFIG_ARCH_SUN50IW6)
pr_info("[ddrfreq] disabled, but continue to get freq!\n");
#else
pr_info("[ddrfreq] disabled!\n");
goto err;
#endif
}
ret = sunxi_dramfreq_resource_init(pdev, dramfreq);
if (ret) {
DRAMFREQ_ERR("Init resource failed!\n");
goto err;
}
ret = sunxi_dramfreq_opp_init(pdev, dramfreq);
if (ret) {
DRAMFREQ_ERR("Init opp failed!\n");
goto err;
}
dev_set_name(&pdev->dev, "dramfreq");
platform_set_drvdata(pdev, dramfreq);
sunxi_dramfreq_get_cur_freq(&pdev->dev,
&sunxi_dramfreq_profile.initial_freq);
dramfreq->devfreq =
devfreq_add_device(&pdev->dev, &sunxi_dramfreq_profile,
SUNXI_DRAM_FREQ_GOVERNOR, NULL);
if (IS_ERR(dramfreq->devfreq)) {
DRAMFREQ_ERR("Add devfreq device failed!\n");
ret = PTR_ERR(dramfreq->devfreq);
goto err;
}
dramfreq->max = sunxi_dramfreq_get_max_freq();
dramfreq->min = sunxi_dramfreq_get_min_freq();
dramfreq->devfreq->max_freq = dramfreq->max;
dramfreq->devfreq->min_freq = dramfreq->min;
dramfreq->master_reg_num = MASTER_REG_NUM;
dramfreq->pause = 1;
dramfreq->governor_state_update = sunxi_dramfreq_governor_state_update;
mutex_init(&dramfreq->lock);
spin_lock_init(&dramfreq->master_lock);
register_reboot_notifier(&reboot_notifier);
/* init some hardware paras*/
sunxi_dramfreq_hw_init(dramfreq);
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
if (request_irq(dramfreq->irq, sunxi_dramfreq_irq_handler,
0, "mdfs", dramfreq)) {
DRAMFREQ_ERR("Request IRQ failed!\n");
ret = -EBUSY;
goto err_irq_req;
}
/* set irq mask invalid */
sunxi_dramfreq_irq_mask_control(dramfreq, false);
#endif
return 0;
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
err_irq_req:
mutex_destroy(&dramfreq->lock);
devfreq_remove_device(dramfreq->devfreq);
#endif
err:
kfree(dramfreq);
dramfreq = NULL;
return ret;
}
static int sunxi_dramfreq_remove(struct platform_device *pdev)
{
struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
if (!dramfreq)
return -EINVAL;
mutex_destroy(&dramfreq->lock);
if (dramfreq->devfreq)
devfreq_remove_device(dramfreq->devfreq);
#if !defined(CONFIG_ARCH_SUN50IW6)
if (dramfreq->clk_pll_ddr1) {
clk_put(dramfreq->clk_pll_ddr1);
dramfreq->clk_pll_ddr1 = NULL;
}
#endif
if (dramfreq->clk_pll_ddr0) {
clk_put(dramfreq->clk_pll_ddr0);
dramfreq->clk_pll_ddr0 = NULL;
}
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
if (clk_ahb1) {
clk_put(clk_ahb1);
clk_ahb1 = NULL;
}
#endif
kfree(dramfreq);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id sunxi_dramfreq_match[] = {
{ .compatible = "allwinner,sunxi-dramfreq", },
{},
};
#else
struct platform_device sunxi_dramfreq_device = {
.name = "sunxi-dramfreq",
.id = -1,
};
#endif
#ifdef CONFIG_PM
static bool sunxi_dramfreq_cur_pause;
static int sunxi_dramfreq_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
unsigned long cur_freq, target = dramfreq->max;
int err = -1;
if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) {
sunxi_dramfreq_cur_pause = dramfreq->pause;
if (!sunxi_dramfreq_cur_pause) {
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq disable */
sunxi_dramfreq_irq_enable_control(dramfreq, false);
/* set irq mask valid */
sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
dramfreq->pause = 1;
sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq);
if (cur_freq != target) {
err = sunxi_dramfreq_target(&pdev->dev,
&target, 0);
if (!err)
dramfreq->devfreq->previous_freq = target;
}
}
}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger"))
cancel_delayed_work_sync(&sunxi_temp_work);
#endif
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
static int sunxi_dramfreq_resume(struct platform_device *pdev)
{
struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
unsigned long cur_freq;
/*
* we should init hw,
* otherwise dram hw will block when change freq.
*/
sunxi_dramfreq_hw_init(dramfreq);
if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) {
sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq);
if (dramfreq->devfreq->previous_freq != cur_freq)
dramfreq->devfreq->previous_freq = cur_freq;
if (!sunxi_dramfreq_cur_pause) {
dramfreq->pause = 0;
/* set master access init state */
sunxi_dramfreq_masters_state_init(dramfreq);
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
/* set irq mask invalid */
sunxi_dramfreq_irq_mask_control(dramfreq, false);
/* clear irq flag */
sunxi_dramfreq_irq_clear_flag(dramfreq);
/* set irq enable */
sunxi_dramfreq_irq_enable_control(dramfreq, true);
#endif
}
}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger")) {
INIT_DELAYED_WORK(&sunxi_temp_work,
sunxi_dramfreq_temp_work_func);
queue_delayed_work_on(0, dramfreq_temp_workqueue,
&sunxi_temp_work, msecs_to_jiffies(2000));
}
#endif
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
#endif
static struct platform_driver sunxi_dramfreq_driver = {
.probe = sunxi_dramfreq_probe,
.remove = sunxi_dramfreq_remove,
#ifdef CONFIG_PM
.suspend = sunxi_dramfreq_suspend,
.resume = sunxi_dramfreq_resume,
#endif
.driver = {
.name = "sunxi-dramfreq",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = sunxi_dramfreq_match,
#endif
},
};
static int __init sunxi_dramfreq_initcall(void)
{
int ret = 0;
#ifndef CONFIG_OF
ret = platform_device_register(&sunxi_dramfreq_device);
if (ret) {
DRAMFREQ_ERR("Register dramfreq device failed!\n");
goto out;
}
#endif
ret = platform_driver_register(&sunxi_dramfreq_driver);
if (ret) {
DRAMFREQ_ERR("Register dramfreq driver failed!\n");
goto out;
}
out:
return ret;
}
fs_initcall(sunxi_dramfreq_initcall);
#ifdef CONFIG_DEBUG_FS
static struct dentry *debugfs_dramfreq_root;
static int dramfreq_debugfs_gettime_show(struct seq_file *s, void *data)
{
if (!dramfreq)
seq_printf(s, "Invalid paras\n");
seq_printf(s, "%lld\n", dramfreq->dramfreq_get_us);
return 0;
}
static int
dramfreq_debugfs_gettime_open(struct inode *inode, struct file *file)
{
return single_open(file, dramfreq_debugfs_gettime_show,
inode->i_private);
}
static const struct file_operations dramfreq_debugfs_gettime_fops = {
.open = dramfreq_debugfs_gettime_open,
.read = seq_read,
};
static int dramfreq_debugfs_settime_show(struct seq_file *s, void *data)
{
if (!dramfreq)
seq_printf(s, "Invalid paras\n");
seq_printf(s, "%lld\n", dramfreq->dramfreq_set_us);
return 0;
}
static int
dramfreq_debugfs_settime_open(struct inode *inode, struct file *file)
{
return single_open(file, dramfreq_debugfs_settime_show,
inode->i_private);
}
static const struct file_operations dramfreq_debugfs_settime_fops = {
.open = dramfreq_debugfs_settime_open,
.read = seq_read,
};
static int __init dramfreq_debugfs_init(void)
{
int err = 0;
debugfs_dramfreq_root = debugfs_create_dir("dramfreq", 0);
if (!debugfs_dramfreq_root)
return -ENOMEM;
if (!debugfs_create_file("get_time", 0444, debugfs_dramfreq_root, NULL,
&dramfreq_debugfs_gettime_fops)) {
err = -ENOMEM;
goto out;
}
if (!debugfs_create_file("set_time", 0444, debugfs_dramfreq_root, NULL,
&dramfreq_debugfs_settime_fops)) {
err = -ENOMEM;
goto out;
}
return 0;
out:
debugfs_remove_recursive(debugfs_dramfreq_root);
return err;
}
static void __exit dramfreq_debugfs_exit(void)
{
debugfs_remove_recursive(debugfs_dramfreq_root);
}
late_initcall(dramfreq_debugfs_init);
module_exit(dramfreq_debugfs_exit);
#endif /* CONFIG_DEBUG_FS */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SUNXI dramfreq driver with devfreq framework");
MODULE_AUTHOR("Pan Nan <pannan@allwinnertech.com>");