mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-23 20:53:53 +08:00
af47e6bb88
Introduce an IP reset API for use on DaVinci SoC. There is no existing "reset" framework support for SoC devices. The remoteproc driver needs explicit control of the DSP's reset line. To support this, a new DaVinci specific API is added. This private API will disappear with DT migration. Some discussion regarding a proposed DT "reset" binding is here: https://patchwork.kernel.org/patch/1635051/ Modify davinci_clk_init() to set clk "reset" function for clocks that indicate PSC_LRST support. Also fix indentation issue with function opening curly brace. Signed-off-by: Robert Tivy <rtivy@ti.com> [nsekhar@ti.com: rename davinci_psc_config_reset() to davinci_psc_reset()] Signed-off-by: Sekhar Nori <nsekhar@ti.com>
707 lines
15 KiB
C
707 lines
15 KiB
C
/*
|
|
* Clock and PLL control for DaVinci devices
|
|
*
|
|
* Copyright (C) 2006-2007 Texas Instruments.
|
|
* Copyright (C) 2008-2009 Deep Root Systems, LLC
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <mach/hardware.h>
|
|
|
|
#include <mach/clock.h>
|
|
#include <mach/psc.h>
|
|
#include <mach/cputype.h>
|
|
#include "clock.h"
|
|
|
|
static LIST_HEAD(clocks);
|
|
static DEFINE_MUTEX(clocks_mutex);
|
|
static DEFINE_SPINLOCK(clockfw_lock);
|
|
|
|
static void __clk_enable(struct clk *clk)
|
|
{
|
|
if (clk->parent)
|
|
__clk_enable(clk->parent);
|
|
if (clk->usecount++ == 0 && (clk->flags & CLK_PSC))
|
|
davinci_psc_config(clk->domain, clk->gpsc, clk->lpsc,
|
|
true, clk->flags);
|
|
}
|
|
|
|
static void __clk_disable(struct clk *clk)
|
|
{
|
|
if (WARN_ON(clk->usecount == 0))
|
|
return;
|
|
if (--clk->usecount == 0 && !(clk->flags & CLK_PLL) &&
|
|
(clk->flags & CLK_PSC))
|
|
davinci_psc_config(clk->domain, clk->gpsc, clk->lpsc,
|
|
false, clk->flags);
|
|
if (clk->parent)
|
|
__clk_disable(clk->parent);
|
|
}
|
|
|
|
int davinci_clk_reset(struct clk *clk, bool reset)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
if (clk->flags & CLK_PSC)
|
|
davinci_psc_reset(clk->gpsc, clk->lpsc, reset);
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(davinci_clk_reset);
|
|
|
|
int davinci_clk_reset_assert(struct clk *clk)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk) || !clk->reset)
|
|
return -EINVAL;
|
|
|
|
return clk->reset(clk, true);
|
|
}
|
|
EXPORT_SYMBOL(davinci_clk_reset_assert);
|
|
|
|
int davinci_clk_reset_deassert(struct clk *clk)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk) || !clk->reset)
|
|
return -EINVAL;
|
|
|
|
return clk->reset(clk, false);
|
|
}
|
|
EXPORT_SYMBOL(davinci_clk_reset_deassert);
|
|
|
|
int clk_enable(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
__clk_enable(clk);
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_enable);
|
|
|
|
void clk_disable(struct clk *clk)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return;
|
|
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
__clk_disable(clk);
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(clk_disable);
|
|
|
|
unsigned long clk_get_rate(struct clk *clk)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
return clk->rate;
|
|
}
|
|
EXPORT_SYMBOL(clk_get_rate);
|
|
|
|
long clk_round_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
if (clk->round_rate)
|
|
return clk->round_rate(clk, rate);
|
|
|
|
return clk->rate;
|
|
}
|
|
EXPORT_SYMBOL(clk_round_rate);
|
|
|
|
/* Propagate rate to children */
|
|
static void propagate_rate(struct clk *root)
|
|
{
|
|
struct clk *clk;
|
|
|
|
list_for_each_entry(clk, &root->children, childnode) {
|
|
if (clk->recalc)
|
|
clk->rate = clk->recalc(clk);
|
|
propagate_rate(clk);
|
|
}
|
|
}
|
|
|
|
int clk_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
unsigned long flags;
|
|
int ret = -EINVAL;
|
|
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return ret;
|
|
|
|
if (clk->set_rate)
|
|
ret = clk->set_rate(clk, rate);
|
|
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
if (ret == 0) {
|
|
if (clk->recalc)
|
|
clk->rate = clk->recalc(clk);
|
|
propagate_rate(clk);
|
|
}
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_rate);
|
|
|
|
int clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
/* Cannot change parent on enabled clock */
|
|
if (WARN_ON(clk->usecount))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&clocks_mutex);
|
|
clk->parent = parent;
|
|
list_del_init(&clk->childnode);
|
|
list_add(&clk->childnode, &clk->parent->children);
|
|
mutex_unlock(&clocks_mutex);
|
|
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
if (clk->recalc)
|
|
clk->rate = clk->recalc(clk);
|
|
propagate_rate(clk);
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_set_parent);
|
|
|
|
int clk_register(struct clk *clk)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return -EINVAL;
|
|
|
|
if (WARN(clk->parent && !clk->parent->rate,
|
|
"CLK: %s parent %s has no rate!\n",
|
|
clk->name, clk->parent->name))
|
|
return -EINVAL;
|
|
|
|
INIT_LIST_HEAD(&clk->children);
|
|
|
|
mutex_lock(&clocks_mutex);
|
|
list_add_tail(&clk->node, &clocks);
|
|
if (clk->parent)
|
|
list_add_tail(&clk->childnode, &clk->parent->children);
|
|
mutex_unlock(&clocks_mutex);
|
|
|
|
/* If rate is already set, use it */
|
|
if (clk->rate)
|
|
return 0;
|
|
|
|
/* Else, see if there is a way to calculate it */
|
|
if (clk->recalc)
|
|
clk->rate = clk->recalc(clk);
|
|
|
|
/* Otherwise, default to parent rate */
|
|
else if (clk->parent)
|
|
clk->rate = clk->parent->rate;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clk_register);
|
|
|
|
void clk_unregister(struct clk *clk)
|
|
{
|
|
if (clk == NULL || IS_ERR(clk))
|
|
return;
|
|
|
|
mutex_lock(&clocks_mutex);
|
|
list_del(&clk->node);
|
|
list_del(&clk->childnode);
|
|
mutex_unlock(&clocks_mutex);
|
|
}
|
|
EXPORT_SYMBOL(clk_unregister);
|
|
|
|
#ifdef CONFIG_DAVINCI_RESET_CLOCKS
|
|
/*
|
|
* Disable any unused clocks left on by the bootloader
|
|
*/
|
|
int __init davinci_clk_disable_unused(void)
|
|
{
|
|
struct clk *ck;
|
|
|
|
spin_lock_irq(&clockfw_lock);
|
|
list_for_each_entry(ck, &clocks, node) {
|
|
if (ck->usecount > 0)
|
|
continue;
|
|
if (!(ck->flags & CLK_PSC))
|
|
continue;
|
|
|
|
/* ignore if in Disabled or SwRstDisable states */
|
|
if (!davinci_psc_is_clk_active(ck->gpsc, ck->lpsc))
|
|
continue;
|
|
|
|
pr_debug("Clocks: disable unused %s\n", ck->name);
|
|
|
|
davinci_psc_config(ck->domain, ck->gpsc, ck->lpsc,
|
|
false, ck->flags);
|
|
}
|
|
spin_unlock_irq(&clockfw_lock);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static unsigned long clk_sysclk_recalc(struct clk *clk)
|
|
{
|
|
u32 v, plldiv;
|
|
struct pll_data *pll;
|
|
unsigned long rate = clk->rate;
|
|
|
|
/* If this is the PLL base clock, no more calculations needed */
|
|
if (clk->pll_data)
|
|
return rate;
|
|
|
|
if (WARN_ON(!clk->parent))
|
|
return rate;
|
|
|
|
rate = clk->parent->rate;
|
|
|
|
/* Otherwise, the parent must be a PLL */
|
|
if (WARN_ON(!clk->parent->pll_data))
|
|
return rate;
|
|
|
|
pll = clk->parent->pll_data;
|
|
|
|
/* If pre-PLL, source clock is before the multiplier and divider(s) */
|
|
if (clk->flags & PRE_PLL)
|
|
rate = pll->input_rate;
|
|
|
|
if (!clk->div_reg)
|
|
return rate;
|
|
|
|
v = __raw_readl(pll->base + clk->div_reg);
|
|
if (v & PLLDIV_EN) {
|
|
plldiv = (v & pll->div_ratio_mask) + 1;
|
|
if (plldiv)
|
|
rate /= plldiv;
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
int davinci_set_sysclk_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
unsigned v;
|
|
struct pll_data *pll;
|
|
unsigned long input;
|
|
unsigned ratio = 0;
|
|
|
|
/* If this is the PLL base clock, wrong function to call */
|
|
if (clk->pll_data)
|
|
return -EINVAL;
|
|
|
|
/* There must be a parent... */
|
|
if (WARN_ON(!clk->parent))
|
|
return -EINVAL;
|
|
|
|
/* ... the parent must be a PLL... */
|
|
if (WARN_ON(!clk->parent->pll_data))
|
|
return -EINVAL;
|
|
|
|
/* ... and this clock must have a divider. */
|
|
if (WARN_ON(!clk->div_reg))
|
|
return -EINVAL;
|
|
|
|
pll = clk->parent->pll_data;
|
|
|
|
input = clk->parent->rate;
|
|
|
|
/* If pre-PLL, source clock is before the multiplier and divider(s) */
|
|
if (clk->flags & PRE_PLL)
|
|
input = pll->input_rate;
|
|
|
|
if (input > rate) {
|
|
/*
|
|
* Can afford to provide an output little higher than requested
|
|
* only if maximum rate supported by hardware on this sysclk
|
|
* is known.
|
|
*/
|
|
if (clk->maxrate) {
|
|
ratio = DIV_ROUND_CLOSEST(input, rate);
|
|
if (input / ratio > clk->maxrate)
|
|
ratio = 0;
|
|
}
|
|
|
|
if (ratio == 0)
|
|
ratio = DIV_ROUND_UP(input, rate);
|
|
|
|
ratio--;
|
|
}
|
|
|
|
if (ratio > pll->div_ratio_mask)
|
|
return -EINVAL;
|
|
|
|
do {
|
|
v = __raw_readl(pll->base + PLLSTAT);
|
|
} while (v & PLLSTAT_GOSTAT);
|
|
|
|
v = __raw_readl(pll->base + clk->div_reg);
|
|
v &= ~pll->div_ratio_mask;
|
|
v |= ratio | PLLDIV_EN;
|
|
__raw_writel(v, pll->base + clk->div_reg);
|
|
|
|
v = __raw_readl(pll->base + PLLCMD);
|
|
v |= PLLCMD_GOSET;
|
|
__raw_writel(v, pll->base + PLLCMD);
|
|
|
|
do {
|
|
v = __raw_readl(pll->base + PLLSTAT);
|
|
} while (v & PLLSTAT_GOSTAT);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(davinci_set_sysclk_rate);
|
|
|
|
static unsigned long clk_leafclk_recalc(struct clk *clk)
|
|
{
|
|
if (WARN_ON(!clk->parent))
|
|
return clk->rate;
|
|
|
|
return clk->parent->rate;
|
|
}
|
|
|
|
int davinci_simple_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
clk->rate = rate;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long clk_pllclk_recalc(struct clk *clk)
|
|
{
|
|
u32 ctrl, mult = 1, prediv = 1, postdiv = 1;
|
|
u8 bypass;
|
|
struct pll_data *pll = clk->pll_data;
|
|
unsigned long rate = clk->rate;
|
|
|
|
ctrl = __raw_readl(pll->base + PLLCTL);
|
|
rate = pll->input_rate = clk->parent->rate;
|
|
|
|
if (ctrl & PLLCTL_PLLEN) {
|
|
bypass = 0;
|
|
mult = __raw_readl(pll->base + PLLM);
|
|
if (cpu_is_davinci_dm365())
|
|
mult = 2 * (mult & PLLM_PLLM_MASK);
|
|
else
|
|
mult = (mult & PLLM_PLLM_MASK) + 1;
|
|
} else
|
|
bypass = 1;
|
|
|
|
if (pll->flags & PLL_HAS_PREDIV) {
|
|
prediv = __raw_readl(pll->base + PREDIV);
|
|
if (prediv & PLLDIV_EN)
|
|
prediv = (prediv & pll->div_ratio_mask) + 1;
|
|
else
|
|
prediv = 1;
|
|
}
|
|
|
|
/* pre-divider is fixed, but (some?) chips won't report that */
|
|
if (cpu_is_davinci_dm355() && pll->num == 1)
|
|
prediv = 8;
|
|
|
|
if (pll->flags & PLL_HAS_POSTDIV) {
|
|
postdiv = __raw_readl(pll->base + POSTDIV);
|
|
if (postdiv & PLLDIV_EN)
|
|
postdiv = (postdiv & pll->div_ratio_mask) + 1;
|
|
else
|
|
postdiv = 1;
|
|
}
|
|
|
|
if (!bypass) {
|
|
rate /= prediv;
|
|
rate *= mult;
|
|
rate /= postdiv;
|
|
}
|
|
|
|
pr_debug("PLL%d: input = %lu MHz [ ",
|
|
pll->num, clk->parent->rate / 1000000);
|
|
if (bypass)
|
|
pr_debug("bypass ");
|
|
if (prediv > 1)
|
|
pr_debug("/ %d ", prediv);
|
|
if (mult > 1)
|
|
pr_debug("* %d ", mult);
|
|
if (postdiv > 1)
|
|
pr_debug("/ %d ", postdiv);
|
|
pr_debug("] --> %lu MHz output.\n", rate / 1000000);
|
|
|
|
return rate;
|
|
}
|
|
|
|
/**
|
|
* davinci_set_pllrate - set the output rate of a given PLL.
|
|
*
|
|
* Note: Currently tested to work with OMAP-L138 only.
|
|
*
|
|
* @pll: pll whose rate needs to be changed.
|
|
* @prediv: The pre divider value. Passing 0 disables the pre-divider.
|
|
* @pllm: The multiplier value. Passing 0 leads to multiply-by-one.
|
|
* @postdiv: The post divider value. Passing 0 disables the post-divider.
|
|
*/
|
|
int davinci_set_pllrate(struct pll_data *pll, unsigned int prediv,
|
|
unsigned int mult, unsigned int postdiv)
|
|
{
|
|
u32 ctrl;
|
|
unsigned int locktime;
|
|
unsigned long flags;
|
|
|
|
if (pll->base == NULL)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* PLL lock time required per OMAP-L138 datasheet is
|
|
* (2000 * prediv)/sqrt(pllm) OSCIN cycles. We approximate sqrt(pllm)
|
|
* as 4 and OSCIN cycle as 25 MHz.
|
|
*/
|
|
if (prediv) {
|
|
locktime = ((2000 * prediv) / 100);
|
|
prediv = (prediv - 1) | PLLDIV_EN;
|
|
} else {
|
|
locktime = PLL_LOCK_TIME;
|
|
}
|
|
if (postdiv)
|
|
postdiv = (postdiv - 1) | PLLDIV_EN;
|
|
if (mult)
|
|
mult = mult - 1;
|
|
|
|
/* Protect against simultaneous calls to PLL setting seqeunce */
|
|
spin_lock_irqsave(&clockfw_lock, flags);
|
|
|
|
ctrl = __raw_readl(pll->base + PLLCTL);
|
|
|
|
/* Switch the PLL to bypass mode */
|
|
ctrl &= ~(PLLCTL_PLLENSRC | PLLCTL_PLLEN);
|
|
__raw_writel(ctrl, pll->base + PLLCTL);
|
|
|
|
udelay(PLL_BYPASS_TIME);
|
|
|
|
/* Reset and enable PLL */
|
|
ctrl &= ~(PLLCTL_PLLRST | PLLCTL_PLLDIS);
|
|
__raw_writel(ctrl, pll->base + PLLCTL);
|
|
|
|
if (pll->flags & PLL_HAS_PREDIV)
|
|
__raw_writel(prediv, pll->base + PREDIV);
|
|
|
|
__raw_writel(mult, pll->base + PLLM);
|
|
|
|
if (pll->flags & PLL_HAS_POSTDIV)
|
|
__raw_writel(postdiv, pll->base + POSTDIV);
|
|
|
|
udelay(PLL_RESET_TIME);
|
|
|
|
/* Bring PLL out of reset */
|
|
ctrl |= PLLCTL_PLLRST;
|
|
__raw_writel(ctrl, pll->base + PLLCTL);
|
|
|
|
udelay(locktime);
|
|
|
|
/* Remove PLL from bypass mode */
|
|
ctrl |= PLLCTL_PLLEN;
|
|
__raw_writel(ctrl, pll->base + PLLCTL);
|
|
|
|
spin_unlock_irqrestore(&clockfw_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(davinci_set_pllrate);
|
|
|
|
/**
|
|
* davinci_set_refclk_rate() - Set the reference clock rate
|
|
* @rate: The new rate.
|
|
*
|
|
* Sets the reference clock rate to a given value. This will most likely
|
|
* result in the entire clock tree getting updated.
|
|
*
|
|
* This is used to support boards which use a reference clock different
|
|
* than that used by default in <soc>.c file. The reference clock rate
|
|
* should be updated early in the boot process; ideally soon after the
|
|
* clock tree has been initialized once with the default reference clock
|
|
* rate (davinci_common_init()).
|
|
*
|
|
* Returns 0 on success, error otherwise.
|
|
*/
|
|
int davinci_set_refclk_rate(unsigned long rate)
|
|
{
|
|
struct clk *refclk;
|
|
|
|
refclk = clk_get(NULL, "ref");
|
|
if (IS_ERR(refclk)) {
|
|
pr_err("%s: failed to get reference clock.\n", __func__);
|
|
return PTR_ERR(refclk);
|
|
}
|
|
|
|
clk_set_rate(refclk, rate);
|
|
|
|
clk_put(refclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __init davinci_clk_init(struct clk_lookup *clocks)
|
|
{
|
|
struct clk_lookup *c;
|
|
struct clk *clk;
|
|
size_t num_clocks = 0;
|
|
|
|
for (c = clocks; c->clk; c++) {
|
|
clk = c->clk;
|
|
|
|
if (!clk->recalc) {
|
|
|
|
/* Check if clock is a PLL */
|
|
if (clk->pll_data)
|
|
clk->recalc = clk_pllclk_recalc;
|
|
|
|
/* Else, if it is a PLL-derived clock */
|
|
else if (clk->flags & CLK_PLL)
|
|
clk->recalc = clk_sysclk_recalc;
|
|
|
|
/* Otherwise, it is a leaf clock (PSC clock) */
|
|
else if (clk->parent)
|
|
clk->recalc = clk_leafclk_recalc;
|
|
}
|
|
|
|
if (clk->pll_data) {
|
|
struct pll_data *pll = clk->pll_data;
|
|
|
|
if (!pll->div_ratio_mask)
|
|
pll->div_ratio_mask = PLLDIV_RATIO_MASK;
|
|
|
|
if (pll->phys_base && !pll->base) {
|
|
pll->base = ioremap(pll->phys_base, SZ_4K);
|
|
WARN_ON(!pll->base);
|
|
}
|
|
}
|
|
|
|
if (clk->recalc)
|
|
clk->rate = clk->recalc(clk);
|
|
|
|
if (clk->lpsc)
|
|
clk->flags |= CLK_PSC;
|
|
|
|
if (clk->flags & PSC_LRST)
|
|
clk->reset = davinci_clk_reset;
|
|
|
|
clk_register(clk);
|
|
num_clocks++;
|
|
|
|
/* Turn on clocks that Linux doesn't otherwise manage */
|
|
if (clk->flags & ALWAYS_ENABLED)
|
|
clk_enable(clk);
|
|
}
|
|
|
|
clkdev_add_table(clocks, num_clocks);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
#define CLKNAME_MAX 10 /* longest clock name */
|
|
#define NEST_DELTA 2
|
|
#define NEST_MAX 4
|
|
|
|
static void
|
|
dump_clock(struct seq_file *s, unsigned nest, struct clk *parent)
|
|
{
|
|
char *state;
|
|
char buf[CLKNAME_MAX + NEST_DELTA * NEST_MAX];
|
|
struct clk *clk;
|
|
unsigned i;
|
|
|
|
if (parent->flags & CLK_PLL)
|
|
state = "pll";
|
|
else if (parent->flags & CLK_PSC)
|
|
state = "psc";
|
|
else
|
|
state = "";
|
|
|
|
/* <nest spaces> name <pad to end> */
|
|
memset(buf, ' ', sizeof(buf) - 1);
|
|
buf[sizeof(buf) - 1] = 0;
|
|
i = strlen(parent->name);
|
|
memcpy(buf + nest, parent->name,
|
|
min(i, (unsigned)(sizeof(buf) - 1 - nest)));
|
|
|
|
seq_printf(s, "%s users=%2d %-3s %9ld Hz\n",
|
|
buf, parent->usecount, state, clk_get_rate(parent));
|
|
/* REVISIT show device associations too */
|
|
|
|
/* cost is now small, but not linear... */
|
|
list_for_each_entry(clk, &parent->children, childnode) {
|
|
dump_clock(s, nest + NEST_DELTA, clk);
|
|
}
|
|
}
|
|
|
|
static int davinci_ck_show(struct seq_file *m, void *v)
|
|
{
|
|
struct clk *clk;
|
|
|
|
/*
|
|
* Show clock tree; We trust nonzero usecounts equate to PSC enables...
|
|
*/
|
|
mutex_lock(&clocks_mutex);
|
|
list_for_each_entry(clk, &clocks, node)
|
|
if (!clk->parent)
|
|
dump_clock(m, 0, clk);
|
|
mutex_unlock(&clocks_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int davinci_ck_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, davinci_ck_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations davinci_ck_operations = {
|
|
.open = davinci_ck_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int __init davinci_clk_debugfs_init(void)
|
|
{
|
|
debugfs_create_file("davinci_clocks", S_IFREG | S_IRUGO, NULL, NULL,
|
|
&davinci_ck_operations);
|
|
return 0;
|
|
|
|
}
|
|
device_initcall(davinci_clk_debugfs_init);
|
|
#endif /* CONFIG_DEBUG_FS */
|