5dcc3b975e
While we move to Common Clk Framework (CCF), direct deferencing of struct clk wouldn't be possible anymore. Hence get rid of all such instances in the current clock code and use macros/helpers similar to the ones that are provided by CCF. While here also concatenate some strings split across multiple lines which seem to be needed anyway. Signed-off-by: Rajendra Nayak <rnayak@ti.com> [paul@pwsan.com: simplified some compound expressions; reformatted some messages] Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Mike Turquette <mturquette@linaro.org>
534 lines
15 KiB
C
534 lines
15 KiB
C
/*
|
|
* clkt_clksel.c - OMAP2/3/4 clksel clock functions
|
|
*
|
|
* Copyright (C) 2005-2008 Texas Instruments, Inc.
|
|
* Copyright (C) 2004-2010 Nokia Corporation
|
|
*
|
|
* Contacts:
|
|
* Richard Woodruff <r-woodruff2@ti.com>
|
|
* Paul Walmsley
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*
|
|
* clksel clocks are clocks that do not have a fixed parent, or that
|
|
* can divide their parent's rate, or possibly both at the same time, based
|
|
* on the contents of a hardware register bitfield.
|
|
*
|
|
* All of the various mux and divider settings can be encoded into
|
|
* struct clksel* data structures, and then these can be autogenerated
|
|
* from some hardware database for each new chip generation. This
|
|
* should avoid the need to write, review, and validate a lot of new
|
|
* clock code for each new chip, since it can be exported from the SoC
|
|
* design flow. This is now done on OMAP4.
|
|
*
|
|
* The fusion of mux and divider clocks is a software creation. In
|
|
* hardware reality, the multiplexer (parent selection) and the
|
|
* divider exist separately. XXX At some point these clksel clocks
|
|
* should be split into "divider" clocks and "mux" clocks to better
|
|
* match the hardware.
|
|
*
|
|
* (The name "clksel" comes from the name of the corresponding
|
|
* register field in the OMAP2/3 family of SoCs.)
|
|
*
|
|
* XXX Currently these clocks are only used in the OMAP2/3/4 code, but
|
|
* many of the OMAP1 clocks should be convertible to use this
|
|
* mechanism.
|
|
*/
|
|
#undef DEBUG
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/bug.h>
|
|
|
|
#include <plat/clock.h>
|
|
|
|
#include "clock.h"
|
|
|
|
/* Private functions */
|
|
|
|
/**
|
|
* _get_clksel_by_parent() - return clksel struct for a given clk & parent
|
|
* @clk: OMAP struct clk ptr to inspect
|
|
* @src_clk: OMAP struct clk ptr of the parent clk to search for
|
|
*
|
|
* Scan the struct clksel array associated with the clock to find
|
|
* the element associated with the supplied parent clock address.
|
|
* Returns a pointer to the struct clksel on success or NULL on error.
|
|
*/
|
|
static const struct clksel *_get_clksel_by_parent(struct clk *clk,
|
|
struct clk *src_clk)
|
|
{
|
|
const struct clksel *clks;
|
|
|
|
for (clks = clk->clksel; clks->parent; clks++)
|
|
if (clks->parent == src_clk)
|
|
break; /* Found the requested parent */
|
|
|
|
if (!clks->parent) {
|
|
/* This indicates a data problem */
|
|
WARN(1, "clock: %s: could not find parent clock %s in clksel array\n",
|
|
__clk_get_name(clk), __clk_get_name(src_clk));
|
|
return NULL;
|
|
}
|
|
|
|
return clks;
|
|
}
|
|
|
|
/**
|
|
* _get_div_and_fieldval() - find the new clksel divisor and field value to use
|
|
* @src_clk: planned new parent struct clk *
|
|
* @clk: struct clk * that is being reparented
|
|
* @field_val: pointer to a u32 to contain the register data for the divisor
|
|
*
|
|
* Given an intended new parent struct clk * @src_clk, and the struct
|
|
* clk * @clk to the clock that is being reparented, find the
|
|
* appropriate rate divisor for the new clock (returned as the return
|
|
* value), and the corresponding register bitfield data to program to
|
|
* reach that divisor (returned in the u32 pointed to by @field_val).
|
|
* Returns 0 on error, or returns the newly-selected divisor upon
|
|
* success (in this latter case, the corresponding register bitfield
|
|
* value is passed back in the variable pointed to by @field_val)
|
|
*/
|
|
static u8 _get_div_and_fieldval(struct clk *src_clk, struct clk *clk,
|
|
u32 *field_val)
|
|
{
|
|
const struct clksel *clks;
|
|
const struct clksel_rate *clkr, *max_clkr = NULL;
|
|
u8 max_div = 0;
|
|
|
|
clks = _get_clksel_by_parent(clk, src_clk);
|
|
if (!clks)
|
|
return 0;
|
|
|
|
/*
|
|
* Find the highest divisor (e.g., the one resulting in the
|
|
* lowest rate) to use as the default. This should avoid
|
|
* clock rates that are too high for the device. XXX A better
|
|
* solution here would be to try to determine if there is a
|
|
* divisor matching the original clock rate before the parent
|
|
* switch, and if it cannot be found, to fall back to the
|
|
* highest divisor.
|
|
*/
|
|
for (clkr = clks->rates; clkr->div; clkr++) {
|
|
if (!(clkr->flags & cpu_mask))
|
|
continue;
|
|
|
|
if (clkr->div > max_div) {
|
|
max_div = clkr->div;
|
|
max_clkr = clkr;
|
|
}
|
|
}
|
|
|
|
if (max_div == 0) {
|
|
/* This indicates an error in the clksel data */
|
|
WARN(1, "clock: %s: could not find divisor for parent %s\n",
|
|
__clk_get_name(clk),
|
|
__clk_get_name(__clk_get_parent(src_clk)));
|
|
return 0;
|
|
}
|
|
|
|
*field_val = max_clkr->val;
|
|
|
|
return max_div;
|
|
}
|
|
|
|
/**
|
|
* _write_clksel_reg() - program a clock's clksel register in hardware
|
|
* @clk: struct clk * to program
|
|
* @v: clksel bitfield value to program (with LSB at bit 0)
|
|
*
|
|
* Shift the clksel register bitfield value @v to its appropriate
|
|
* location in the clksel register and write it in. This function
|
|
* will ensure that the write to the clksel_reg reaches its
|
|
* destination before returning -- important since PRM and CM register
|
|
* accesses can be quite slow compared to ARM cycles -- but does not
|
|
* take into account any time the hardware might take to switch the
|
|
* clock source.
|
|
*/
|
|
static void _write_clksel_reg(struct clk *clk, u32 field_val)
|
|
{
|
|
u32 v;
|
|
|
|
v = __raw_readl(clk->clksel_reg);
|
|
v &= ~clk->clksel_mask;
|
|
v |= field_val << __ffs(clk->clksel_mask);
|
|
__raw_writel(v, clk->clksel_reg);
|
|
|
|
v = __raw_readl(clk->clksel_reg); /* OCP barrier */
|
|
}
|
|
|
|
/**
|
|
* _clksel_to_divisor() - turn clksel field value into integer divider
|
|
* @clk: OMAP struct clk to use
|
|
* @field_val: register field value to find
|
|
*
|
|
* Given a struct clk of a rate-selectable clksel clock, and a register field
|
|
* value to search for, find the corresponding clock divisor. The register
|
|
* field value should be pre-masked and shifted down so the LSB is at bit 0
|
|
* before calling. Returns 0 on error or returns the actual integer divisor
|
|
* upon success.
|
|
*/
|
|
static u32 _clksel_to_divisor(struct clk *clk, u32 field_val)
|
|
{
|
|
const struct clksel *clks;
|
|
const struct clksel_rate *clkr;
|
|
struct clk *parent;
|
|
|
|
parent = __clk_get_parent(clk);
|
|
clks = _get_clksel_by_parent(clk, parent);
|
|
if (!clks)
|
|
return 0;
|
|
|
|
for (clkr = clks->rates; clkr->div; clkr++) {
|
|
if (!(clkr->flags & cpu_mask))
|
|
continue;
|
|
|
|
if (clkr->val == field_val)
|
|
break;
|
|
}
|
|
|
|
if (!clkr->div) {
|
|
/* This indicates a data error */
|
|
WARN(1, "clock: %s: could not find fieldval %d for parent %s\n",
|
|
__clk_get_name(clk), field_val, __clk_get_name(parent));
|
|
return 0;
|
|
}
|
|
|
|
return clkr->div;
|
|
}
|
|
|
|
/**
|
|
* _divisor_to_clksel() - turn clksel integer divisor into a field value
|
|
* @clk: OMAP struct clk to use
|
|
* @div: integer divisor to search for
|
|
*
|
|
* Given a struct clk of a rate-selectable clksel clock, and a clock
|
|
* divisor, find the corresponding register field value. Returns the
|
|
* register field value _before_ left-shifting (i.e., LSB is at bit
|
|
* 0); or returns 0xFFFFFFFF (~0) upon error.
|
|
*/
|
|
static u32 _divisor_to_clksel(struct clk *clk, u32 div)
|
|
{
|
|
const struct clksel *clks;
|
|
const struct clksel_rate *clkr;
|
|
struct clk *parent;
|
|
|
|
/* should never happen */
|
|
WARN_ON(div == 0);
|
|
|
|
parent = __clk_get_parent(clk);
|
|
clks = _get_clksel_by_parent(clk, parent);
|
|
if (!clks)
|
|
return ~0;
|
|
|
|
for (clkr = clks->rates; clkr->div; clkr++) {
|
|
if (!(clkr->flags & cpu_mask))
|
|
continue;
|
|
|
|
if (clkr->div == div)
|
|
break;
|
|
}
|
|
|
|
if (!clkr->div) {
|
|
pr_err("clock: %s: could not find divisor %d for parent %s\n",
|
|
__clk_get_name(clk), div, __clk_get_name(parent));
|
|
return ~0;
|
|
}
|
|
|
|
return clkr->val;
|
|
}
|
|
|
|
/**
|
|
* _read_divisor() - get current divisor applied to parent clock (from hdwr)
|
|
* @clk: OMAP struct clk to use.
|
|
*
|
|
* Read the current divisor register value for @clk that is programmed
|
|
* into the hardware, convert it into the actual divisor value, and
|
|
* return it; or return 0 on error.
|
|
*/
|
|
static u32 _read_divisor(struct clk *clk)
|
|
{
|
|
u32 v;
|
|
|
|
if (!clk->clksel || !clk->clksel_mask)
|
|
return 0;
|
|
|
|
v = __raw_readl(clk->clksel_reg);
|
|
v &= clk->clksel_mask;
|
|
v >>= __ffs(clk->clksel_mask);
|
|
|
|
return _clksel_to_divisor(clk, v);
|
|
}
|
|
|
|
/* Public functions */
|
|
|
|
/**
|
|
* omap2_clksel_round_rate_div() - find divisor for the given clock and rate
|
|
* @clk: OMAP struct clk to use
|
|
* @target_rate: desired clock rate
|
|
* @new_div: ptr to where we should store the divisor
|
|
*
|
|
* Finds 'best' divider value in an array based on the source and target
|
|
* rates. The divider array must be sorted with smallest divider first.
|
|
* This function is also used by the DPLL3 M2 divider code.
|
|
*
|
|
* Returns the rounded clock rate or returns 0xffffffff on error.
|
|
*/
|
|
u32 omap2_clksel_round_rate_div(struct clk *clk, unsigned long target_rate,
|
|
u32 *new_div)
|
|
{
|
|
unsigned long test_rate;
|
|
const struct clksel *clks;
|
|
const struct clksel_rate *clkr;
|
|
u32 last_div = 0;
|
|
struct clk *parent;
|
|
unsigned long parent_rate;
|
|
const char *clk_name;
|
|
|
|
parent = __clk_get_parent(clk);
|
|
parent_rate = __clk_get_rate(parent);
|
|
clk_name = __clk_get_name(clk);
|
|
|
|
if (!clk->clksel || !clk->clksel_mask)
|
|
return ~0;
|
|
|
|
pr_debug("clock: clksel_round_rate_div: %s target_rate %ld\n",
|
|
clk_name, target_rate);
|
|
|
|
*new_div = 1;
|
|
|
|
clks = _get_clksel_by_parent(clk, parent);
|
|
if (!clks)
|
|
return ~0;
|
|
|
|
for (clkr = clks->rates; clkr->div; clkr++) {
|
|
if (!(clkr->flags & cpu_mask))
|
|
continue;
|
|
|
|
/* Sanity check */
|
|
if (clkr->div <= last_div)
|
|
pr_err("clock: %s: clksel_rate table not sorted\n",
|
|
clk_name);
|
|
|
|
last_div = clkr->div;
|
|
|
|
test_rate = parent_rate / clkr->div;
|
|
|
|
if (test_rate <= target_rate)
|
|
break; /* found it */
|
|
}
|
|
|
|
if (!clkr->div) {
|
|
pr_err("clock: %s: could not find divisor for target rate %ld for parent %s\n",
|
|
clk_name, target_rate, __clk_get_name(parent));
|
|
return ~0;
|
|
}
|
|
|
|
*new_div = clkr->div;
|
|
|
|
pr_debug("clock: new_div = %d, new_rate = %ld\n", *new_div,
|
|
(parent_rate / clkr->div));
|
|
|
|
return parent_rate / clkr->div;
|
|
}
|
|
|
|
/*
|
|
* Clocktype interface functions to the OMAP clock code
|
|
* (i.e., those used in struct clk field function pointers, etc.)
|
|
*/
|
|
|
|
/**
|
|
* omap2_init_clksel_parent() - set a clksel clk's parent field from the hdwr
|
|
* @clk: OMAP clock struct ptr to use
|
|
*
|
|
* Given a pointer @clk to a source-selectable struct clk, read the
|
|
* hardware register and determine what its parent is currently set
|
|
* to. Update @clk's .parent field with the appropriate clk ptr. No
|
|
* return value.
|
|
*/
|
|
void omap2_init_clksel_parent(struct clk *clk)
|
|
{
|
|
const struct clksel *clks;
|
|
const struct clksel_rate *clkr;
|
|
u32 r, found = 0;
|
|
struct clk *parent;
|
|
const char *clk_name;
|
|
|
|
if (!clk->clksel || !clk->clksel_mask)
|
|
return;
|
|
|
|
parent = __clk_get_parent(clk);
|
|
clk_name = __clk_get_name(clk);
|
|
|
|
r = __raw_readl(clk->clksel_reg) & clk->clksel_mask;
|
|
r >>= __ffs(clk->clksel_mask);
|
|
|
|
for (clks = clk->clksel; clks->parent && !found; clks++) {
|
|
for (clkr = clks->rates; clkr->div && !found; clkr++) {
|
|
if (!(clkr->flags & cpu_mask))
|
|
continue;
|
|
|
|
if (clkr->val == r) {
|
|
if (parent != clks->parent) {
|
|
pr_debug("clock: inited %s parent "
|
|
"to %s (was %s)\n",
|
|
clk_name,
|
|
__clk_get_name(clks->parent),
|
|
((parent) ?
|
|
__clk_get_name(parent) :
|
|
"NULL"));
|
|
clk_reparent(clk, clks->parent);
|
|
};
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This indicates a data error */
|
|
WARN(!found, "clock: %s: init parent: could not find regval %0x\n",
|
|
clk_name, r);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* omap2_clksel_recalc() - function ptr to pass via struct clk .recalc field
|
|
* @clk: struct clk *
|
|
*
|
|
* This function is intended to be called only by the clock framework.
|
|
* Each clksel clock should have its struct clk .recalc field set to this
|
|
* function. Returns the clock's current rate, based on its parent's rate
|
|
* and its current divisor setting in the hardware.
|
|
*/
|
|
unsigned long omap2_clksel_recalc(struct clk *clk)
|
|
{
|
|
unsigned long rate;
|
|
u32 div = 0;
|
|
struct clk *parent;
|
|
|
|
div = _read_divisor(clk);
|
|
if (div == 0)
|
|
return __clk_get_rate(clk);
|
|
|
|
parent = __clk_get_parent(clk);
|
|
rate = __clk_get_rate(parent) / div;
|
|
|
|
pr_debug("clock: %s: recalc'd rate is %ld (div %d)\n",
|
|
__clk_get_name(clk), rate, div);
|
|
|
|
return rate;
|
|
}
|
|
|
|
/**
|
|
* omap2_clksel_round_rate() - find rounded rate for the given clock and rate
|
|
* @clk: OMAP struct clk to use
|
|
* @target_rate: desired clock rate
|
|
*
|
|
* This function is intended to be called only by the clock framework.
|
|
* Finds best target rate based on the source clock and possible dividers.
|
|
* rates. The divider array must be sorted with smallest divider first.
|
|
*
|
|
* Returns the rounded clock rate or returns 0xffffffff on error.
|
|
*/
|
|
long omap2_clksel_round_rate(struct clk *clk, unsigned long target_rate)
|
|
{
|
|
u32 new_div;
|
|
|
|
return omap2_clksel_round_rate_div(clk, target_rate, &new_div);
|
|
}
|
|
|
|
/**
|
|
* omap2_clksel_set_rate() - program clock rate in hardware
|
|
* @clk: struct clk * to program rate
|
|
* @rate: target rate to program
|
|
*
|
|
* This function is intended to be called only by the clock framework.
|
|
* Program @clk's rate to @rate in the hardware. The clock can be
|
|
* either enabled or disabled when this happens, although if the clock
|
|
* is enabled, some downstream devices may glitch or behave
|
|
* unpredictably when the clock rate is changed - this depends on the
|
|
* hardware. This function does not currently check the usecount of
|
|
* the clock, so if multiple drivers are using the clock, and the rate
|
|
* is changed, they will all be affected without any notification.
|
|
* Returns -EINVAL upon error, or 0 upon success.
|
|
*/
|
|
int omap2_clksel_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
u32 field_val, validrate, new_div = 0;
|
|
|
|
if (!clk->clksel || !clk->clksel_mask)
|
|
return -EINVAL;
|
|
|
|
validrate = omap2_clksel_round_rate_div(clk, rate, &new_div);
|
|
if (validrate != rate)
|
|
return -EINVAL;
|
|
|
|
field_val = _divisor_to_clksel(clk, new_div);
|
|
if (field_val == ~0)
|
|
return -EINVAL;
|
|
|
|
_write_clksel_reg(clk, field_val);
|
|
|
|
clk->rate = __clk_get_rate(__clk_get_parent(clk)) / new_div;
|
|
|
|
pr_debug("clock: %s: set rate to %ld\n", __clk_get_name(clk),
|
|
__clk_get_rate(clk));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clksel parent setting function - not passed in struct clk function
|
|
* pointer - instead, the OMAP clock code currently assumes that any
|
|
* parent-setting clock is a clksel clock, and calls
|
|
* omap2_clksel_set_parent() by default
|
|
*/
|
|
|
|
/**
|
|
* omap2_clksel_set_parent() - change a clock's parent clock
|
|
* @clk: struct clk * of the child clock
|
|
* @new_parent: struct clk * of the new parent clock
|
|
*
|
|
* This function is intended to be called only by the clock framework.
|
|
* Change the parent clock of clock @clk to @new_parent. This is
|
|
* intended to be used while @clk is disabled. This function does not
|
|
* currently check the usecount of the clock, so if multiple drivers
|
|
* are using the clock, and the parent is changed, they will all be
|
|
* affected without any notification. Returns -EINVAL upon error, or
|
|
* 0 upon success.
|
|
*/
|
|
int omap2_clksel_set_parent(struct clk *clk, struct clk *new_parent)
|
|
{
|
|
u32 field_val = 0;
|
|
u32 parent_div;
|
|
|
|
if (!clk->clksel || !clk->clksel_mask)
|
|
return -EINVAL;
|
|
|
|
parent_div = _get_div_and_fieldval(new_parent, clk, &field_val);
|
|
if (!parent_div)
|
|
return -EINVAL;
|
|
|
|
_write_clksel_reg(clk, field_val);
|
|
|
|
clk_reparent(clk, new_parent);
|
|
|
|
/* CLKSEL clocks follow their parents' rates, divided by a divisor */
|
|
clk->rate = __clk_get_rate(new_parent);
|
|
|
|
if (parent_div > 0)
|
|
__clk_get_rate(clk) /= parent_div;
|
|
|
|
pr_debug("clock: %s: set parent to %s (new rate %ld)\n",
|
|
__clk_get_name(clk),
|
|
__clk_get_name(__clk_get_parent(clk)),
|
|
__clk_get_rate(clk));
|
|
|
|
return 0;
|
|
}
|