rk30: add clock support

This commit is contained in:
xxx
2012-03-23 04:12:30 -07:00
parent 2e571ab3e3
commit fa827cb995
12 changed files with 5214 additions and 78 deletions

View File

@@ -1,4 +1,6 @@
obj-y += clock.o obj-y += clock.o
obj-y += clock_data.o
obj-y += dvfs.o
obj-y += common.o obj-y += common.o
obj-y += devices.o obj-y += devices.o
obj-y += io.o obj-y += io.o

View File

@@ -1060,6 +1060,11 @@ static void __init rk30_reserve(void)
board_mem_reserved(); board_mem_reserved();
} }
void __init board_clock_init(void)
{
rk30_clock_data_init(periph_pll_297mhz, codec_pll_360mhz, max_i2s_12288khz);
}
MACHINE_START(RK30, "RK30board") MACHINE_START(RK30, "RK30board")
.boot_params = PLAT_PHYS_OFFSET + 0x800, .boot_params = PLAT_PHYS_OFFSET + 0x800,
.fixup = rk30_fixup, .fixup = rk30_fixup,

760
arch/arm/mach-rk30/clock.c Normal file → Executable file
View File

@@ -1,111 +1,729 @@
/* linux/arch/arm/mach-rk30/clock.c
*
* Copyright (C) 2012 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/clkdev.h> #include <linux/clkdev.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/hardirq.h>
#include <linux/delay.h>
#include <mach/clock.h>
#include "clock.h"
#include <mach/dvfs.h>
#include <linux/delay.h>
#include <mach/board.h> #define CLOCK_PRINTK_DBG(fmt, args...) printk(fmt, ## args);
#define CLOCK_PRINTK_ERR(fmt, args...) printk(fmt, ## args);
/* Clock flags */ /* Clock flags */
/* bit 0 is free */ /* bit 0 is free */
#define RATE_FIXED (1 << 1) /* Fixed clock rate */ #define RATE_FIXED (1 << 1) /* Fixed clock rate */
#define CONFIG_PARTICIPANT (1 << 10) /* Fundamental clock */ #define CONFIG_PARTICIPANT (1 << 10) /* Fundamental clock */
#define IS_PD (1 << 2) /* Power Domain */
#define MHZ (1000*1000) #define MHZ (1000*1000)
#define KHZ 1000 #define KHZ (1000)
struct clk { static void __clk_recalc(struct clk *clk);
struct list_head node; static void __propagate_rate(struct clk *tclk);
const char *name; static void __clk_reparent(struct clk *child, struct clk *parent);
struct clk *parent;
struct list_head children;
struct list_head sibling; /* node for children */
unsigned long rate;
u32 flags;
int (*mode)(struct clk *clk, int on);
unsigned long (*recalc)(struct clk *); /* if null, follow parent */
int (*set_rate)(struct clk *, unsigned long);
long (*round_rate)(struct clk *, unsigned long);
struct clk* (*get_parent)(struct clk *); /* get clk's parent from the hardware. default is clksel_get_parent if parents present */
int (*set_parent)(struct clk *, struct clk *); /* default is clksel_set_parent if parents present */
s16 usecount;
u16 notifier_count;
u8 gate_idx;
u8 pll_idx;
u8 clksel_con;
u8 clksel_mask;
u8 clksel_shift;
u8 clksel_maxdiv;
u8 clksel_parent_mask;
u8 clksel_parent_shift;
struct clk **parents;
};
static struct clk xin24m = { static LIST_HEAD(clocks);
.name = "xin24m", static DEFINE_MUTEX(clocks_mutex);
.rate = 24 * MHZ, static DEFINE_SPINLOCK(clockfw_lock);
.flags = RATE_FIXED, static LIST_HEAD(root_clks);
}; static void clk_notify(struct clk *clk, unsigned long msg,
unsigned long old_rate, unsigned long new_rate);
#define CLK(dev, con, ck) \ #define LOCK() do { WARN_ON(in_irq()); if (!irqs_disabled()) spin_lock_bh(&clockfw_lock); } while (0)
{ \ #define UNLOCK() do { if (!irqs_disabled()) spin_unlock_bh(&clockfw_lock); } while (0)
.dev_id = dev, \ /**********************************************for clock data****************************************************/
.con_id = con, \ static struct clk *def_ops_clk=NULL;
.clk = ck, \
}
static struct clk_lookup clks[] = { void clk_register_default_ops_clk(struct clk *clk)
CLK("rk30_i2c.0", "i2c", &xin24m),
CLK("rk30_i2c.1", "i2c", &xin24m),
CLK("rk30_i2c.2", "i2c", &xin24m),
CLK("rk30_i2c.3", "i2c", &xin24m),
CLK("rk30_i2c.4", "i2c", &xin24m),
CLK("rk29xx_spim.0", "spi", &xin24m),
CLK("rk29xx_spim.1", "spi", &xin24m),
};
void __init rk30_clock_init(void)
{ {
struct clk_lookup *lk; def_ops_clk=clk;
for (lk = clks; lk < clks + ARRAY_SIZE(clks); lk++) {
clkdev_add(lk);
}
} }
int clk_enable(struct clk *clk) static struct clk *clk_default_get_parent(struct clk *clk)
{
if(def_ops_clk&&def_ops_clk->get_parent)
return def_ops_clk->get_parent(clk);
else return NULL;
}
static int clk_default_set_parent(struct clk *clk, struct clk *parent)
{
if(def_ops_clk&&def_ops_clk->set_parent)
return def_ops_clk->set_parent(clk,parent);
else
return -EINVAL;
}
int __init clk_disable_unused(void)
{
struct clk *ck;
CLOCK_PRINTK_DBG("clk_disable_unused in\n");
list_for_each_entry(ck, &clocks, node) {
if (ck->usecount > 0 || ck->mode == NULL || (ck->flags & IS_PD))
continue;
CLOCK_PRINTK_DBG("disbale %s\n",ck->name);
LOCK();
clk_enable_nolock(ck);
//clk_disable_nolock(ck);
UNLOCK();
}
return 0;
}
/**
* recalculate_root_clocks - recalculate and propagate all root clocks
*
* Recalculates all root clocks (clocks with no parent), which if the
* clock's .recalc is set correctly, should also propagate their rates.
* Called at init.
*/
void clk_recalculate_root_clocks_nolock(void)
{
struct clk *clkp;
list_for_each_entry(clkp, &root_clks, sibling) {
__clk_recalc(clkp);
__propagate_rate(clkp);
}
}
/*
void clk_recalculate_root_clocks(void)
{
LOCK();
clk_recalculate_root_clocks_nolock();
UNLOCK();
}*/
/**
* clk_preinit - initialize any fields in the struct clk before clk init
* @clk: struct clk * to initialize
*
* Initialize any struct clk fields needed before normal clk initialization
* can run. No return value.
*/
int clk_register(struct clk *clk)
{
if (clk == NULL || IS_ERR(clk))
return -EINVAL;
//INIT_LIST_HEAD(&clk->sibling);
INIT_LIST_HEAD(&clk->children);
/*
* trap out already registered clocks
*/
if (clk->node.next || clk->node.prev)
return 0;
mutex_lock(&clocks_mutex);
if (clk->get_parent)
clk->parent = clk->get_parent(clk);
else if (clk->parents)
clk->parent =clk_default_get_parent(clk);
if (clk->parent)
list_add(&clk->sibling, &clk->parent->children);
else
list_add(&clk->sibling, &root_clks);
list_add(&clk->node, &clocks);
mutex_unlock(&clocks_mutex);
return 0;
}
/************************************************************/
static void __clk_recalc(struct clk *clk)
{
if (unlikely(clk->flags & RATE_FIXED))
return;
if (clk->recalc)
clk->rate = clk->recalc(clk);
else if (clk->parent)
clk->rate = clk->parent->rate;
}
static void __clk_reparent(struct clk *child, struct clk *parent)
{
if (child->parent == parent)
return;
CLOCK_PRINTK_DBG("%s reparent to %s (was %s)\n", child->name, parent->name, ((child->parent) ? child->parent->name : "NULL"));
list_del_init(&child->sibling);
if (parent)
list_add(&child->sibling, &parent->children);
child->parent = parent;
}
/* Propagate rate to children */
static void __propagate_rate(struct clk *tclk)
{
struct clk *clkp;
//CLOCK_PRINTK_DBG("propagate_rate clk %s\n",clkp->name);
list_for_each_entry(clkp, &tclk->children, sibling) {
__clk_recalc(clkp);
__propagate_rate(clkp);
}
//CLOCK_PRINTK_DBG("propagate_rate clk %s end\n",clkp->name);
}
int clk_enable_nolock(struct clk *clk)
{
int ret = 0;
if (clk->usecount == 0) {
if (clk->parent) {
ret = clk_enable_nolock(clk->parent);
if (ret)
return ret;
}
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_ENABLE, clk->rate, clk->rate);
if (clk->mode)
ret = clk->mode(clk, 1);
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_ENABLE : CLK_POST_ENABLE, clk->rate, clk->rate);
if (ret) {
if (clk->parent)
clk_disable_nolock(clk->parent);
return ret;
}
pr_debug("%s enabled\n", clk->name);
}
clk->usecount++;
return ret;
}
void clk_disable_nolock(struct clk *clk)
{
if (clk->usecount == 0) {
printk(KERN_ERR "Trying disable clock %s with 0 usecount\n", clk->name);
WARN_ON(1);
return;
}
if (--clk->usecount == 0) {
int ret = 0;
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_DISABLE, clk->rate, clk->rate);
if (clk->mode)
ret = clk->mode(clk, 0);
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_DISABLE : CLK_POST_DISABLE, clk->rate, clk->rate);
pr_debug("%s disabled\n", clk->name);
if (ret == 0 && clk->parent)
clk_disable_nolock(clk->parent);
}
}
/* Given a clock and a rate apply a clock specific rounding function */
long clk_round_rate_nolock(struct clk *clk, unsigned long rate)
{
if (clk->round_rate)
return clk->round_rate(clk, rate);
if (clk->flags & RATE_FIXED)
printk(KERN_ERR "clock: clk_round_rate called on fixed-rate clock %s\n", clk->name);
return clk->rate;
}
int clk_set_rate_nolock(struct clk *clk, unsigned long rate)
{
int ret;
unsigned long old_rate;
if (rate == clk->rate)
return 0;
if (clk->flags & CONFIG_PARTICIPANT)
return -EINVAL;
if (!clk->set_rate)
return -EINVAL;
//CLOCK_PRINTK_DBG("**will set %s rate %lu\n", clk->name, rate);
old_rate = clk->rate;
if (clk->notifier_count)
clk_notify(clk, CLK_PRE_RATE_CHANGE, old_rate, rate);
ret = clk->set_rate(clk, rate);
if (ret == 0) {
__clk_recalc(clk);
//CLOCK_PRINTK_DBG("**set %s rate recalc=%lu\n",clk->name,clk->rate);
__propagate_rate(clk);
}
if (clk->notifier_count)
clk_notify(clk, ret ? CLK_ABORT_RATE_CHANGE : CLK_POST_RATE_CHANGE, old_rate, clk->rate);
return ret;
}
int clk_set_parent_nolock(struct clk *clk, struct clk *parent)
{
int ret;
int enabled = clk->usecount > 0;
struct clk *old_parent = clk->parent;
if (clk->parent == parent)
return 0;
/* if clk is already enabled, enable new parent first and disable old parent later. */
if (enabled)
clk_enable_nolock(parent);
if (clk->set_parent)
ret = clk->set_parent(clk, parent);
else
ret = clk_default_set_parent(clk,parent);
if (ret == 0) {
/* OK */
//CLOCK_PRINTK_DBG("set_parent %s reparent\n",clk->name,parent->name);
__clk_reparent(clk, parent);
__clk_recalc(clk);
__propagate_rate(clk);
if (enabled)
clk_disable_nolock(old_parent);
} else {
//CLOCK_PRINTK_DBG("set_parent err\n",clk->name,parent->name);
if (enabled)
clk_disable_nolock(parent);
}
return ret;
}
/**********************************dvfs****************************************************/
struct clk_node *clk_get_dvfs_info(struct clk *clk)
{
return clk->dvfs_info;
}
int clk_set_rate_locked(struct clk * clk,unsigned long rate)
{
int ret;
CLOCK_PRINTK_DBG("%s dvfs clk_set_locked\n",clk->name);
LOCK();
ret=clk_set_rate_nolock(clk, rate);;
UNLOCK();
return ret;
}
void clk_register_dvfs(struct clk_node *dvfs_clk, struct clk *clk)
{
clk->dvfs_info = dvfs_clk;
}
/*-------------------------------------------------------------------------
* Optional clock functions defined in include/linux/clk.h
*-------------------------------------------------------------------------*/
#ifdef RK30_CLK_OFFBOARD_TEST
long rk30_clk_round_rate(struct clk *clk, unsigned long rate)
#else
long clk_round_rate(struct clk *clk, unsigned long rate)
#endif
{
long ret = 0;
if (clk == NULL || IS_ERR(clk))
return ret;
LOCK();
ret = clk_round_rate_nolock(clk, rate);
UNLOCK();
return ret;
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_round_rate);
#else
EXPORT_SYMBOL(clk_round_rate);
#endif
#ifdef RK30_CLK_OFFBOARD_TEST
unsigned long rk30_clk_get_rate(struct clk *clk)
#else
unsigned long clk_get_rate(struct clk *clk)
#endif
{
if (clk == NULL || IS_ERR(clk))
return 0;
return clk->rate;
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_get_rate);
#else
EXPORT_SYMBOL(clk_get_rate);
#endif
/* Set the clock rate for a clock source */
#ifdef RK30_CLK_OFFBOARD_TEST
int rk30_clk_set_rate(struct clk *clk, unsigned long rate)
#else
int clk_set_rate(struct clk *clk, unsigned long rate)
#endif
{
int ret = -EINVAL;
if (clk == NULL || IS_ERR(clk)){
return ret;
}
if (clk->dvfs_info!=NULL&&is_support_dvfs(clk->dvfs_info))
return dvfs_set_rate(clk, rate);
LOCK();
ret = clk_set_rate_nolock(clk, rate);
UNLOCK();
return ret;
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_set_rate);
#else
EXPORT_SYMBOL(clk_set_rate);
#endif
#ifdef RK30_CLK_OFFBOARD_TEST
int rk30_clk_set_parent(struct clk *clk, struct clk *parent)
#else
int clk_set_parent(struct clk *clk, struct clk *parent)
#endif
{
int ret = -EINVAL;
if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent))
return ret;
if (clk->set_parent==NULL||clk->parents == NULL)
return ret;
LOCK();
if (clk->usecount == 0)
ret = clk_set_parent_nolock(clk, parent);
else
ret = -EBUSY;
UNLOCK();
return ret;
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_set_parent);
#else
EXPORT_SYMBOL(clk_set_parent);
#endif
#ifdef RK30_CLK_OFFBOARD_TEST
struct clk *rk30_clk_get_parent(struct clk *clk)
#else
struct clk *clk_get_parent(struct clk *clk)
#endif
{
return clk->parent;
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_get_parent);
#else
EXPORT_SYMBOL(clk_get_parent);
#endif
#ifdef RK30_CLK_OFFBOARD_TEST
void rk30_clk_disable(struct clk *clk)
#else
void clk_disable(struct clk *clk)
#endif
{
if (clk == NULL || IS_ERR(clk))
return;
LOCK();
clk_disable_nolock(clk);
UNLOCK();
}
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_disable);
#else
EXPORT_SYMBOL(clk_disable);
#endif
#ifdef RK30_CLK_OFFBOARD_TEST
int rk30_clk_enable(struct clk *clk)
#else
int clk_enable(struct clk *clk)
#endif
{ {
int ret = 0; int ret = 0;
if (clk == NULL || IS_ERR(clk)) if (clk == NULL || IS_ERR(clk))
return -EINVAL; return -EINVAL;
LOCK();
ret = clk_enable_nolock(clk);
UNLOCK();
return ret; return ret;
} }
#ifdef RK30_CLK_OFFBOARD_TEST
EXPORT_SYMBOL(rk30_clk_enable);
#else
EXPORT_SYMBOL(clk_enable); EXPORT_SYMBOL(clk_enable);
#endif
void clk_disable(struct clk *clk) /* Clk notifier implementation */
/**
* struct clk_notifier - associate a clk with a notifier
* @clk: struct clk * to associate the notifier with
* @notifier_head: a raw_notifier_head for this clk
* @node: linked list pointers
*
* A list of struct clk_notifier is maintained by the notifier code.
* An entry is created whenever code registers the first notifier on a
* particular @clk. Future notifiers on that @clk are added to the
* @notifier_head.
*/
struct clk_notifier {
struct clk *clk;
struct raw_notifier_head notifier_head;
struct list_head node;
};
static LIST_HEAD(clk_notifier_list);
/**
* _clk_free_notifier_chain - safely remove struct clk_notifier
* @cn: struct clk_notifier *
*
* Removes the struct clk_notifier @cn from the clk_notifier_list and
* frees it.
*/
static void _clk_free_notifier_chain(struct clk_notifier *cn)
{ {
if (clk == NULL || IS_ERR(clk)) list_del(&cn->node);
return; kfree(cn);
} }
EXPORT_SYMBOL(clk_disable);
unsigned long clk_get_rate(struct clk *clk) /**
* clk_notify - call clk notifier chain
* @clk: struct clk * that is changing rate
* @msg: clk notifier type (i.e., CLK_POST_RATE_CHANGE; see mach/clock.h)
* @old_rate: old rate
* @new_rate: new rate
*
* Triggers a notifier call chain on the post-clk-rate-change notifier
* for clock 'clk'. Passes a pointer to the struct clk and the
* previous and current rates to the notifier callback. Intended to be
* called by internal clock code only. No return value.
*/
static void clk_notify(struct clk *clk, unsigned long msg,
unsigned long old_rate, unsigned long new_rate)
{ {
return 24000000; struct clk_notifier *cn;
} struct clk_notifier_data cnd;
EXPORT_SYMBOL(clk_get_rate);
int clk_set_rate(struct clk *clk, unsigned long rate) cnd.clk = clk;
cnd.old_rate = old_rate;
cnd.new_rate = new_rate;
UNLOCK();
list_for_each_entry(cn, &clk_notifier_list, node) {
if (cn->clk == clk) {
pr_debug("%s msg %lu rate %lu -> %lu\n", clk->name, msg, old_rate, new_rate);
raw_notifier_call_chain(&cn->notifier_head, msg, &cnd);
break;
}
}
LOCK();
}
/**
* clk_notifier_register - add a clock parameter change notifier
* @clk: struct clk * to watch
* @nb: struct notifier_block * with callback info
*
* Request notification for changes to the clock 'clk'. This uses a
* blocking notifier. Callback code must not call into the clock
* framework, as clocks_mutex is held. Pre-notifier callbacks will be
* passed the previous and new rate of the clock.
*
* clk_notifier_register() must be called from process
* context. Returns -EINVAL if called with null arguments, -ENOMEM
* upon allocation failure; otherwise, passes along the return value
* of blocking_notifier_chain_register().
*/
int rk30_clk_notifier_register(struct clk *clk, struct notifier_block *nb)
{ {
int ret = -EINVAL; struct clk_notifier *cn = NULL, *cn_new = NULL;
int r;
struct clk *clkp;
if (clk == NULL || IS_ERR(clk)) if (!clk || IS_ERR(clk) || !nb)
return ret; return -EINVAL;
return ret; mutex_lock(&clocks_mutex);
list_for_each_entry(cn, &clk_notifier_list, node)
if (cn->clk == clk)
break;
if (cn->clk != clk) {
cn_new = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL);
if (!cn_new) {
r = -ENOMEM;
goto cnr_out;
};
cn_new->clk = clk;
RAW_INIT_NOTIFIER_HEAD(&cn_new->notifier_head);
list_add(&cn_new->node, &clk_notifier_list);
cn = cn_new;
}
r = raw_notifier_chain_register(&cn->notifier_head, nb);
if (!IS_ERR_VALUE(r)) {
clkp = clk;
do {
clkp->notifier_count++;
} while ((clkp = clkp->parent));
} else {
if (cn_new)
_clk_free_notifier_chain(cn);
}
cnr_out:
mutex_unlock(&clocks_mutex);
return r;
} }
EXPORT_SYMBOL(clk_set_rate); EXPORT_SYMBOL(rk30_clk_notifier_register);
/**
* clk_notifier_unregister - remove a clock change notifier
* @clk: struct clk *
* @nb: struct notifier_block * with callback info
*
* Request no further notification for changes to clock 'clk'.
* Returns -EINVAL if called with null arguments; otherwise, passes
* along the return value of blocking_notifier_chain_unregister().
*/
int rk30_clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
{
struct clk_notifier *cn = NULL;
struct clk *clkp;
int r = -EINVAL;
if (!clk || IS_ERR(clk) || !nb)
return -EINVAL;
mutex_lock(&clocks_mutex);
list_for_each_entry(cn, &clk_notifier_list, node)
if (cn->clk == clk)
break;
if (cn->clk != clk) {
r = -ENOENT;
goto cnu_out;
};
r = raw_notifier_chain_unregister(&cn->notifier_head, nb);
if (!IS_ERR_VALUE(r)) {
clkp = clk;
do {
clkp->notifier_count--;
} while ((clkp = clkp->parent));
}
/*
* XXX ugh, layering violation. There should be some
* support in the notifier code for this.
*/
if (!cn->notifier_head.head)
_clk_free_notifier_chain(cn);
cnu_out:
mutex_unlock(&clocks_mutex);
return r;
}
EXPORT_SYMBOL(rk30_clk_notifier_unregister);
#ifdef CONFIG_PROC_FS
static struct clk_dump_ops *dump_def_ops;
void clk_register_dump_ops(struct clk_dump_ops *ops)
{
dump_def_ops=ops;
}
static int proc_clk_show(struct seq_file *s, void *v)
{
struct clk* clk;
if(!dump_def_ops)
return 0;
if(dump_def_ops->dump_clk)
{
mutex_lock(&clocks_mutex);
list_for_each_entry(clk, &clocks, node) {
if (!clk->parent)
{
dump_def_ops->dump_clk(s, clk, 0,&clocks);
}
}
mutex_unlock(&clocks_mutex);
}
if(dump_def_ops->dump_regs)
dump_def_ops->dump_regs(s);
return 0;
}
static int proc_clk_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_clk_show, NULL);
}
static const struct file_operations proc_clk_fops = {
.open = proc_clk_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init clk_proc_init(void)
{
proc_create("clocks", 0, NULL, &proc_clk_fops);
return 0;
}
late_initcall(clk_proc_init);
#endif /* CONFIG_PROC_FS */

View File

@@ -0,0 +1,98 @@
#ifndef __MACH_CLOCK_H__
#define __MACH_CLOCK_H__
#ifndef CONFIG_ARCH_RK30
#define RK30_CLK_OFFBOARD_TEST
#endif
/* Clock flags */
/* bit 0 is free */
#define RATE_FIXED (1 << 1) /* Fixed clock rate */
#define CONFIG_PARTICIPANT (1 << 10) /* Fundamental clock */
#define IS_PD (1 << 2) /* Power Domain */
enum _clk_i2s_rate_support {
i2s_8192khz = 8192000,
i2s_11289_6khz = 11289600,
i2s_12288khz = 12288000,
i2s_22579_2khz = 22579200,
i2s_24576khz = 24576000,//HDMI
i2s_49152khz = 24576000,//HDMI
};
struct _pll_data{
u8 id;
void *table;
};
//struct clk_node;
struct clk {
struct list_head node;
const char *name;
struct clk *parent;
struct list_head children;
struct list_head sibling; /* node for children */
int (*mode)(struct clk *clk, int on);
unsigned long (*recalc)(struct clk *); /* if null, follow parent */
int (*set_rate)(struct clk *, unsigned long);
long (*round_rate)(struct clk *, unsigned long);
struct clk* (*get_parent)(struct clk *); /* get clk's parent from the hardware. default is clksel_get_parent if parents present */
int (*set_parent)(struct clk *, struct clk *); /* default is clksel_set_parent if parents present */
unsigned long rate;
u32 flags;
s16 usecount;
u16 notifier_count;
u8 gate_idx;
struct _pll_data *pll;
u32 clksel_con;
u32 div_mask;
u32 div_shift;
u32 div_max;
u32 src_mask;
u32 src_shift;
struct clk **parents;
u8 parents_num;
struct clk_node *dvfs_info;
};
int __init clk_disable_unused(void);
void clk_recalculate_root_clocks_nolock(void);
void clk_recalculate_root_clocks(void);
int clk_register(struct clk *clk);
void clk_register_default_ops_clk(struct clk *clk);
int clk_enable_nolock(struct clk *clk);
void clk_disable_nolock(struct clk *clk);
long clk_round_rate_nolock(struct clk *clk, unsigned long rate);
int clk_set_rate_nolock(struct clk *clk, unsigned long rate);
int clk_set_parent_nolock(struct clk *clk, struct clk *parent);
int clk_set_rate_locked(struct clk * clk,unsigned long rate);
void clk_register_dvfs(struct clk_node *dvfs_clk, struct clk *clk);
struct clk_node *clk_get_dvfs_info(struct clk *clk);
#ifdef RK30_CLK_OFFBOARD_TEST
#include <linux/device.h>
struct clk *rk30_clk_get(struct device *dev, const char *con_id);
#endif
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
struct clk_dump_ops {
void (*dump_clk)(struct seq_file *s, struct clk *clk, int deep,const struct list_head *root_clocks);
void (*dump_regs)(struct seq_file *s);
};
void clk_register_dump_ops(struct clk_dump_ops *ops);
#else
static void clk_register_dump_ops(struct clk_dump_ops *ops){
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,7 @@ void __init rk30_map_io(void)
rk30_map_common_io(); rk30_map_common_io();
rk29_setup_early_printk(); rk29_setup_early_printk();
rk29_sram_init(); rk29_sram_init();
rk30_clock_init(); board_clock_init();
rk30_l2_cache_init(); rk30_l2_cache_init();
rk30_iomux_init(); rk30_iomux_init();
} }

942
arch/arm/mach-rk30/dvfs.c Normal file
View File

@@ -0,0 +1,942 @@
/* arch/arm/mach-rk30/rk30_dvfs.c
*
* Copyright (C) 2012 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include "clock.h"
#include <mach/dvfs.h>
#include <mach/clock.h>
#if 0
#define DVFS_DBG(fmt, args...) pr_debug(fmt, ##args)
#define DVFS_ERR(fmt, args...) pr_err(fmt, ##args)
#else
#define DEBUG_RK30_DVFS
#define DVFS_DBG(fmt, args...) printk(fmt, ##args)
#define DVFS_ERR(fmt, args...) printk(fmt, ##args)
#endif
#ifndef CONFIG_ARCH_RK30
#define DVFS_TEST_OFF_BOARD
#endif
#ifdef DVFS_TEST_OFF_BOARD
/* Just for simulation */
struct regulator {
int min_uV;
};
#if 0
static void test_regulator_put(struct regulator *regulator)
{
kfree(regulator);
}
#endif
struct regulator regulators[100];
static struct regulator *test_regulator_get(struct device *dev, const char *id) {
static int ret_cnt = 0;
return &regulators[ret_cnt++];
}
static int test_regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV)
{
regulator->min_uV = min_uV;
return 0;
}
static int test_regulator_get_voltage(struct regulator *regulator)
{
return regulator->min_uV;
}
int rk30_clk_set_rate(struct clk *clk, unsigned long rate);
static void dump_dbg_map(void);
int rk30_dvfs_init_test(void);
int rk30_clk_enable(struct clk *clk);
int rk30_clk_disable(struct clk *clk);
#define dvfs_regulator_get(dev,id) test_regulator_get((dev),(id))
#define dvfs_regulator_put(regu) test_regulator_get((regu))
#define dvfs_regulator_set_voltage(regu,min_uV,max_uV) test_regulator_set_voltage((regu),(min_uV),(max_uV))
#define dvfs_regulator_get_voltage(regu) test_regulator_get_voltage((regu))
/* clock */
#define dvfs_clk_get(a,b) rk30_clk_get((a),(b))
#define dvfs_clk_set_rate(a,b) rk30_clk_set_rate((a),(b))
#define dvfs_clk_enable(a) rk30_clk_enable((a))
#define dvfs_clk_disable(a) rk30_clk_disable((a))
#else
/* board runing */
#include <linux/regulator/consumer.h>
#define dvfs_regulator_get(dev,id) regulator_get((dev),(id))
#define dvfs_regulator_put(regu) regulator_get((regu))
#define dvfs_regulator_set_voltage(regu,min_uV,max_uV) regulator_set_voltage((regu),(min_uV),(max_uV))
#define dvfs_regulator_get_voltage(regu) regulator_get_voltage((regu))
#define dvfs_clk_get(a,b) clk_get((a),(b))
#define dvfs_clk_set_rate(a,b) clk_set_rate((a),(b))
#define dvfs_clk_enable(a) clk_enable((a))
#define dvfs_clk_disable(a) clk_disable((a))
#endif
static LIST_HEAD(rk_dvfs_tree);
static DEFINE_MUTEX(mutex);
/*
int dvfs_target_core(struct clk *clk, unsigned int rate);
int dvfs_target(struct clk *clk, unsigned int rate);
int dvfs_clk_set_rate(struct clk *clk, unsigned long rate);
*/
extern int rk30_clk_notifier_register(struct clk *clk, struct notifier_block *nb);
extern int rk30_clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
#define FV_TABLE_END 0
#define PD_ON 1
#define PD_OFF 0
static void dvfs_clk_scale_volt(struct clk_node *dvfs_clk, unsigned int volt);
static int dvfs_clk_get_volt(struct clk_node *dvfs_clk, unsigned long rate,
struct cpufreq_frequency_table *clk_fv);
/**
* **************************FUNCTIONS***********************************
*/
#ifdef DEBUG_RK30_DVFS
/**
* dump_dbg_map() : Draw all informations of dvfs while debug
*/
static void dump_dbg_map(void)
{
int i;
struct vd_node *vd;
struct pd_node *pd, *clkparent;
struct clk_list *child;
struct clk_node *dvfs_clk;
DVFS_DBG("-------------DVFS DEBUG-----------\n\n\n");
DVFS_DBG("RK30 DVFS TREE:\n");
list_for_each_entry(vd, &rk_dvfs_tree, node) {
DVFS_DBG("|\n|- voltage domain:%s\n", vd->name);
DVFS_DBG("|- current voltage:%d\n", vd->cur_volt);
list_for_each_entry(pd, &vd->pd_list, node) {
DVFS_DBG("| |\n| |- power domain:%s, status = %s, current volt = %d\n",
pd->name, (pd->pd_status == PD_ON) ? "ON" : "OFF", pd->cur_volt);
list_for_each_entry(child, &pd->clk_list, node) {
dvfs_clk = child->dvfs_clk;
DVFS_DBG("| | |\n| | |- clock: %s current: rate %d, volt = %d, enable_dvfs = %s\n",
dvfs_clk->name, dvfs_clk->cur_freq, dvfs_clk->cur_volt, dvfs_clk->enable_dvfs == 0 ? "DISABLE" : "ENABLE");
for (i = 0; dvfs_clk->pds[i].pd != NULL; i++) {
clkparent = dvfs_clk->pds[i].pd;
DVFS_DBG("| | | |- clock parents: %s, vd_parent = %s\n", clkparent->name, clkparent->vd->name);
}
for (i = 0; (dvfs_clk->dvfs_table[i].frequency != FV_TABLE_END); i++) {
DVFS_DBG("| | | |- freq = %d, volt = %d\n", dvfs_clk->dvfs_table[i].frequency, dvfs_clk->dvfs_table[i].index);
}
}
}
}
DVFS_DBG("-------------DVFS DEBUG END------------\n");
}
#endif
int is_support_dvfs(struct clk_node *dvfs_info)
{
return (dvfs_info->vd && dvfs_info->vd->vd_dvfs_target && dvfs_info->enable_dvfs);
}
static int rk_dvfs_clk_notifier_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct clk_notifier_data *noti_info;
struct clk *clk;
struct clk_node *dvfs_clk;
noti_info = (struct clk_notifier_data *)ptr;
clk = noti_info->clk;
dvfs_clk = clk->dvfs_info;
switch (event) {
case CLK_PRE_RATE_CHANGE:
DVFS_DBG("%s CLK_PRE_RATE_CHANGE\n", __func__);
break;
case CLK_POST_RATE_CHANGE:
DVFS_DBG("%s CLK_POST_RATE_CHANGE\n", __func__);
break;
case CLK_ABORT_RATE_CHANGE:
DVFS_DBG("%s CLK_ABORT_RATE_CHANGE\n", __func__);
break;
case CLK_PRE_ENABLE:
DVFS_DBG("%s CLK_PRE_ENABLE\n", __func__);
break;
case CLK_POST_ENABLE:
DVFS_DBG("%s CLK_POST_ENABLE\n", __func__);
break;
case CLK_ABORT_ENABLE:
DVFS_DBG("%s CLK_ABORT_ENABLE\n", __func__);
break;
case CLK_PRE_DISABLE:
DVFS_DBG("%s CLK_PRE_DISABLE\n", __func__);
break;
case CLK_POST_DISABLE:
DVFS_DBG("%s CLK_POST_DISABLE\n", __func__);
dvfs_clk->cur_freq = 0;
dvfs_clk_scale_volt(dvfs_clk, 0);
break;
case CLK_ABORT_DISABLE:
DVFS_DBG("%s CLK_ABORT_DISABLE\n", __func__);
break;
default:
break;
}
return 0;
}
static struct notifier_block rk_dvfs_clk_notifier = {
.notifier_call = rk_dvfs_clk_notifier_event,
};
int clk_disable_dvfs(struct clk *clk)
{
struct clk_node *dvfs_clk;
dvfs_clk = clk->dvfs_info;
if(dvfs_clk->enable_dvfs - 1 < 0) {
DVFS_ERR("clk is already closed!\n");
return -1;
} else {
DVFS_ERR("clk is disable now!\n");
dvfs_clk->enable_dvfs--;
if(0 == dvfs_clk->enable_dvfs) {
DVFS_ERR("clk closed!\n");
rk30_clk_notifier_unregister(clk, dvfs_clk->dvfs_nb);
DVFS_ERR("clk unregister nb!\n");
dvfs_clk_scale_volt(dvfs_clk, 0);
}
}
dump_dbg_map();
return 0;
}
int clk_enable_dvfs(struct clk *clk)
{
struct regulator *regulator;
struct clk_node *dvfs_clk;
struct cpufreq_frequency_table clk_fv;
if(!clk->dvfs_info) {
DVFS_ERR("This clk(%s) not support dvfs!\n", clk->name);
return -1;
}
dvfs_clk = clk->dvfs_info;
DVFS_ERR("dvfs clk enable dvfs %s\n", dvfs_clk->name);
if(0 == dvfs_clk->enable_dvfs) {
dvfs_clk->enable_dvfs++;
if(!dvfs_clk->vd->regulator) {
regulator = dvfs_regulator_get(NULL, dvfs_clk->vd->regulator_name);
if(regulator)
dvfs_clk->vd->regulator = regulator;
else
dvfs_clk->vd->regulator = NULL;
}
if(dvfs_clk->dvfs_nb) {
// must unregister when clk disable
rk30_clk_notifier_register(clk, dvfs_clk->dvfs_nb);
}
if(!clk || IS_ERR(clk)) {
DVFS_ERR("%s get clk %s error\n", __func__, dvfs_clk->name);
return -1;
}
//DVFS_DBG("%s get clk %s rate = %lu\n", __func__, clk->name, clk->rate);
if(dvfs_clk->cur_freq == 0)
dvfs_clk_get_volt(dvfs_clk, clk->rate, &clk_fv);
else
dvfs_clk_get_volt(dvfs_clk, dvfs_clk->cur_freq, &clk_fv);
dvfs_clk->cur_volt = clk_fv.index;
dvfs_clk->cur_freq = clk_fv.frequency;
dvfs_clk_scale_volt(dvfs_clk, dvfs_clk->cur_volt);
dump_dbg_map();
} else {
DVFS_ERR("dvfs already enable clk enable = %d!\n", dvfs_clk->enable_dvfs);
dvfs_clk->enable_dvfs++;
}
return 0;
}
int dvfs_set_rate(struct clk *clk, unsigned long rate)
{
int ret = 0;
struct vd_node *vd;
DVFS_DBG("%s dvfs start\n", clk->name);
if(!clk->dvfs_info) {
DVFS_ERR("%s :This clk do not support dvfs!\n", __func__);
ret = -1;
} else {
vd = clk->dvfs_info->vd;
mutex_lock(&vd->dvfs_mutex);
ret = vd->vd_dvfs_target(clk, rate);
mutex_unlock(&vd->dvfs_mutex);
}
return ret;
}
/**
* get correspond voltage khz
*/
static int dvfs_clk_get_volt(struct clk_node *dvfs_clk, unsigned long rate,
struct cpufreq_frequency_table *clk_fv)
{
int i = 0;
if (rate == 0) {
/* since no need*/
return -1;
}
clk_fv->frequency = rate;
clk_fv->index = 0;
for(i = 0; (dvfs_clk->dvfs_table[i].frequency != FV_TABLE_END); i++) {
if(dvfs_clk->dvfs_table[i].frequency >= rate) {
clk_fv->frequency = dvfs_clk->dvfs_table[i].frequency;
clk_fv->index = dvfs_clk->dvfs_table[i].index;
DVFS_DBG("%s dvfs_clk_get_volt rate=%u hz ref vol=%d uV\n", dvfs_clk->name, clk_fv->frequency, clk_fv->index);
return 0;
}
}
clk_fv->frequency = 0;
clk_fv->index = 0;
DVFS_ERR("%s get corresponding voltage error! out of bound\n", dvfs_clk->name);
return -1;
}
static int dvfs_clk_round_volt(struct clk_node *dvfs_clk, int volt)
{
struct pd_node *pd;
struct clk_node *dvfs_clk_tmp;
int volt_max = 0;
int i;
for(i = 0; (dvfs_clk->pds[i].pd != NULL); i++) {
pd = dvfs_clk->pds[i].pd;
if(volt > pd->cur_volt) {
/**
* if dvfs_clk parent power domain's voltage is smaller then
* this dvfs_clk's voltage ignore this power domain
*/
volt_max = max(volt_max, volt);
continue;
}
list_for_each_entry(dvfs_clk_tmp, &pd->clk_list, node) {
/**
* found the max voltage uninclude dvfs_clk
*/
if(dvfs_clk_tmp != dvfs_clk) {
volt_max = max(volt_max, dvfs_clk_tmp->cur_volt);
}
}
}
volt_max = max(volt_max, volt);
return volt_max;
}
static void dvfs_clk_scale_volt(struct clk_node *dvfs_clk, unsigned int volt)
{
struct vd_node *vd;
struct pd_node *pd;
struct clk_list *child;
struct clk_node *dvfs_clk_tmp;
int volt_max_vd = 0, volt_max_pd = 0, i;
dvfs_clk->cur_volt = volt;//set clk node volt
vd = dvfs_clk->vd;// vd
for(i = 0; (dvfs_clk->pds[i].pd != NULL); i++) {
pd = dvfs_clk->pds[i].pd;
volt_max_pd = 0;
/**
* set corresponding voltage, clk do not need to set voltage,just for
* powerdomain
*/
if(volt > pd->cur_volt) {
pd->cur_volt = volt;
pd->pd_status = (pd->cur_volt == 0) ? PD_OFF : PD_ON;
continue;
}
/* set power domain voltage */
list_for_each_entry(child, &pd->clk_list, node) {
dvfs_clk_tmp = child->dvfs_clk;
if(dvfs_clk_tmp->enable_dvfs){
volt_max_pd = max(volt_max_pd, dvfs_clk_tmp->cur_volt);
}
}
pd->cur_volt = volt_max_pd;
pd->pd_status = (volt_max_pd == 0) ? PD_OFF : PD_ON;
}
/* set voltage domain voltage */
volt_max_vd = 0;
list_for_each_entry(pd, &vd->pd_list, node) {
volt_max_vd = max(volt_max_vd, pd->cur_volt);
}
vd->cur_volt = volt_max_vd;
}
int dvfs_target_set_rate_core(struct clk *clk, unsigned long rate)
{
struct clk_node *dvfs_clk;
int volt_new = 0, volt_old = 0;
struct cpufreq_frequency_table clk_fv;
int ret = 0;
dvfs_clk = clk_get_dvfs_info(clk);
DVFS_ERR("%s get clk %s\n", __func__, clk->name);
if(dvfs_clk->vd->regulator == NULL) {
DVFS_ERR("%s can't get dvfs regulater\n", clk->name);
return -1;
}
/* If power domain off do scale in the notify function */
/*
if (rate == 0) {
dvfs_clk->cur_freq = 0;
dvfs_clk_scale_volt(dvfs_clk, 0);
return 0;
}
*/
/* need round rate */
DVFS_ERR("%s going to round rate = %lu\n", clk->name, rate);
rate = clk_round_rate_nolock(clk, rate);
DVFS_ERR("%s round get rate = %lu\n", clk->name, rate);
/* find the clk corresponding voltage */
if (0 != dvfs_clk_get_volt(dvfs_clk, rate, &clk_fv)) {
DVFS_ERR("%s rate %lukhz is larger,not support\n", clk->name, rate);
return -1;
}
volt_old = dvfs_clk->vd->cur_volt;
volt_new = clk_fv.index;
DVFS_DBG("vol_new = %d mV(was %d mV)\n", volt_new, volt_old);
/* if up the voltage*/
if (volt_old < volt_new) {
if(dvfs_regulator_set_voltage(dvfs_clk->vd->regulator, volt_new, volt_new) < 0) {
DVFS_ERR("set voltage err\n");
return -1;
}
dvfs_clk->vd->cur_volt = volt_new;
/* CPU do not use power domain, so save scale times */
//dvfs_clk_scale_volt(dvfs_clk, clk_fv.index);
}
if(dvfs_clk->clk_dvfs_target) {
ret = dvfs_clk->clk_dvfs_target(clk, rate, clk_set_rate_locked);
} else {
ret = clk_set_rate_locked(clk, rate);
}
if (ret < 0) {
DVFS_ERR("set rate err\n");
return -1;
}
dvfs_clk->cur_freq = rate;
dvfs_clk->cur_volt = volt_new;
/* if down the voltage */
if (volt_old > volt_new) {
if(dvfs_regulator_set_voltage(dvfs_clk->vd->regulator, volt_new, volt_new) < 0) {
DVFS_ERR("set voltage err\n");
return -1;
}
dvfs_clk->vd->cur_volt = volt_new;
/* CPU do not use power domain, so save scale times */
//dvfs_clk_scale_volt(dvfs_clk, clk_fv.index);
}
return ret;
}
int dvfs_target_set_rate_normal(struct clk *clk, unsigned long rate)
{
struct clk_node *dvfs_clk;
unsigned int volt_new = 0, volt_old = 0;
struct cpufreq_frequency_table clk_fv = {0, 0};
int ret = 0;
dvfs_clk = clk_get_dvfs_info(clk);
DVFS_ERR("%s get clk %s\n", __func__, clk->name);
if(dvfs_clk->vd->regulator == NULL) {
DVFS_DBG("%s can't get dvfs regulater\n", clk->name);
return -1;
}
/* need round rate */
DVFS_ERR("%s going to round rate = %lu\n", clk->name, rate);
rate = clk_round_rate_nolock(clk, rate);
DVFS_ERR("%s round get rate = %lu\n", clk->name, rate);
/* find the clk corresponding voltage */
if (dvfs_clk_get_volt(dvfs_clk, rate, &clk_fv)) {
DVFS_DBG("dvfs_clk_get_volt:rate = Get corresponding voltage error!\n");
return -1;
}
volt_old = dvfs_clk->vd->cur_volt;
volt_new = dvfs_clk_round_volt(dvfs_clk, clk_fv.index);
// if up the voltage
if (volt_old < volt_new) {
if(dvfs_regulator_set_voltage(dvfs_clk->vd->regulator, volt_new, volt_new) < 0) {
DVFS_DBG("set voltage err\n");
return -1;
}
dvfs_clk_scale_volt(dvfs_clk, clk_fv.index);
}
if(dvfs_clk->clk_dvfs_target) {
ret = dvfs_clk->clk_dvfs_target(clk, rate, clk_set_rate_locked);
} else {
ret = clk_set_rate_locked(clk, rate);
}
if (ret < 0) {
DVFS_ERR("set rate err\n");
return -1;
}
dvfs_clk->cur_freq = rate;
dvfs_clk->cur_volt = volt_new;
// if down the voltage
if (volt_old > volt_new) {
if(dvfs_regulator_set_voltage(dvfs_clk->vd->regulator, volt_new, volt_new) < 0) {
DVFS_DBG("set voltage err\n");
return -1;
}
dvfs_clk_scale_volt(dvfs_clk, clk_fv.index);
}
return 0;
}
/*****************************init**************************/
/**
* rate must be raising sequence
*/
struct cpufreq_frequency_table cpu_dvfs_table[] = {
{.frequency = 126000000, .index = 800000},
{.frequency = 252000000, .index = 850000},
{.frequency = 504000000, .index = 900000},
{.frequency = 816000000, .index = 1050000},
{.frequency = 1008000000, .index = 1100000},
{.frequency = 1200000000, .index = 1200000},
{.frequency = FV_TABLE_END},
};
struct cpufreq_frequency_table ddr_dvfs_table[] = {
{.frequency = 24000000, .index = 600000},
{.frequency = 64000000, .index = 700000},
{.frequency = 126000000, .index = 800000},
{.frequency = 252000000, .index = 850000},
{.frequency = 504000000, .index = 900000},
{.frequency = FV_TABLE_END},
};
struct cpufreq_frequency_table gpu_dvfs_table[] = {
{.frequency = 64000000, .index = 700000},
{.frequency = 126000000, .index = 800000},
{.frequency = 360000000, .index = 850000},
{.frequency = FV_TABLE_END},
};
static struct vd_node vd_cpu = {
.name = "vd_cpu",
.vd_dvfs_target = dvfs_target_set_rate_core,
};
static struct vd_node vd_core = {
.name = "vd_core",
.vd_dvfs_target = dvfs_target_set_rate_normal,
};
static struct vd_node vd_rtc = {
.name = "vd_rtc",
.vd_dvfs_target = NULL,
};
#define LOOKUP_VD(_pvd, _regulator_name) \
{ \
.vd = _pvd, \
.regulator_name = _regulator_name, \
}
static struct vd_node_lookup rk30_vds[] = {
LOOKUP_VD(&vd_cpu, "cpu"),
LOOKUP_VD(&vd_core, "core"),
LOOKUP_VD(&vd_rtc, "rtc"),
};
static struct pd_node pd_a9_0 = {
.name = "pd_a9_0",
.vd = &vd_cpu,
};
static struct pd_node pd_a9_1 = {
.name = "pd_a9_1",
.vd = &vd_cpu,
};
static struct pd_node pd_debug = {
.name = "pd_debug",
.vd = &vd_cpu,
};
static struct pd_node pd_scu = {
.name = "pd_scu",
.vd = &vd_cpu,
};
static struct pd_node pd_video = {
.name = "pd_video",
.vd = &vd_core,
};
static struct pd_node pd_vio = {
.name = "pd_vio",
.vd = &vd_core,
};
static struct pd_node pd_gpu = {
.name = "pd_gpu",
.vd = &vd_core,
};
static struct pd_node pd_peri = {
.name = "pd_peri",
.vd = &vd_core,
};
static struct pd_node pd_cpu = {
.name = "pd_cpu",
.vd = &vd_core,
};
static struct pd_node pd_alive = {
.name = "pd_alive",
.vd = &vd_core,
};
static struct pd_node pd_rtc = {
.name = "pd_rtc",
.vd = &vd_rtc,
};
#define LOOKUP_PD(_ppd) \
{ \
.pd = _ppd, \
}
static struct pd_node_lookup rk30_pds[] = {
LOOKUP_PD(&pd_a9_0),
LOOKUP_PD(&pd_a9_1),
LOOKUP_PD(&pd_debug),
LOOKUP_PD(&pd_scu),
LOOKUP_PD(&pd_video),
LOOKUP_PD(&pd_vio),
LOOKUP_PD(&pd_gpu),
LOOKUP_PD(&pd_peri),
LOOKUP_PD(&pd_cpu),
LOOKUP_PD(&pd_alive),
LOOKUP_PD(&pd_rtc),
};
#define CLK_PDS(_ppd) \
{ \
.pd = _ppd, \
}
static struct pds_list cpu_pds[] = {
CLK_PDS(&pd_a9_0),
CLK_PDS(&pd_a9_1),
CLK_PDS(NULL),
};
static struct pds_list ddr_pds[] = {
CLK_PDS(&pd_cpu),
CLK_PDS(NULL),
};
static struct pds_list gpu_pds[] = {
CLK_PDS(&pd_gpu),
CLK_PDS(NULL),
};
#define RK_CLKS(_clk_name, _ppds, _dvfs_table, _dvfs_nb) \
{ \
.name = _clk_name, \
.pds = _ppds, \
.dvfs_table = _dvfs_table, \
.dvfs_nb = _dvfs_nb, \
}
static struct clk_node rk30_clks[] = {
RK_CLKS("cpu", cpu_pds, cpu_dvfs_table, &rk_dvfs_clk_notifier),
RK_CLKS("ddr", ddr_pds, ddr_dvfs_table, &rk_dvfs_clk_notifier),
RK_CLKS("gpu", gpu_pds, gpu_dvfs_table, &rk_dvfs_clk_notifier),
};
/**
* first scale regulator volt
*/
static int rk_dvfs_check_regulator_volt(void)
{
struct vd_node *vd;
struct pd_node *pd;
struct clk_list *child;
struct clk_node *dvfs_clk;
struct clk *clk;
struct cpufreq_frequency_table clk_fv;
unsigned int vmax_pd = 0, vmax_vd = 0;
list_for_each_entry(vd, &rk_dvfs_tree, node) {
vmax_vd = 0;
list_for_each_entry(pd, &vd->pd_list, node) {
vmax_pd = 0;
list_for_each_entry(child, &pd->clk_list, node) {
dvfs_clk = child->dvfs_clk;
clk = dvfs_clk_get(NULL, dvfs_clk->name);
if(!clk || IS_ERR(clk)) {
DVFS_ERR("%s get clk %s error\n", __func__, dvfs_clk->name);
continue;
}
//DVFS_DBG("%s get clk %s rate = %lu\n", __func__, clk->name, clk->rate);
dvfs_clk_get_volt(dvfs_clk, clk->rate, &clk_fv);
dvfs_clk->cur_volt = clk_fv.index;
dvfs_clk->cur_freq = clk_fv.frequency;
vmax_pd = max(vmax_pd, clk_fv.index);
pd->pd_status = (vmax_pd == 0) ? PD_OFF : PD_ON;
}
pd->cur_volt = vmax_pd;
vmax_vd = max(vmax_vd, vmax_pd);
}
vd->cur_volt = vmax_vd;
//DVFS_DBG("%s check error: %d, %d\n", vd->name, vd->cur_volt, dvfs_regulator_get_voltage(vd->regulator));
//if (vd->cur_volt != dvfs_regulator_get_voltage(vd->regulator)) {
// DVFS_ERR("%s default voltage domain value error!\n", vd->name);
//}
}
return 0;
}
static int rk_regist_vd(struct vd_node_lookup *vd_lookup)
{
struct vd_node *vd;
if(!vd_lookup)
return -1;
vd = vd_lookup->vd;
vd->regulator_name = vd_lookup->regulator_name;
mutex_lock(&mutex);
mutex_init(&vd->dvfs_mutex);
list_add(&vd->node, &rk_dvfs_tree);
INIT_LIST_HEAD(&vd->pd_list);
mutex_unlock(&mutex);
return 0;
}
static int rk_regist_pd(struct pd_node_lookup *pd_lookup)
{
struct vd_node *vd;
struct pd_node *pd;
mutex_lock(&mutex);
pd = pd_lookup->pd;
list_for_each_entry(vd, &rk_dvfs_tree, node) {
if (vd == pd->vd) {
list_add(&pd->node, &vd->pd_list);
INIT_LIST_HEAD(&pd->clk_list);
break;
}
}
mutex_unlock(&mutex);
return 0;
}
//extern int rk30_clk_notifier_register(struct clk *clk, struct notifier_block *nb);
static int rk_regist_clk(struct clk_node *dvfs_clk)
{
struct pd_node *pd;
struct clk_list *child;
struct clk *clk;
int i = 0;
if(!dvfs_clk)
return -1;
if(!dvfs_clk->pds)
return -1;
mutex_lock(&mutex);
// set clk unsupport dvfs
dvfs_clk->enable_dvfs = 0;
dvfs_clk->vd = dvfs_clk->pds[0].pd->vd;
for (i = 0; dvfs_clk->pds[i].pd != NULL; i++) {
child = &(dvfs_clk->pds[i].clk_list);
child->dvfs_clk = dvfs_clk;
pd = dvfs_clk->pds[i].pd;
list_add(&child->node, &pd->clk_list);
}
clk = dvfs_clk_get(NULL, dvfs_clk->name);
clk_register_dvfs(dvfs_clk, clk);
mutex_unlock(&mutex);
return 0;
}
int rk30_dvfs_init(void)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(rk30_vds); i++) {
rk_regist_vd(&rk30_vds[i]);
}
for (i = 0; i < ARRAY_SIZE(rk30_pds); i++) {
rk_regist_pd(&rk30_pds[i]);
}
for (i = 0; i < ARRAY_SIZE(rk30_clks); i++) {
rk_regist_clk(&rk30_clks[i]);
}
dump_dbg_map();
//DVFS_DBG("%s dvfs tree create finish!\n", __func__);
//rk_dvfs_check_regulator_volt();
return 0;
}
void dvfs_clk_set_rate_callback(struct clk *clk, clk_dvfs_target_callback clk_dvfs_target)
{
struct clk_node *dvfs_clk = clk_get_dvfs_info(clk);
dvfs_clk->clk_dvfs_target = clk_dvfs_target;
}
//
/*
*cpufreq_frequency_table->index for cpufreq is index
*cpufreq_frequency_table->index for dvfstable is volt
*/
int cpufreq_dvfs_init(struct clk *clk, struct cpufreq_frequency_table **table, clk_dvfs_target_callback clk_dvfs_target)
{
struct cpufreq_frequency_table *freq_table;
struct clk_node *info = clk_get_dvfs_info(clk);
struct cpufreq_frequency_table *dvfs_table;//dvfs volt freq table
int i = 0;
DVFS_DBG("%s clk name %s\n", __func__, clk->name);
if(!info) {
return -1;
}
dvfs_table = info->dvfs_table;
if(!dvfs_table) {
return -1;
}
/********************************count table num****************************/
i = 0;
while(dvfs_table[i].frequency != FV_TABLE_END) {
//DVFS_DBG("dvfs_table1 %lu\n",dvfs_table[i].frequency);
i++;
}
freq_table = kzalloc(sizeof(struct cpufreq_frequency_table) * (i + 1), GFP_KERNEL);
//last freq is end tab
freq_table[i].index = i;
freq_table[i].frequency = CPUFREQ_TABLE_END;
//set freq table
i = 0;
while(dvfs_table[i].frequency != FV_TABLE_END) {
freq_table[i].index = i;
freq_table[i].frequency = dvfs_table[i].frequency;
//DVFS_DBG("dvfs_table %d %lu\n",i,dvfs_table[i].frequency);
i++;
}
*table = &freq_table[0];
dvfs_clk_set_rate_callback(clk, clk_dvfs_target);
return 0;
}
int clk_dvfs_set_dvfs_table(struct clk *clk, struct cpufreq_frequency_table *table)
{
struct clk_node *info = clk_get_dvfs_info(clk);
if(!table || !info)
return -1;
info->dvfs_table = table;
return 0;
}
/********************************simulation cases****************************/
#ifdef DVFS_TEST_OFF_BOARD
int rk30_dvfs_init_test(void)
{
struct clk *clk1;
DVFS_DBG("********************************simulation cases****************************\n");
#ifdef DEBUG_RK30_DVFS
DVFS_DBG("\n\n");
dump_dbg_map();
#endif
clk1 = dvfs_clk_get(NULL, "cpu");
if (clk1) {
dvfs_clk_set_rate(clk1, 1008000000);
dump_dbg_map();
dvfs_clk_set_rate(clk1, 816000000);
dump_dbg_map();
dvfs_clk_set_rate(clk1, 0);
dump_dbg_map();
dvfs_clk_set_rate(clk1, 1200000000);
dump_dbg_map();
dvfs_clk_set_rate(clk1, 1009000000);
dump_dbg_map();
dvfs_clk_set_rate(clk1, 1416000000);
dump_dbg_map();
} else {
DVFS_DBG("\t\t%s:\t can not find clk cpu\n", __func__);
}
clk1 = dvfs_clk_get(NULL, "gpu");
if (clk1) {
dvfs_clk_set_rate(clk1, 120000000);
dump_dbg_map();
dvfs_clk_enable(clk1);
dvfs_clk_disable(clk1);
dump_dbg_map();
} else {
DVFS_DBG("\t\t%s:\t can not find clk gpu\n", __func__);
dump_dbg_map();
}
clk1 = dvfs_clk_get(NULL, "arm_pll");
if (clk1) {
dvfs_clk_set_rate(clk1, 24000000);
dump_dbg_map();
} else {
DVFS_DBG("\t\t%s:\t can not find clk arm_pll\n", __func__);
}
DVFS_DBG("********************************simulation cases end***************************\n");
return 0;

View File

@@ -30,8 +30,35 @@ void __init rk30_init_irq(void);
void __init rk30_map_io(void); void __init rk30_map_io(void);
struct machine_desc; struct machine_desc;
void __init rk30_fixup(struct machine_desc *desc, struct tag *tags, char **cmdline, struct meminfo *mi); void __init rk30_fixup(struct machine_desc *desc, struct tag *tags, char **cmdline, struct meminfo *mi);
void __init rk30_clock_init(void); void __init rk30_clock_data_init(unsigned long gpll,unsigned long cpll,unsigned long max_i2s_rate);
void __init board_clock_init(void);
extern struct sys_timer rk30_timer; extern struct sys_timer rk30_timer;
enum _periph_pll {
periph_pll_1485mhz = 148500000,
periph_pll_297mhz = 297000000,
periph_pll_1188mhz = 1188000000, /* for box*/
periph_pll_default = periph_pll_297mhz,
};
enum _codec_pll {
codec_pll_360mhz = 360000000, /* for HDMI */
codec_pll_408mhz = 408000000,
codec_pll_456mhz = 456000000,
codec_pll_504mhz = 504000000,
codec_pll_552mhz = 552000000, /* for HDMI */
codec_pll_600mhz = 600000000,
codec_pll_default = codec_pll_360mhz,
};
enum _max_i2s_rate {
max_i2s_8192khz = 8192000,
max_i2s_11289_6khz = 11289600,
max_i2s_12288khz = 12288000,
max_i2s_22579_2khz = 22579200,
max_i2s_24576khz = 24576000,//HDMI
max_i2s_49152khz = 24576000,//HDMI
max_i2s_default = max_i2s_12288khz,
};
#endif #endif

View File

@@ -0,0 +1,81 @@
/* arch/arm/mach-rk29/include/mach/clock.h
*
* Copyright (C) 2011 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __ASM_ARCH_RK30_CLOCK_H
#define __ASM_ARCH_RK30_CLOCK_H
/**
* struct clk_notifier_data - rate data to pass to the notifier callback
* @clk: struct clk * being changed
* @old_rate: previous rate of this clock
* @new_rate: new rate of this clock
*
* For a pre-notifier, old_rate is the clock's rate before this rate
* change, and new_rate is what the rate will be in the future. For a
* post-notifier, old_rate and new_rate are both set to the clock's
* current rate (this was done to optimize the implementation).
*/
struct clk_notifier_data {
struct clk *clk;
unsigned long old_rate;
unsigned long new_rate;
};
/*
* Clk notifier callback types
*
* Since the notifier is called with interrupts disabled, any actions
* taken by callbacks must be extremely fast and lightweight.
*
* CLK_PRE_RATE_CHANGE - called after all callbacks have approved the
* rate change, immediately before the clock rate is changed, to
* indicate that the rate change will proceed. Drivers must
* immediately terminate any operations that will be affected by
* the rate change. Callbacks must always return NOTIFY_DONE.
*
* CLK_ABORT_RATE_CHANGE: called if the rate change failed for some
* reason after CLK_PRE_RATE_CHANGE. In this case, all registered
* notifiers on the clock will be called with
* CLK_ABORT_RATE_CHANGE. Callbacks must always return
* NOTIFY_DONE.
*
* CLK_POST_RATE_CHANGE - called after the clock rate change has
* successfully completed. Callbacks must always return
* NOTIFY_DONE.
*
*/
#define CLK_PRE_RATE_CHANGE 1
#define CLK_POST_RATE_CHANGE 2
#define CLK_ABORT_RATE_CHANGE 3
#define CLK_PRE_ENABLE 4
#define CLK_POST_ENABLE 5
#define CLK_ABORT_ENABLE 6
#define CLK_PRE_DISABLE 7
#define CLK_POST_DISABLE 8
#define CLK_ABORT_DISABLE 9
struct notifier_block;
extern int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
extern int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
#endif

View File

@@ -35,6 +35,7 @@ enum rk_plls_id {
/********************************************************************/ /********************************************************************/
#define CRU_W_MSK(bits_shift, msk) ((msk) << ((bits_shift) + 16)) #define CRU_W_MSK(bits_shift, msk) ((msk) << ((bits_shift) + 16))
#define CRU_SET_VAL_BITS(val,bits_shift,msk) (((msk)<<((bits_shift)+16))|(val))
/*******************PLL CON0 BITS***************************/ /*******************PLL CON0 BITS***************************/
@@ -45,7 +46,7 @@ enum rk_plls_id {
((((reg) >> (shift)) & (msk)) + 1) ((((reg) >> (shift)) & (msk)) + 1)
#define PLL_OD_MSK (0xf) #define PLL_OD_MSK (0xf)
#define PLL_OD_SHIFT (0) #define PLL_OD_SHIFT (0x0)
#define PLL_CLKOD(val) PLL_CLKFACTOR_SET(val, PLL_OD_SHIFT, PLL_OD_MSK) #define PLL_CLKOD(val) PLL_CLKFACTOR_SET(val, PLL_OD_SHIFT, PLL_OD_MSK)
#define PLL_NO(reg) PLL_CLKFACTOR_GET(reg, PLL_OD_SHIFT, PLL_OD_MSK) #define PLL_NO(reg) PLL_CLKFACTOR_GET(reg, PLL_OD_SHIFT, PLL_OD_MSK)
@@ -135,11 +136,21 @@ enum rk_plls_id {
/*******************MODE BITS***************************/ /*******************MODE BITS***************************/
#define PLL_MODE_W_MSK(id) (0x3 << (16 + (id) * 4))
#define PLL_MODE_MSK(id) (0x3 << ((id) * 4)) #define PLL_MODE_MSK(id) (0x3 << ((id) * 4))
#define PLL_MODE_SLOW(id) (0x0 << ((id) * 4)) #define PLL_MODE_SLOW(id) ((0x0<<((id)*4))|(0x3<<(16+(id)*4)))
#define PLL_MODE_NORM(id) (0x1 << ((id) * 4)) #define PLL_MODE_NORM(id) ((0x1<<((id)*4))|(0x3<<(16+(id)*4)))
#define PLL_MODE_DEEP(id) (0x2 << ((id) * 4)) #define PLL_MODE_DEEP(id) ((0x2<<((id)*4))|(0x3<<(16+(id)*4)))
/*******************clksel10***************************/
#define PERI_ACLK_DIV_MASK 0x1f
#define PERI_ACLK_DIV_OFF 0
#define PERI_HCLK_DIV_MASK 0x3
#define PERI_HCLK_DIV_OFF 8
#define PERI_PCLK_DIV_MASK 0x3
#define PERI_PCLK_DIV_OFF 12
/*******************gate BITS***************************/ /*******************gate BITS***************************/

View File

@@ -0,0 +1,102 @@
/* arch/arm/mach-rk30/rk30_dvfs.h
*
* Copyright (C) 2012 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef _RK30_DVFS_H_
#define _RK30_DVFS_H_
typedef int (*vd_dvfs_target_callback)(struct clk *clk, unsigned long rate);
typedef int (*dvfs_set_rate_callback)(struct clk *clk, unsigned long rate);
typedef int (*clk_dvfs_target_callback)(struct clk *clk, unsigned long rate,
dvfs_set_rate_callback set_rate);
/**
* struct vd_node: To Store All Voltage Domains' info
* @vd_name: Voltage Domain's Name
* @cur_volt: Voltage Domain's Current Voltage
* @vd_list: Point of he Voltage Domain List Node
* @pd_list: Head of Power Domain List Belongs to This Voltage Domain
* @vd_voltreq_list: Head of Voltage Request List for Voltage Domain
*/
struct vd_node {
char *name;
char *regulator_name;
int cur_volt;
struct regulator *regulator;
struct mutex dvfs_mutex;
struct list_head node;
struct list_head pd_list;
vd_dvfs_target_callback vd_dvfs_target;
};
struct vd_node_lookup {
struct vd_node *vd;
char *regulator_name;
};
/**
* struct pd_node: To Store All Power Domains' info per Voltage Domain
* @pd_name: Power Domain's Name
* @cur_volt: Power Domain's Current Voltage
* @pd_list: Point of the Power Domain List Node
* @clk_list: Head of Power Domain's Clocks List
* @pd_status: If The Power Domain On: 1 means on, 0 means off
*/
struct pd_node {
char *name;
int cur_volt;
unsigned char pd_status;
struct vd_node *vd;
struct clk *pd_clk;
struct list_head node;
struct list_head clk_list;
};
struct pd_node_lookup {
struct pd_node* pd;
};
struct clk_list{
struct clk_node *dvfs_clk;
struct list_head node;
};
struct pds_list {
struct clk_list clk_list;
struct pd_node *pd;
};
struct clk_node {
char *name;
int cur_freq;
int cur_volt;
int enable_dvfs;
struct pds_list *pds;
struct vd_node *vd;
struct cpufreq_frequency_table *dvfs_table;
struct notifier_block *dvfs_nb;
struct list_head node;
clk_dvfs_target_callback clk_dvfs_target;
};
int rk30_dvfs_init(void);
int is_support_dvfs(struct clk_node *dvfs_info);
int dvfs_set_rate(struct clk *clk, unsigned long rate);
void clk_set_dvfs_target_rate_callback(struct clk *ck, clk_dvfs_target_callback clk_dvfs_target);
int clk_enable_dvfs(struct clk *clk);
int clk_disable_dvfs(struct clk *clk);
int cpufreq_dvfs_init(struct clk *ck, struct cpufreq_frequency_table **table, clk_dvfs_target_callback clk_dvfs_target);
int clk_dvfs_set_dvfs_table(struct clk *clk,struct cpufreq_frequency_table *table);
#endif

2
arch/arm/mach-rk30/timer.c Normal file → Executable file
View File

@@ -203,6 +203,8 @@ static void __init rk30_sched_clock_init(void)
static void __init rk30_timer_init(void) static void __init rk30_timer_init(void)
{ {
printk("rk30_timer_init\n");
#ifdef CONFIG_HAVE_ARM_TWD #ifdef CONFIG_HAVE_ARM_TWD
twd_base = RK30_PTIMER_BASE; twd_base = RK30_PTIMER_BASE;
#endif #endif