xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_mult.c (revision 5a1ea477)
1 /*
2  * Copyright (C) 2016 Maxime Ripard
3  * Maxime Ripard <maxime.ripard@free-electrons.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) any later version.
9  */
10 
11 #include <linux/clk-provider.h>
12 #include <linux/io.h>
13 
14 #include "ccu_gate.h"
15 #include "ccu_mult.h"
16 
17 struct _ccu_mult {
18 	unsigned long	mult, min, max;
19 };
20 
21 static void ccu_mult_find_best(unsigned long parent, unsigned long rate,
22 			       struct _ccu_mult *mult)
23 {
24 	int _mult;
25 
26 	_mult = rate / parent;
27 	if (_mult < mult->min)
28 		_mult = mult->min;
29 
30 	if (_mult > mult->max)
31 		_mult = mult->max;
32 
33 	mult->mult = _mult;
34 }
35 
36 static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
37 					 struct clk_hw *parent,
38 					 unsigned long *parent_rate,
39 					 unsigned long rate,
40 					 void *data)
41 {
42 	struct ccu_mult *cm = data;
43 	struct _ccu_mult _cm;
44 
45 	_cm.min = cm->mult.min;
46 
47 	if (cm->mult.max)
48 		_cm.max = cm->mult.max;
49 	else
50 		_cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
51 
52 	ccu_mult_find_best(*parent_rate, rate, &_cm);
53 
54 	return *parent_rate * _cm.mult;
55 }
56 
57 static void ccu_mult_disable(struct clk_hw *hw)
58 {
59 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
60 
61 	return ccu_gate_helper_disable(&cm->common, cm->enable);
62 }
63 
64 static int ccu_mult_enable(struct clk_hw *hw)
65 {
66 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
67 
68 	return ccu_gate_helper_enable(&cm->common, cm->enable);
69 }
70 
71 static int ccu_mult_is_enabled(struct clk_hw *hw)
72 {
73 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
74 
75 	return ccu_gate_helper_is_enabled(&cm->common, cm->enable);
76 }
77 
78 static unsigned long ccu_mult_recalc_rate(struct clk_hw *hw,
79 					unsigned long parent_rate)
80 {
81 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
82 	unsigned long val;
83 	u32 reg;
84 
85 	if (ccu_frac_helper_is_enabled(&cm->common, &cm->frac))
86 		return ccu_frac_helper_read_rate(&cm->common, &cm->frac);
87 
88 	reg = readl(cm->common.base + cm->common.reg);
89 	val = reg >> cm->mult.shift;
90 	val &= (1 << cm->mult.width) - 1;
91 
92 	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
93 						  parent_rate);
94 
95 	return parent_rate * (val + cm->mult.offset);
96 }
97 
98 static int ccu_mult_determine_rate(struct clk_hw *hw,
99 				struct clk_rate_request *req)
100 {
101 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
102 
103 	return ccu_mux_helper_determine_rate(&cm->common, &cm->mux,
104 					     req, ccu_mult_round_rate, cm);
105 }
106 
107 static int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate,
108 			   unsigned long parent_rate)
109 {
110 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
111 	struct _ccu_mult _cm;
112 	unsigned long flags;
113 	u32 reg;
114 
115 	if (ccu_frac_helper_has_rate(&cm->common, &cm->frac, rate)) {
116 		ccu_frac_helper_enable(&cm->common, &cm->frac);
117 
118 		return ccu_frac_helper_set_rate(&cm->common, &cm->frac,
119 						rate, cm->lock);
120 	} else {
121 		ccu_frac_helper_disable(&cm->common, &cm->frac);
122 	}
123 
124 	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
125 						  parent_rate);
126 
127 	_cm.min = cm->mult.min;
128 
129 	if (cm->mult.max)
130 		_cm.max = cm->mult.max;
131 	else
132 		_cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
133 
134 	ccu_mult_find_best(parent_rate, rate, &_cm);
135 
136 	spin_lock_irqsave(cm->common.lock, flags);
137 
138 	reg = readl(cm->common.base + cm->common.reg);
139 	reg &= ~GENMASK(cm->mult.width + cm->mult.shift - 1, cm->mult.shift);
140 	reg |= ((_cm.mult - cm->mult.offset) << cm->mult.shift);
141 
142 	writel(reg, cm->common.base + cm->common.reg);
143 
144 	spin_unlock_irqrestore(cm->common.lock, flags);
145 
146 	ccu_helper_wait_for_lock(&cm->common, cm->lock);
147 
148 	return 0;
149 }
150 
151 static u8 ccu_mult_get_parent(struct clk_hw *hw)
152 {
153 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
154 
155 	return ccu_mux_helper_get_parent(&cm->common, &cm->mux);
156 }
157 
158 static int ccu_mult_set_parent(struct clk_hw *hw, u8 index)
159 {
160 	struct ccu_mult *cm = hw_to_ccu_mult(hw);
161 
162 	return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index);
163 }
164 
165 const struct clk_ops ccu_mult_ops = {
166 	.disable	= ccu_mult_disable,
167 	.enable		= ccu_mult_enable,
168 	.is_enabled	= ccu_mult_is_enabled,
169 
170 	.get_parent	= ccu_mult_get_parent,
171 	.set_parent	= ccu_mult_set_parent,
172 
173 	.determine_rate	= ccu_mult_determine_rate,
174 	.recalc_rate	= ccu_mult_recalc_rate,
175 	.set_rate	= ccu_mult_set_rate,
176 };
177