[PATCH 11/74] ST SPEAr: Adding support for divisor per parent clock
Viresh KUMAR
viresh.kumar at st.com
Mon Aug 30 06:38:42 EDT 2010
This patch was intended to provide support for clocks with multiple parents,
where clock derived from any parent can be divided by a fixed number or can be
passed to synthesizer. For this clock framework is reorganized to make behaviour
of functions more closer to their name.
Signed-off-by: Viresh Kumar <viresh.kumar at st.com>
---
arch/arm/plat-spear/clock.c | 249 +++++++++++++++++++-----------
arch/arm/plat-spear/include/plat/clock.h | 6 +-
2 files changed, 162 insertions(+), 93 deletions(-)
diff --git a/arch/arm/plat-spear/clock.c b/arch/arm/plat-spear/clock.c
index f1cf832..ab29353 100644
--- a/arch/arm/plat-spear/clock.c
+++ b/arch/arm/plat-spear/clock.c
@@ -64,6 +64,40 @@ static struct clkops generic_clkops = {
.disable = generic_clk_disable,
};
+/* returns current programmed clocks clock info structure */
+static struct pclk_info *pclk_info_get(struct clk *clk)
+{
+ unsigned int mask, i;
+ struct pclk_info *info = NULL;
+
+ mask = (readl(clk->pclk_sel->pclk_sel_reg) >> clk->pclk_sel_shift)
+ & clk->pclk_sel->pclk_sel_mask;
+
+ for (i = 0; i < clk->pclk_sel->pclk_count; i++) {
+ if (clk->pclk_sel->pclk_info[i].pclk_mask == mask)
+ info = &clk->pclk_sel->pclk_info[i];
+ }
+
+ return info;
+}
+
+/*
+ * Set Update pclk, and pclk_info of clk and add clock sibling node to current
+ * parents children list
+ */
+static void update_clk_tree(struct clk *clk, struct pclk_info *pclk_info)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+ list_del(&clk->sibling);
+ list_add(&clk->sibling, &pclk_info->pclk->children);
+
+ clk->pclk = pclk_info->pclk;
+ clk->pclk_info = pclk_info;
+ spin_unlock_irqrestore(&clocks_lock, flags);
+}
+
/*
* clk_enable - inform the system when the clock source should be running.
* @clk: clock source
@@ -161,6 +195,7 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
if (clk->pclk == parent)
return 0;
+ /* check if requested parent is in clk parent list */
for (i = 0; i < clk->pclk_sel->pclk_count; i++) {
if (clk->pclk_sel->pclk_info[i].pclk == parent) {
found = 1;
@@ -180,8 +215,11 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
spin_unlock_irqrestore(&clocks_lock, flags);
/* reflect parent change in software */
+ update_clk_tree(clk, &clk->pclk_sel->pclk_info[i]);
+
clk->recalc(clk);
propagate_rate(&clk->children);
+
return 0;
}
EXPORT_SYMBOL(clk_set_parent);
@@ -220,14 +258,23 @@ void clk_register(struct clk_lookup *cl)
/* root clock don't have any parents */
if (!clk->pclk && !clk->pclk_sel) {
list_add(&clk->sibling, &root_clks);
- /* add clocks with only one parent to parent's children list */
} else if (clk->pclk && !clk->pclk_sel) {
+ /* add clocks with only one parent to parent's children list */
list_add(&clk->sibling, &clk->pclk->children);
} else {
- /* add clocks with > 1 parent to 1st parent's children list */
- clk->pclk = clk->pclk_sel->pclk_info[0].pclk;
- list_add(&clk->sibling,
- &clk->pclk_sel->pclk_info[0].pclk->children);
+ /* clocks with more than one parent */
+ struct pclk_info *pclk_info;
+
+ pclk_info = pclk_info_get(clk);
+ if (!pclk_info) {
+ printk(KERN_ERR "CLKDEV: invalid pclk info of clk with"
+ " %s dev_id and %s con_id\n",
+ cl->dev_id, cl->con_id);
+ } else {
+ clk->pclk = pclk_info->pclk;
+ clk->pclk_info = pclk_info;
+ list_add(&clk->sibling, &pclk_info->pclk->children);
+ }
}
spin_unlock_irqrestore(&clocks_lock, flags);
@@ -252,42 +299,6 @@ static void propagate_rate(struct list_head *lhead)
}
}
-/* returns current programmed clocks clock info structure */
-static struct pclk_info *pclk_info_get(struct clk *clk)
-{
- unsigned int mask, i;
- unsigned long flags;
- struct pclk_info *info = NULL;
-
- spin_lock_irqsave(&clocks_lock, flags);
- mask = (readl(clk->pclk_sel->pclk_sel_reg) >> clk->pclk_sel_shift)
- & clk->pclk_sel->pclk_sel_mask;
-
- for (i = 0; i < clk->pclk_sel->pclk_count; i++) {
- if (clk->pclk_sel->pclk_info[i].pclk_mask == mask)
- info = &clk->pclk_sel->pclk_info[i];
- }
- spin_unlock_irqrestore(&clocks_lock, flags);
-
- return info;
-}
-
-/*
- * Set pclk as cclk's parent and add clock sibling node to current parents
- * children list
- */
-static void change_parent(struct clk *cclk, struct clk *pclk)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&clocks_lock, flags);
- list_del(&cclk->sibling);
- list_add(&cclk->sibling, &pclk->children);
-
- cclk->pclk = pclk;
- spin_unlock_irqrestore(&clocks_lock, flags);
-}
-
/*
* calculates current programmed rate of pll1
*
@@ -304,28 +315,53 @@ void pll_clk_recalc(struct clk *clk)
unsigned long flags;
spin_lock_irqsave(&clocks_lock, flags);
- mode = (readl(config->mode_reg) >> config->masks->mode_shift) &
- config->masks->mode_mask;
-
- val = readl(config->cfg_reg);
- /* calculate denominator */
- den = (val >> config->masks->div_p_shift) & config->masks->div_p_mask;
- den = 1 << den;
- den *= (val >> config->masks->div_n_shift) & config->masks->div_n_mask;
-
- /* calculate numerator & denominator */
- if (!mode) {
- /* Normal mode */
- num *= (val >> config->masks->norm_fdbk_m_shift) &
- config->masks->norm_fdbk_m_mask;
+
+ /*
+ * read divisor from hardware, only in two cases:
+ * - There is only parent to clk and it requires *_clk_recalc
+ * - There are two parents of a clock and current pclk requires
+ * *_clk_recalc
+ */
+ if (!clk->pclk_info || clk->pclk_info->scalable) {
+ mode = (readl(config->mode_reg) >> config->masks->mode_shift) &
+ config->masks->mode_mask;
+
+ val = readl(config->cfg_reg);
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ /* calculate denominator */
+ den = (val >> config->masks->div_p_shift) &
+ config->masks->div_p_mask;
+ den = 1 << den;
+ den *= (val >> config->masks->div_n_shift) &
+ config->masks->div_n_mask;
+
+ /* calculate numerator & denominator */
+ if (!mode) {
+ /* Normal mode */
+ num *= (val >> config->masks->norm_fdbk_m_shift) &
+ config->masks->norm_fdbk_m_mask;
+ } else {
+ /* Dithered mode */
+ num *= (val >> config->masks->dith_fdbk_m_shift) &
+ config->masks->dith_fdbk_m_mask;
+ den *= 256;
+ }
+
+ spin_lock_irqsave(&clocks_lock, flags);
+ val = (((clk->pclk->rate/10000) * num) / den) * 10000;
} else {
- /* Dithered mode */
- num *= (val >> config->masks->dith_fdbk_m_shift) &
- config->masks->dith_fdbk_m_mask;
- den *= 256;
+ int div = 0;
+ /*
+ * only if there are two parents and current parent requires
+ * simple division
+ */
+ div = (clk->pclk_info->div_factor < 1) ? 1 :
+ clk->pclk_info->div_factor;
+ val = clk->pclk->rate/div;
}
- clk->rate = (((clk->pclk->rate/10000) * num) / den) * 10000;
+ clk->rate = val;
spin_unlock_irqrestore(&clocks_lock, flags);
}
@@ -337,8 +373,23 @@ void bus_clk_recalc(struct clk *clk)
unsigned long flags;
spin_lock_irqsave(&clocks_lock, flags);
- div = ((readl(config->reg) >> config->masks->shift) &
- config->masks->mask) + 1;
+ /*
+ * read divisor from hardware, only in two cases:
+ * - There is only parent to clk and it requires *_clk_recalc
+ * - There are two parents of a clock and current pclk requires
+ * *_clk_recalc
+ */
+ if (!clk->pclk_info || clk->pclk_info->scalable) {
+ div = ((readl(config->reg) >> config->masks->shift) &
+ config->masks->mask) + 1;
+ } else {
+ /*
+ * only if there are two parents and current parent requires
+ * simple division
+ */
+ div = (clk->pclk_info->div_factor < 1) ? 1 :
+ clk->pclk_info->div_factor;
+ }
clk->rate = (unsigned long)clk->pclk->rate / div;
spin_unlock_irqrestore(&clocks_lock, flags);
}
@@ -356,25 +407,19 @@ void bus_clk_recalc(struct clk *clk)
void aux_clk_recalc(struct clk *clk)
{
struct aux_clk_config *config = clk->private_data;
- struct pclk_info *pclk_info = NULL;
unsigned int num = 1, den = 1, val, eqn;
unsigned long flags;
- /* get current programmed parent */
- pclk_info = pclk_info_get(clk);
- if (!pclk_info) {
- spin_lock_irqsave(&clocks_lock, flags);
- clk->pclk = NULL;
- clk->rate = 0;
- spin_unlock_irqrestore(&clocks_lock, flags);
- return;
- }
-
- change_parent(clk, pclk_info->pclk);
-
spin_lock_irqsave(&clocks_lock, flags);
- if (pclk_info->scalable) {
+ /*
+ * read divisor from hardware, only in two cases:
+ * - There is only parent to clk and it requires *_clk_recalc
+ * - There are two parents of a clock and current pclk requires
+ * *_clk_recalc
+ */
+ if (!clk->pclk_info || clk->pclk_info->scalable) {
val = readl(config->synth_reg);
+ spin_unlock_irqrestore(&clocks_lock, flags);
eqn = (val >> config->masks->eq_sel_shift) &
config->masks->eq_sel_mask;
@@ -388,9 +433,19 @@ void aux_clk_recalc(struct clk *clk)
/* calculate denominator */
den *= (val >> config->masks->yscale_sel_shift) &
config->masks->yscale_sel_mask;
+
+ spin_lock_irqsave(&clocks_lock, flags);
val = (((clk->pclk->rate/10000) * num) / den) * 10000;
- } else
- val = clk->pclk->rate;
+ } else {
+ /*
+ * only if there are two parents and current parent requires
+ * simple division
+ */
+ int div_factor = (clk->pclk_info->div_factor < 1) ? 1 :
+ clk->pclk_info->div_factor;
+
+ val = clk->pclk->rate/div_factor;
+ }
clk->rate = val;
spin_unlock_irqrestore(&clocks_lock, flags);
@@ -404,28 +459,32 @@ void aux_clk_recalc(struct clk *clk)
void gpt_clk_recalc(struct clk *clk)
{
struct gpt_clk_config *config = clk->private_data;
- struct pclk_info *pclk_info = NULL;
unsigned int div = 1, val;
unsigned long flags;
- pclk_info = pclk_info_get(clk);
- if (!pclk_info) {
- spin_lock_irqsave(&clocks_lock, flags);
- clk->pclk = NULL;
- clk->rate = 0;
- spin_unlock_irqrestore(&clocks_lock, flags);
- return;
- }
-
- change_parent(clk, pclk_info->pclk);
-
spin_lock_irqsave(&clocks_lock, flags);
- if (pclk_info->scalable) {
+ /*
+ * read divisor from hardware, only in two cases:
+ * - There is only parent to clk and it requires *_clk_recalc
+ * - There are two parents of a clock and current pclk requires
+ * *_clk_recalc
+ */
+ if (!clk->pclk_info || clk->pclk_info->scalable) {
val = readl(config->synth_reg);
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
div += (val >> config->masks->mscale_sel_shift) &
config->masks->mscale_sel_mask;
div *= 1 << (((val >> config->masks->nscale_sel_shift) &
config->masks->nscale_sel_mask) + 1);
+ spin_lock_irqsave(&clocks_lock, flags);
+ } else {
+ /*
+ * only if there are two parents and current parent requires
+ * simple division
+ */
+ div = (clk->pclk_info->div_factor < 1) ? 1 :
+ clk->pclk_info->div_factor;
}
clk->rate = (unsigned long)clk->pclk->rate / div;
@@ -439,9 +498,15 @@ void gpt_clk_recalc(struct clk *clk)
void follow_parent(struct clk *clk)
{
unsigned long flags;
- unsigned int div_factor = (clk->div_factor < 1) ? 1 : clk->div_factor;
+ unsigned int div_factor;
spin_lock_irqsave(&clocks_lock, flags);
+ if (clk->pclk_info)
+ div_factor = clk->pclk_info->div_factor;
+ else
+ div_factor = clk->div_factor;
+ div_factor = (div_factor < 1) ? 1 : div_factor;
+
clk->rate = clk->pclk->rate/div_factor;
spin_unlock_irqrestore(&clocks_lock, flags);
}
diff --git a/arch/arm/plat-spear/include/plat/clock.h b/arch/arm/plat-spear/include/plat/clock.h
index e08b58c..d8d0856 100644
--- a/arch/arm/plat-spear/include/plat/clock.h
+++ b/arch/arm/plat-spear/include/plat/clock.h
@@ -37,11 +37,13 @@ struct clkops {
* @pclk: pointer to parent clk
* @pclk_mask: value to be written for selecting this parent
* @scalable: Is parent scalable (1 - YES, 0 - NO)
+ * @div_factor: div factor for pclk
*/
struct pclk_info {
struct clk *pclk;
u8 pclk_mask;
u8 scalable;
+ u8 div_factor;
};
/**
@@ -67,8 +69,9 @@ struct pclk_sel {
* @en_reg_bit: clk enable/disable bit
* @ops: clk enable/disable ops - generic_clkops selected if NULL
* @recalc: pointer to clock rate recalculate function
- * @div_factor: division factor to parent clock. Only for recalc = follow_parent
+ * @div_factor: division factor to parent clock. Only for clks with one parent
* @pclk: current parent clk
+ * @pclk_info: current parent clk's pclk_info
* @pclk_sel: pointer to parent selection structure
* @pclk_sel_shift: register shift for selecting parent of this clock
* @children: list for childrens or this clock
@@ -86,6 +89,7 @@ struct clk {
unsigned int div_factor;
struct clk *pclk;
+ struct pclk_info *pclk_info;
struct pclk_sel *pclk_sel;
unsigned int pclk_sel_shift;
--
1.7.2.2
More information about the linux-arm-kernel
mailing list