mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 20:32:04 +09:00
rk30: add clock support
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
obj-y += clock.o
|
||||
obj-y += clock_data.o
|
||||
obj-y += dvfs.o
|
||||
obj-y += common.o
|
||||
obj-y += devices.o
|
||||
obj-y += io.o
|
||||
|
||||
@@ -1060,6 +1060,11 @@ static void __init rk30_reserve(void)
|
||||
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")
|
||||
.boot_params = PLAT_PHYS_OFFSET + 0x800,
|
||||
.fixup = rk30_fixup,
|
||||
|
||||
760
arch/arm/mach-rk30/clock.c
Normal file → Executable file
760
arch/arm/mach-rk30/clock.c
Normal file → Executable 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/clkdev.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.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 */
|
||||
/* 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 */
|
||||
|
||||
#define MHZ (1000*1000)
|
||||
#define KHZ 1000
|
||||
#define KHZ (1000)
|
||||
|
||||
struct clk {
|
||||
struct list_head node;
|
||||
const char *name;
|
||||
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 void __clk_recalc(struct clk *clk);
|
||||
static void __propagate_rate(struct clk *tclk);
|
||||
static void __clk_reparent(struct clk *child, struct clk *parent);
|
||||
|
||||
static struct clk xin24m = {
|
||||
.name = "xin24m",
|
||||
.rate = 24 * MHZ,
|
||||
.flags = RATE_FIXED,
|
||||
};
|
||||
static LIST_HEAD(clocks);
|
||||
static DEFINE_MUTEX(clocks_mutex);
|
||||
static DEFINE_SPINLOCK(clockfw_lock);
|
||||
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) \
|
||||
{ \
|
||||
.dev_id = dev, \
|
||||
.con_id = con, \
|
||||
.clk = 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)
|
||||
/**********************************************for clock data****************************************************/
|
||||
static struct clk *def_ops_clk=NULL;
|
||||
|
||||
static struct clk_lookup clks[] = {
|
||||
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)
|
||||
void clk_register_default_ops_clk(struct clk *clk)
|
||||
{
|
||||
struct clk_lookup *lk;
|
||||
|
||||
for (lk = clks; lk < clks + ARRAY_SIZE(clks); lk++) {
|
||||
clkdev_add(lk);
|
||||
}
|
||||
def_ops_clk=clk;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (clk == NULL || IS_ERR(clk))
|
||||
return -EINVAL;
|
||||
|
||||
LOCK();
|
||||
ret = clk_enable_nolock(clk);
|
||||
UNLOCK();
|
||||
|
||||
return ret;
|
||||
}
|
||||
#ifdef RK30_CLK_OFFBOARD_TEST
|
||||
EXPORT_SYMBOL(rk30_clk_enable);
|
||||
#else
|
||||
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))
|
||||
return;
|
||||
list_del(&cn->node);
|
||||
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;
|
||||
}
|
||||
EXPORT_SYMBOL(clk_get_rate);
|
||||
struct clk_notifier *cn;
|
||||
struct clk_notifier_data cnd;
|
||||
|
||||
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))
|
||||
return ret;
|
||||
if (!clk || IS_ERR(clk) || !nb)
|
||||
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 */
|
||||
|
||||
|
||||
98
arch/arm/mach-rk30/clock.h
Normal file
98
arch/arm/mach-rk30/clock.h
Normal 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
|
||||
3248
arch/arm/mach-rk30/clock_data.c
Normal file
3248
arch/arm/mach-rk30/clock_data.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ void __init rk30_map_io(void)
|
||||
rk30_map_common_io();
|
||||
rk29_setup_early_printk();
|
||||
rk29_sram_init();
|
||||
rk30_clock_init();
|
||||
board_clock_init();
|
||||
rk30_l2_cache_init();
|
||||
rk30_iomux_init();
|
||||
}
|
||||
|
||||
942
arch/arm/mach-rk30/dvfs.c
Normal file
942
arch/arm/mach-rk30/dvfs.c
Normal 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 ®ulators[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;
|
||||
|
||||
@@ -30,8 +30,35 @@ void __init rk30_init_irq(void);
|
||||
void __init rk30_map_io(void);
|
||||
struct machine_desc;
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
81
arch/arm/mach-rk30/include/mach/clock.h
Executable file
81
arch/arm/mach-rk30/include/mach/clock.h
Executable 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ enum rk_plls_id {
|
||||
/********************************************************************/
|
||||
|
||||
#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***************************/
|
||||
|
||||
@@ -45,7 +46,7 @@ enum rk_plls_id {
|
||||
((((reg) >> (shift)) & (msk)) + 1)
|
||||
|
||||
#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_NO(reg) PLL_CLKFACTOR_GET(reg, PLL_OD_SHIFT, PLL_OD_MSK)
|
||||
@@ -135,11 +136,21 @@ enum rk_plls_id {
|
||||
|
||||
/*******************MODE BITS***************************/
|
||||
|
||||
#define PLL_MODE_W_MSK(id) (0x3 << (16 + (id) * 4))
|
||||
#define PLL_MODE_MSK(id) (0x3 << ((id) * 4))
|
||||
#define PLL_MODE_SLOW(id) (0x0 << ((id) * 4))
|
||||
#define PLL_MODE_NORM(id) (0x1 << ((id) * 4))
|
||||
#define PLL_MODE_DEEP(id) (0x2 << ((id) * 4))
|
||||
#define PLL_MODE_SLOW(id) ((0x0<<((id)*4))|(0x3<<(16+(id)*4)))
|
||||
#define PLL_MODE_NORM(id) ((0x1<<((id)*4))|(0x3<<(16+(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***************************/
|
||||
|
||||
|
||||
102
arch/arm/mach-rk30/include/mach/dvfs.h
Normal file
102
arch/arm/mach-rk30/include/mach/dvfs.h
Normal 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
2
arch/arm/mach-rk30/timer.c
Normal file → Executable file
@@ -203,6 +203,8 @@ static void __init rk30_sched_clock_init(void)
|
||||
|
||||
static void __init rk30_timer_init(void)
|
||||
{
|
||||
|
||||
printk("rk30_timer_init\n");
|
||||
#ifdef CONFIG_HAVE_ARM_TWD
|
||||
twd_base = RK30_PTIMER_BASE;
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user