]>
Commit | Line | Data |
---|---|---|
47f21315 JB |
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> | |
889c2b7e JB |
8 | #include <linux/module.h> |
9 | ||
10 | #include "clk-regmap.h" | |
11 | #include "clk-phase.h" | |
47f21315 JB |
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 | ||
889c2b7e | 21 | static int meson_clk_degrees_from_val(unsigned int val, unsigned int width) |
47f21315 JB |
22 | { |
23 | return phase_step(width) * val; | |
24 | } | |
47f21315 | 25 | |
889c2b7e | 26 | static unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width) |
47f21315 JB |
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 | } | |
47f21315 JB |
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 | .get_phase = meson_clk_phase_get_phase, | |
62 | .set_phase = meson_clk_phase_set_phase, | |
63 | }; | |
64 | EXPORT_SYMBOL_GPL(meson_clk_phase_ops); | |
889c2b7e JB |
65 | |
66 | /* | |
67 | * This is a special clock for the audio controller. | |
68 | * The phase of mst_sclk clock output can be controlled independently | |
69 | * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2). | |
70 | * Controlling these 3 phases as just one makes things simpler and | |
71 | * give the same clock view to all the element on the i2s bus. | |
72 | * If necessary, we can still control the phase in the tdm block | |
73 | * which makes these independent control redundant. | |
74 | */ | |
75 | static inline struct meson_clk_triphase_data * | |
76 | meson_clk_triphase_data(struct clk_regmap *clk) | |
77 | { | |
78 | return (struct meson_clk_triphase_data *)clk->data; | |
79 | } | |
80 | ||
89d079dc | 81 | static int meson_clk_triphase_sync(struct clk_hw *hw) |
889c2b7e JB |
82 | { |
83 | struct clk_regmap *clk = to_clk_regmap(hw); | |
84 | struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); | |
85 | unsigned int val; | |
86 | ||
87 | /* Get phase 0 and sync it to phase 1 and 2 */ | |
88 | val = meson_parm_read(clk->map, &tph->ph0); | |
89 | meson_parm_write(clk->map, &tph->ph1, val); | |
90 | meson_parm_write(clk->map, &tph->ph2, val); | |
89d079dc JB |
91 | |
92 | return 0; | |
889c2b7e JB |
93 | } |
94 | ||
95 | static int meson_clk_triphase_get_phase(struct clk_hw *hw) | |
96 | { | |
97 | struct clk_regmap *clk = to_clk_regmap(hw); | |
98 | struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); | |
99 | unsigned int val; | |
100 | ||
101 | /* Phase are in sync, reading phase 0 is enough */ | |
102 | val = meson_parm_read(clk->map, &tph->ph0); | |
103 | ||
104 | return meson_clk_degrees_from_val(val, tph->ph0.width); | |
105 | } | |
106 | ||
107 | static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees) | |
108 | { | |
109 | struct clk_regmap *clk = to_clk_regmap(hw); | |
110 | struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk); | |
111 | unsigned int val; | |
112 | ||
113 | val = meson_clk_degrees_to_val(degrees, tph->ph0.width); | |
114 | meson_parm_write(clk->map, &tph->ph0, val); | |
115 | meson_parm_write(clk->map, &tph->ph1, val); | |
116 | meson_parm_write(clk->map, &tph->ph2, val); | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
121 | const struct clk_ops meson_clk_triphase_ops = { | |
122 | .init = meson_clk_triphase_sync, | |
123 | .get_phase = meson_clk_triphase_get_phase, | |
124 | .set_phase = meson_clk_triphase_set_phase, | |
125 | }; | |
126 | EXPORT_SYMBOL_GPL(meson_clk_triphase_ops); | |
127 | ||
128 | MODULE_DESCRIPTION("Amlogic phase driver"); | |
129 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | |
130 | MODULE_LICENSE("GPL v2"); |