xref: /linux/drivers/clk/meson/clk-phase.c (revision b4ada0618eed0fbd1b1630f73deb048c592b06a1)
1 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
2 /*
3  * Copyright (c) 2018 BayLibre, SAS.
4  * Author: Jerome Brunet <jbrunet@baylibre.com>
5  */
6 
7 #include <linux/clk-provider.h>
8 #include <linux/module.h>
9 
10 #include "clk-regmap.h"
11 #include "clk-phase.h"
12 
13 #define phase_step(_width) (360 / (1 << (_width)))
14 
15 static inline struct meson_clk_phase_data *
16 meson_clk_phase_data(struct clk_regmap *clk)
17 {
18 	return (struct meson_clk_phase_data *)clk->data;
19 }
20 
21 static int meson_clk_degrees_from_val(unsigned int val, unsigned int width)
22 {
23 	return phase_step(width) * val;
24 }
25 
26 static unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width)
27 {
28 	unsigned int val = DIV_ROUND_CLOSEST(degrees, phase_step(width));
29 
30 	/*
31 	 * This last calculation is here for cases when degrees is rounded
32 	 * to 360, in which case val == (1 << width).
33 	 */
34 	return val % (1 << width);
35 }
36 
37 static int meson_clk_phase_get_phase(struct clk_hw *hw)
38 {
39 	struct clk_regmap *clk = to_clk_regmap(hw);
40 	struct meson_clk_phase_data *phase = meson_clk_phase_data(clk);
41 	unsigned int val;
42 
43 	val = meson_parm_read(clk->map, &phase->ph);
44 
45 	return meson_clk_degrees_from_val(val, phase->ph.width);
46 }
47 
48 static int meson_clk_phase_set_phase(struct clk_hw *hw, int degrees)
49 {
50 	struct clk_regmap *clk = to_clk_regmap(hw);
51 	struct meson_clk_phase_data *phase = meson_clk_phase_data(clk);
52 	unsigned int val;
53 
54 	val = meson_clk_degrees_to_val(degrees, phase->ph.width);
55 	meson_parm_write(clk->map, &phase->ph, val);
56 
57 	return 0;
58 }
59 
60 const struct clk_ops meson_clk_phase_ops = {
61 	.init		= clk_regmap_init,
62 	.get_phase	= meson_clk_phase_get_phase,
63 	.set_phase	= meson_clk_phase_set_phase,
64 };
65 EXPORT_SYMBOL_NS_GPL(meson_clk_phase_ops, "CLK_MESON");
66 
67 /*
68  * This is a special clock for the audio controller.
69  * The phase of mst_sclk clock output can be controlled independently
70  * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2).
71  * Controlling these 3 phases as just one makes things simpler and
72  * give the same clock view to all the element on the i2s bus.
73  * If necessary, we can still control the phase in the tdm block
74  * which makes these independent control redundant.
75  */
76 static inline struct meson_clk_triphase_data *
77 meson_clk_triphase_data(struct clk_regmap *clk)
78 {
79 	return (struct meson_clk_triphase_data *)clk->data;
80 }
81 
82 static int meson_clk_triphase_sync(struct clk_hw *hw)
83 {
84 	struct clk_regmap *clk = to_clk_regmap(hw);
85 	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
86 	unsigned int val;
87 	int ret;
88 
89 	ret = clk_regmap_init(hw);
90 	if (ret)
91 		return ret;
92 
93 	/* Get phase 0 and sync it to phase 1 and 2 */
94 	val = meson_parm_read(clk->map, &tph->ph0);
95 	meson_parm_write(clk->map, &tph->ph1, val);
96 	meson_parm_write(clk->map, &tph->ph2, val);
97 
98 	return 0;
99 }
100 
101 static int meson_clk_triphase_get_phase(struct clk_hw *hw)
102 {
103 	struct clk_regmap *clk = to_clk_regmap(hw);
104 	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
105 	unsigned int val;
106 
107 	/* Phase are in sync, reading phase 0 is enough */
108 	val = meson_parm_read(clk->map, &tph->ph0);
109 
110 	return meson_clk_degrees_from_val(val, tph->ph0.width);
111 }
112 
113 static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees)
114 {
115 	struct clk_regmap *clk = to_clk_regmap(hw);
116 	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
117 	unsigned int val;
118 
119 	val = meson_clk_degrees_to_val(degrees, tph->ph0.width);
120 	meson_parm_write(clk->map, &tph->ph0, val);
121 	meson_parm_write(clk->map, &tph->ph1, val);
122 	meson_parm_write(clk->map, &tph->ph2, val);
123 
124 	return 0;
125 }
126 
127 const struct clk_ops meson_clk_triphase_ops = {
128 	.init		= meson_clk_triphase_sync,
129 	.get_phase	= meson_clk_triphase_get_phase,
130 	.set_phase	= meson_clk_triphase_set_phase,
131 };
132 EXPORT_SYMBOL_NS_GPL(meson_clk_triphase_ops, "CLK_MESON");
133 
134 /*
135  * This is a special clock for the audio controller.
136  * This drive a bit clock inverter for which the
137  * opposite value of the inverter bit needs to be manually
138  * set into another bit
139  */
140 static inline struct meson_sclk_ws_inv_data *
141 meson_sclk_ws_inv_data(struct clk_regmap *clk)
142 {
143 	return (struct meson_sclk_ws_inv_data *)clk->data;
144 }
145 
146 static int meson_sclk_ws_inv_sync(struct clk_hw *hw)
147 {
148 	struct clk_regmap *clk = to_clk_regmap(hw);
149 	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
150 	unsigned int val;
151 	int ret;
152 
153 	ret = clk_regmap_init(hw);
154 	if (ret)
155 		return ret;
156 
157 	/* Get phase and sync the inverted value to ws */
158 	val = meson_parm_read(clk->map, &tph->ph);
159 	meson_parm_write(clk->map, &tph->ws, val ? 0 : 1);
160 
161 	return 0;
162 }
163 
164 static int meson_sclk_ws_inv_get_phase(struct clk_hw *hw)
165 {
166 	struct clk_regmap *clk = to_clk_regmap(hw);
167 	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
168 	unsigned int val;
169 
170 	val = meson_parm_read(clk->map, &tph->ph);
171 
172 	return meson_clk_degrees_from_val(val, tph->ph.width);
173 }
174 
175 static int meson_sclk_ws_inv_set_phase(struct clk_hw *hw, int degrees)
176 {
177 	struct clk_regmap *clk = to_clk_regmap(hw);
178 	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
179 	unsigned int val;
180 
181 	val = meson_clk_degrees_to_val(degrees, tph->ph.width);
182 	meson_parm_write(clk->map, &tph->ph, val);
183 	meson_parm_write(clk->map, &tph->ws, val ? 0 : 1);
184 	return 0;
185 }
186 
187 const struct clk_ops meson_sclk_ws_inv_ops = {
188 	.init		= meson_sclk_ws_inv_sync,
189 	.get_phase	= meson_sclk_ws_inv_get_phase,
190 	.set_phase	= meson_sclk_ws_inv_set_phase,
191 };
192 EXPORT_SYMBOL_NS_GPL(meson_sclk_ws_inv_ops, "CLK_MESON");
193 
194 MODULE_DESCRIPTION("Amlogic phase driver");
195 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
196 MODULE_LICENSE("GPL");
197 MODULE_IMPORT_NS("CLK_MESON");
198