xref: /linux/drivers/clk/sunxi-ng/ccu_div.c (revision 502d45774af09f1c681c754c4b7cdfb5d7f72fd9)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2016 Maxime Ripard
4  * Maxime Ripard <maxime.ripard@free-electrons.com>
5  */
6 
7 #include <linux/clk-provider.h>
8 #include <linux/io.h>
9 
10 #include "ccu_gate.h"
11 #include "ccu_div.h"
12 
ccu_div_determine_rate_helper(struct ccu_mux_internal * mux,struct clk_rate_request * req,void * data)13 static int ccu_div_determine_rate_helper(struct ccu_mux_internal *mux,
14 					 struct clk_rate_request *req,
15 					 void *data)
16 {
17 	struct ccu_div *cd = data;
18 	int ret;
19 
20 	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
21 		req->rate *= cd->fixed_post_div;
22 
23 	if (cd->div.flags & CLK_DIVIDER_READ_ONLY) {
24 		unsigned long val;
25 		u32 reg;
26 
27 		reg = readl(cd->common.base + cd->common.reg);
28 		val = reg >> cd->div.shift;
29 		val &= (1 << cd->div.width) - 1;
30 
31 		ret = divider_ro_determine_rate(&cd->common.hw, req, cd->div.table,
32 						cd->div.width, cd->div.flags, val);
33 
34 	} else {
35 		ret = divider_determine_rate(&cd->common.hw, req, cd->div.table,
36 					     cd->div.width, cd->div.flags);
37 	}
38 
39 	if (ret)
40 		return ret;
41 
42 	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
43 		req->rate /= cd->fixed_post_div;
44 
45 	return 0;
46 }
47 
ccu_div_disable(struct clk_hw * hw)48 static void ccu_div_disable(struct clk_hw *hw)
49 {
50 	struct ccu_div *cd = hw_to_ccu_div(hw);
51 
52 	return ccu_gate_helper_disable(&cd->common, cd->enable);
53 }
54 
ccu_div_enable(struct clk_hw * hw)55 static int ccu_div_enable(struct clk_hw *hw)
56 {
57 	struct ccu_div *cd = hw_to_ccu_div(hw);
58 
59 	return ccu_gate_helper_enable(&cd->common, cd->enable);
60 }
61 
ccu_div_is_enabled(struct clk_hw * hw)62 static int ccu_div_is_enabled(struct clk_hw *hw)
63 {
64 	struct ccu_div *cd = hw_to_ccu_div(hw);
65 
66 	return ccu_gate_helper_is_enabled(&cd->common, cd->enable);
67 }
68 
ccu_div_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)69 static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
70 					unsigned long parent_rate)
71 {
72 	struct ccu_div *cd = hw_to_ccu_div(hw);
73 	unsigned long val;
74 	u32 reg;
75 
76 	reg = readl(cd->common.base + cd->common.reg);
77 	val = reg >> cd->div.shift;
78 	val &= (1 << cd->div.width) - 1;
79 
80 	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
81 						  parent_rate);
82 
83 	val = divider_recalc_rate(hw, parent_rate, val, cd->div.table,
84 				  cd->div.flags, cd->div.width);
85 
86 	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
87 		val /= cd->fixed_post_div;
88 
89 	return val;
90 }
91 
ccu_div_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)92 static int ccu_div_determine_rate(struct clk_hw *hw,
93 				struct clk_rate_request *req)
94 {
95 	struct ccu_div *cd = hw_to_ccu_div(hw);
96 
97 	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
98 					     req, ccu_div_determine_rate_helper, cd);
99 }
100 
ccu_div_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)101 static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
102 			   unsigned long parent_rate)
103 {
104 	struct ccu_div *cd = hw_to_ccu_div(hw);
105 	unsigned long flags;
106 	unsigned long val;
107 	u32 reg;
108 
109 	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
110 						  parent_rate);
111 
112 	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
113 		rate *= cd->fixed_post_div;
114 
115 	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
116 			      cd->div.flags);
117 
118 	spin_lock_irqsave(cd->common.lock, flags);
119 
120 	reg = readl(cd->common.base + cd->common.reg);
121 	reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift);
122 	if (cd->common.features & CCU_FEATURE_UPDATE_BIT)
123 		reg |= CCU_SUNXI_UPDATE_BIT;
124 
125 	writel(reg | (val << cd->div.shift),
126 	       cd->common.base + cd->common.reg);
127 
128 	spin_unlock_irqrestore(cd->common.lock, flags);
129 
130 	return 0;
131 }
132 
ccu_div_get_parent(struct clk_hw * hw)133 static u8 ccu_div_get_parent(struct clk_hw *hw)
134 {
135 	struct ccu_div *cd = hw_to_ccu_div(hw);
136 
137 	return ccu_mux_helper_get_parent(&cd->common, &cd->mux);
138 }
139 
ccu_div_set_parent(struct clk_hw * hw,u8 index)140 static int ccu_div_set_parent(struct clk_hw *hw, u8 index)
141 {
142 	struct ccu_div *cd = hw_to_ccu_div(hw);
143 
144 	return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index);
145 }
146 
147 const struct clk_ops ccu_div_ops = {
148 	.disable	= ccu_div_disable,
149 	.enable		= ccu_div_enable,
150 	.is_enabled	= ccu_div_is_enabled,
151 
152 	.get_parent	= ccu_div_get_parent,
153 	.set_parent	= ccu_div_set_parent,
154 
155 	.determine_rate	= ccu_div_determine_rate,
156 	.recalc_rate	= ccu_div_recalc_rate,
157 	.set_rate	= ccu_div_set_rate,
158 };
159 EXPORT_SYMBOL_NS_GPL(ccu_div_ops, "SUNXI_CCU");
160 
161 const struct clk_ops ccu_rodiv_ops = {
162 	.disable	= ccu_div_disable,
163 	.enable		= ccu_div_enable,
164 	.is_enabled	= ccu_div_is_enabled,
165 
166 	.get_parent	= ccu_div_get_parent,
167 	.set_parent	= ccu_div_set_parent,
168 
169 	.determine_rate	= ccu_div_determine_rate,
170 	.recalc_rate	= ccu_div_recalc_rate,
171 };
172 EXPORT_SYMBOL_NS_GPL(ccu_rodiv_ops, "SUNXI_CCU");
173