xref: /openbmc/linux/drivers/clk/sunxi/clk-factors.c (revision 862b728387aef3a3776ad2a261e484aff36c5e67)
1e874a669SEmilio López /*
2e874a669SEmilio López  * Copyright (C) 2013 Emilio López <emilio@elopez.com.ar>
3e874a669SEmilio López  *
4e874a669SEmilio López  * This program is free software; you can redistribute it and/or modify
5e874a669SEmilio López  * it under the terms of the GNU General Public License version 2 as
6e874a669SEmilio López  * published by the Free Software Foundation.
7e874a669SEmilio López  *
8e874a669SEmilio López  * Adjustable factor-based clock implementation
9e874a669SEmilio López  */
10e874a669SEmilio López 
11e874a669SEmilio López #include <linux/clk-provider.h>
12e874a669SEmilio López #include <linux/module.h>
13e874a669SEmilio López #include <linux/slab.h>
14e874a669SEmilio López #include <linux/io.h>
15e874a669SEmilio López #include <linux/err.h>
16e874a669SEmilio López #include <linux/string.h>
17e874a669SEmilio López 
18e874a669SEmilio López #include <linux/delay.h>
19e874a669SEmilio López 
20e874a669SEmilio López #include "clk-factors.h"
21e874a669SEmilio López 
22e874a669SEmilio López /*
23e874a669SEmilio López  * DOC: basic adjustable factor-based clock that cannot gate
24e874a669SEmilio López  *
25e874a669SEmilio López  * Traits of this clock:
26e874a669SEmilio López  * prepare - clk_prepare only ensures that parents are prepared
27e874a669SEmilio López  * enable - clk_enable only ensures that parents are enabled
28e874a669SEmilio López  * rate - rate is adjustable.
29e874a669SEmilio López  *        clk->rate = (parent->rate * N * (K + 1) >> P) / (M + 1)
30e874a669SEmilio López  * parent - fixed parent.  No clk_set_parent support
31e874a669SEmilio López  */
32e874a669SEmilio López 
33e874a669SEmilio López #define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw)
34e874a669SEmilio López 
35c518e84cSEmilio López #define SETMASK(len, pos)		(((1U << (len)) - 1) << (pos))
36e874a669SEmilio López #define CLRMASK(len, pos)		(~(SETMASK(len, pos)))
37e874a669SEmilio López #define FACTOR_GET(bit, len, reg)	(((reg) & SETMASK(len, bit)) >> (bit))
38e874a669SEmilio López 
39e874a669SEmilio López #define FACTOR_SET(bit, len, reg, val) \
40e874a669SEmilio López 	(((reg) & CLRMASK(len, bit)) | (val << (bit)))
41e874a669SEmilio López 
42e874a669SEmilio López static unsigned long clk_factors_recalc_rate(struct clk_hw *hw,
43e874a669SEmilio López 					     unsigned long parent_rate)
44e874a669SEmilio López {
45e874a669SEmilio López 	u8 n = 1, k = 0, p = 0, m = 0;
46e874a669SEmilio López 	u32 reg;
47e874a669SEmilio López 	unsigned long rate;
48e874a669SEmilio López 	struct clk_factors *factors = to_clk_factors(hw);
49e874a669SEmilio López 	struct clk_factors_config *config = factors->config;
50e874a669SEmilio López 
51e874a669SEmilio López 	/* Fetch the register value */
52e874a669SEmilio López 	reg = readl(factors->reg);
53e874a669SEmilio López 
54e874a669SEmilio López 	/* Get each individual factor if applicable */
55e874a669SEmilio López 	if (config->nwidth != SUNXI_FACTORS_NOT_APPLICABLE)
56e874a669SEmilio López 		n = FACTOR_GET(config->nshift, config->nwidth, reg);
57e874a669SEmilio López 	if (config->kwidth != SUNXI_FACTORS_NOT_APPLICABLE)
58e874a669SEmilio López 		k = FACTOR_GET(config->kshift, config->kwidth, reg);
59e874a669SEmilio López 	if (config->mwidth != SUNXI_FACTORS_NOT_APPLICABLE)
60e874a669SEmilio López 		m = FACTOR_GET(config->mshift, config->mwidth, reg);
61e874a669SEmilio López 	if (config->pwidth != SUNXI_FACTORS_NOT_APPLICABLE)
62e874a669SEmilio López 		p = FACTOR_GET(config->pshift, config->pwidth, reg);
63e874a669SEmilio López 
64e874a669SEmilio López 	/* Calculate the rate */
65e874a669SEmilio López 	rate = (parent_rate * n * (k + 1) >> p) / (m + 1);
66e874a669SEmilio López 
67e874a669SEmilio López 	return rate;
68e874a669SEmilio López }
69e874a669SEmilio López 
70e874a669SEmilio López static long clk_factors_round_rate(struct clk_hw *hw, unsigned long rate,
71e874a669SEmilio López 				   unsigned long *parent_rate)
72e874a669SEmilio López {
73e874a669SEmilio López 	struct clk_factors *factors = to_clk_factors(hw);
74e874a669SEmilio López 	factors->get_factors((u32 *)&rate, (u32)*parent_rate,
75e874a669SEmilio López 			     NULL, NULL, NULL, NULL);
76e874a669SEmilio López 
77e874a669SEmilio López 	return rate;
78e874a669SEmilio López }
79e874a669SEmilio López 
80*862b7283SEmilio López static long clk_factors_determine_rate(struct clk_hw *hw, unsigned long rate,
81*862b7283SEmilio López 				       unsigned long *best_parent_rate,
82*862b7283SEmilio López 				       struct clk **best_parent_p)
83*862b7283SEmilio López {
84*862b7283SEmilio López 	struct clk *clk = hw->clk, *parent, *best_parent = NULL;
85*862b7283SEmilio López 	int i, num_parents;
86*862b7283SEmilio López 	unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
87*862b7283SEmilio López 
88*862b7283SEmilio López 	/* find the parent that can help provide the fastest rate <= rate */
89*862b7283SEmilio López 	num_parents = __clk_get_num_parents(clk);
90*862b7283SEmilio López 	for (i = 0; i < num_parents; i++) {
91*862b7283SEmilio López 		parent = clk_get_parent_by_index(clk, i);
92*862b7283SEmilio López 		if (!parent)
93*862b7283SEmilio López 			continue;
94*862b7283SEmilio López 		if (__clk_get_flags(clk) & CLK_SET_RATE_PARENT)
95*862b7283SEmilio López 			parent_rate = __clk_round_rate(parent, rate);
96*862b7283SEmilio López 		else
97*862b7283SEmilio López 			parent_rate = __clk_get_rate(parent);
98*862b7283SEmilio López 
99*862b7283SEmilio López 		child_rate = clk_factors_round_rate(hw, rate, &parent_rate);
100*862b7283SEmilio López 
101*862b7283SEmilio López 		if (child_rate <= rate && child_rate > best_child_rate) {
102*862b7283SEmilio López 			best_parent = parent;
103*862b7283SEmilio López 			best = parent_rate;
104*862b7283SEmilio López 			best_child_rate = child_rate;
105*862b7283SEmilio López 		}
106*862b7283SEmilio López 	}
107*862b7283SEmilio López 
108*862b7283SEmilio López 	if (best_parent)
109*862b7283SEmilio López 		*best_parent_p = best_parent;
110*862b7283SEmilio López 	*best_parent_rate = best;
111*862b7283SEmilio López 
112*862b7283SEmilio López 	return best_child_rate;
113*862b7283SEmilio López }
114*862b7283SEmilio López 
115e874a669SEmilio López static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate,
116e874a669SEmilio López 				unsigned long parent_rate)
117e874a669SEmilio López {
11812ef06afSEmilio López 	u8 n = 0, k = 0, m = 0, p = 0;
119e874a669SEmilio López 	u32 reg;
120e874a669SEmilio López 	struct clk_factors *factors = to_clk_factors(hw);
121e874a669SEmilio López 	struct clk_factors_config *config = factors->config;
122e874a669SEmilio López 	unsigned long flags = 0;
123e874a669SEmilio López 
124e874a669SEmilio López 	factors->get_factors((u32 *)&rate, (u32)parent_rate, &n, &k, &m, &p);
125e874a669SEmilio López 
126e874a669SEmilio López 	if (factors->lock)
127e874a669SEmilio López 		spin_lock_irqsave(factors->lock, flags);
128e874a669SEmilio López 
129e874a669SEmilio López 	/* Fetch the register value */
130e874a669SEmilio López 	reg = readl(factors->reg);
131e874a669SEmilio López 
132e874a669SEmilio López 	/* Set up the new factors - macros do not do anything if width is 0 */
133e874a669SEmilio López 	reg = FACTOR_SET(config->nshift, config->nwidth, reg, n);
134e874a669SEmilio López 	reg = FACTOR_SET(config->kshift, config->kwidth, reg, k);
135e874a669SEmilio López 	reg = FACTOR_SET(config->mshift, config->mwidth, reg, m);
136e874a669SEmilio López 	reg = FACTOR_SET(config->pshift, config->pwidth, reg, p);
137e874a669SEmilio López 
138e874a669SEmilio López 	/* Apply them now */
139e874a669SEmilio López 	writel(reg, factors->reg);
140e874a669SEmilio López 
141e874a669SEmilio López 	/* delay 500us so pll stabilizes */
142e874a669SEmilio López 	__delay((rate >> 20) * 500 / 2);
143e874a669SEmilio López 
144e874a669SEmilio López 	if (factors->lock)
145e874a669SEmilio López 		spin_unlock_irqrestore(factors->lock, flags);
146e874a669SEmilio López 
147e874a669SEmilio López 	return 0;
148e874a669SEmilio López }
149e874a669SEmilio López 
15040a5dcbaSEmilio López const struct clk_ops clk_factors_ops = {
151*862b7283SEmilio López 	.determine_rate = clk_factors_determine_rate,
152e874a669SEmilio López 	.recalc_rate = clk_factors_recalc_rate,
153e874a669SEmilio López 	.round_rate = clk_factors_round_rate,
154e874a669SEmilio López 	.set_rate = clk_factors_set_rate,
155e874a669SEmilio López };
156