summaryrefslogtreecommitdiff
path: root/drivers/clk/meson/clk-phase.c
blob: 7012111206107a25ca77a5c51cb5b19d9755f3c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
 * Copyright (c) 2018 BayLibre, SAS.
 * Author: Jerome Brunet <jbrunet@baylibre.com>
 */

#include <linux/clk-provider.h>
#include <linux/module.h>

#include "clk-regmap.h"
#include "clk-phase.h"

#define phase_step(_width) (360 / (1 << (_width)))

static inline struct meson_clk_phase_data *
meson_clk_phase_data(struct clk_regmap *clk)
{
	return (struct meson_clk_phase_data *)clk->data;
}

static int meson_clk_degrees_from_val(unsigned int val, unsigned int width)
{
	return phase_step(width) * val;
}

static unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width)
{
	unsigned int val = DIV_ROUND_CLOSEST(degrees, phase_step(width));

	/*
	 * This last calculation is here for cases when degrees is rounded
	 * to 360, in which case val == (1 << width).
	 */
	return val % (1 << width);
}

static int meson_clk_phase_get_phase(struct clk_hw *hw)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_clk_phase_data *phase = meson_clk_phase_data(clk);
	unsigned int val;

	val = meson_parm_read(clk->map, &phase->ph);

	return meson_clk_degrees_from_val(val, phase->ph.width);
}

static int meson_clk_phase_set_phase(struct clk_hw *hw, int degrees)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_clk_phase_data *phase = meson_clk_phase_data(clk);
	unsigned int val;

	val = meson_clk_degrees_to_val(degrees, phase->ph.width);
	meson_parm_write(clk->map, &phase->ph, val);

	return 0;
}

const struct clk_ops meson_clk_phase_ops = {
	.get_phase	= meson_clk_phase_get_phase,
	.set_phase	= meson_clk_phase_set_phase,
};
EXPORT_SYMBOL_NS_GPL(meson_clk_phase_ops, "CLK_MESON");

/*
 * This is a special clock for the audio controller.
 * The phase of mst_sclk clock output can be controlled independently
 * for the outside world (ph0), the tdmout (ph1) and tdmin (ph2).
 * Controlling these 3 phases as just one makes things simpler and
 * give the same clock view to all the element on the i2s bus.
 * If necessary, we can still control the phase in the tdm block
 * which makes these independent control redundant.
 */
static inline struct meson_clk_triphase_data *
meson_clk_triphase_data(struct clk_regmap *clk)
{
	return (struct meson_clk_triphase_data *)clk->data;
}

static int meson_clk_triphase_sync(struct clk_hw *hw)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
	unsigned int val;

	/* Get phase 0 and sync it to phase 1 and 2 */
	val = meson_parm_read(clk->map, &tph->ph0);
	meson_parm_write(clk->map, &tph->ph1, val);
	meson_parm_write(clk->map, &tph->ph2, val);

	return 0;
}

static int meson_clk_triphase_get_phase(struct clk_hw *hw)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
	unsigned int val;

	/* Phase are in sync, reading phase 0 is enough */
	val = meson_parm_read(clk->map, &tph->ph0);

	return meson_clk_degrees_from_val(val, tph->ph0.width);
}

static int meson_clk_triphase_set_phase(struct clk_hw *hw, int degrees)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_clk_triphase_data *tph = meson_clk_triphase_data(clk);
	unsigned int val;

	val = meson_clk_degrees_to_val(degrees, tph->ph0.width);
	meson_parm_write(clk->map, &tph->ph0, val);
	meson_parm_write(clk->map, &tph->ph1, val);
	meson_parm_write(clk->map, &tph->ph2, val);

	return 0;
}

const struct clk_ops meson_clk_triphase_ops = {
	.init		= meson_clk_triphase_sync,
	.get_phase	= meson_clk_triphase_get_phase,
	.set_phase	= meson_clk_triphase_set_phase,
};
EXPORT_SYMBOL_NS_GPL(meson_clk_triphase_ops, "CLK_MESON");

/*
 * This is a special clock for the audio controller.
 * This drive a bit clock inverter for which the
 * opposite value of the inverter bit needs to be manually
 * set into another bit
 */
static inline struct meson_sclk_ws_inv_data *
meson_sclk_ws_inv_data(struct clk_regmap *clk)
{
	return (struct meson_sclk_ws_inv_data *)clk->data;
}

static int meson_sclk_ws_inv_sync(struct clk_hw *hw)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
	unsigned int val;

	/* Get phase and sync the inverted value to ws */
	val = meson_parm_read(clk->map, &tph->ph);
	meson_parm_write(clk->map, &tph->ws, val ? 0 : 1);

	return 0;
}

static int meson_sclk_ws_inv_get_phase(struct clk_hw *hw)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
	unsigned int val;

	val = meson_parm_read(clk->map, &tph->ph);

	return meson_clk_degrees_from_val(val, tph->ph.width);
}

static int meson_sclk_ws_inv_set_phase(struct clk_hw *hw, int degrees)
{
	struct clk_regmap *clk = to_clk_regmap(hw);
	struct meson_sclk_ws_inv_data *tph = meson_sclk_ws_inv_data(clk);
	unsigned int val;

	val = meson_clk_degrees_to_val(degrees, tph->ph.width);
	meson_parm_write(clk->map, &tph->ph, val);
	meson_parm_write(clk->map, &tph->ws, val ? 0 : 1);
	return 0;
}

const struct clk_ops meson_sclk_ws_inv_ops = {
	.init		= meson_sclk_ws_inv_sync,
	.get_phase	= meson_sclk_ws_inv_get_phase,
	.set_phase	= meson_sclk_ws_inv_set_phase,
};
EXPORT_SYMBOL_NS_GPL(meson_sclk_ws_inv_ops, "CLK_MESON");

MODULE_DESCRIPTION("Amlogic phase driver");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("CLK_MESON");