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 | /* This driver implements the frontend capture DAI of AXG based SoCs */ |
7 | |
8 | #include <linux/bitfield.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of_platform.h> |
13 | #include <sound/pcm_params.h> |
14 | #include <sound/soc.h> |
15 | #include <sound/soc-dai.h> |
16 | |
17 | #include "axg-fifo.h" |
18 | |
19 | #define CTRL0_TODDR_SEL_RESAMPLE BIT(30) |
20 | #define CTRL0_TODDR_EXT_SIGNED BIT(29) |
21 | #define CTRL0_TODDR_PP_MODE BIT(28) |
22 | #define CTRL0_TODDR_SYNC_CH BIT(27) |
23 | #define CTRL0_TODDR_TYPE GENMASK(15, 13) |
24 | #define CTRL0_TODDR_MSB_POS GENMASK(12, 8) |
25 | #define CTRL0_TODDR_LSB_POS GENMASK(7, 3) |
26 | #define CTRL1_TODDR_FORCE_FINISH BIT(25) |
27 | #define CTRL1_SEL_SHIFT 28 |
28 | |
29 | #define TODDR_MSB_POS 31 |
30 | |
31 | static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd, |
32 | struct snd_soc_dai *dai) |
33 | { |
34 | return axg_fifo_pcm_new(rtd, type: SNDRV_PCM_STREAM_CAPTURE); |
35 | } |
36 | |
37 | static int g12a_toddr_dai_prepare(struct snd_pcm_substream *substream, |
38 | struct snd_soc_dai *dai) |
39 | { |
40 | struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); |
41 | |
42 | /* Reset the write pointer to the FIFO_INIT_ADDR */ |
43 | regmap_update_bits(map: fifo->map, FIFO_CTRL1, |
44 | CTRL1_TODDR_FORCE_FINISH, val: 0); |
45 | regmap_update_bits(map: fifo->map, FIFO_CTRL1, |
46 | CTRL1_TODDR_FORCE_FINISH, CTRL1_TODDR_FORCE_FINISH); |
47 | regmap_update_bits(map: fifo->map, FIFO_CTRL1, |
48 | CTRL1_TODDR_FORCE_FINISH, val: 0); |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream, |
54 | struct snd_pcm_hw_params *params, |
55 | struct snd_soc_dai *dai) |
56 | { |
57 | struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); |
58 | unsigned int type, width; |
59 | |
60 | switch (params_physical_width(p: params)) { |
61 | case 8: |
62 | type = 0; /* 8 samples of 8 bits */ |
63 | break; |
64 | case 16: |
65 | type = 2; /* 4 samples of 16 bits - right justified */ |
66 | break; |
67 | case 32: |
68 | type = 4; /* 2 samples of 32 bits - right justified */ |
69 | break; |
70 | default: |
71 | return -EINVAL; |
72 | } |
73 | |
74 | width = params_width(p: params); |
75 | |
76 | regmap_update_bits(map: fifo->map, FIFO_CTRL0, |
77 | CTRL0_TODDR_TYPE | |
78 | CTRL0_TODDR_MSB_POS | |
79 | CTRL0_TODDR_LSB_POS, |
80 | FIELD_PREP(CTRL0_TODDR_TYPE, type) | |
81 | FIELD_PREP(CTRL0_TODDR_MSB_POS, TODDR_MSB_POS) | |
82 | FIELD_PREP(CTRL0_TODDR_LSB_POS, TODDR_MSB_POS - (width - 1))); |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | static int axg_toddr_dai_startup(struct snd_pcm_substream *substream, |
88 | struct snd_soc_dai *dai) |
89 | { |
90 | struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); |
91 | int ret; |
92 | |
93 | /* Enable pclk to access registers and clock the fifo ip */ |
94 | ret = clk_prepare_enable(clk: fifo->pclk); |
95 | if (ret) |
96 | return ret; |
97 | |
98 | /* Select orginal data - resampling not supported ATM */ |
99 | regmap_update_bits(map: fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, val: 0); |
100 | |
101 | /* Only signed format are supported ATM */ |
102 | regmap_update_bits(map: fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED, |
103 | CTRL0_TODDR_EXT_SIGNED); |
104 | |
105 | /* Apply single buffer mode to the interface */ |
106 | regmap_update_bits(map: fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, val: 0); |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream, |
112 | struct snd_soc_dai *dai) |
113 | { |
114 | struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); |
115 | |
116 | clk_disable_unprepare(clk: fifo->pclk); |
117 | } |
118 | |
119 | static const struct snd_soc_dai_ops axg_toddr_ops = { |
120 | .hw_params = axg_toddr_dai_hw_params, |
121 | .startup = axg_toddr_dai_startup, |
122 | .shutdown = axg_toddr_dai_shutdown, |
123 | .pcm_new = axg_toddr_pcm_new, |
124 | }; |
125 | |
126 | static struct snd_soc_dai_driver axg_toddr_dai_drv = { |
127 | .name = "TODDR" , |
128 | .capture = { |
129 | .stream_name = "Capture" , |
130 | .channels_min = 1, |
131 | .channels_max = AXG_FIFO_CH_MAX, |
132 | .rates = SNDRV_PCM_RATE_CONTINUOUS, |
133 | .rate_min = 5515, |
134 | .rate_max = 384000, |
135 | .formats = AXG_FIFO_FORMATS, |
136 | }, |
137 | .ops = &axg_toddr_ops, |
138 | }; |
139 | |
140 | static const char * const axg_toddr_sel_texts[] = { |
141 | "IN 0" , "IN 1" , "IN 2" , "IN 3" , "IN 4" , "IN 5" , "IN 6" , "IN 7" |
142 | }; |
143 | |
144 | static SOC_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, |
145 | axg_toddr_sel_texts); |
146 | |
147 | static const struct snd_kcontrol_new axg_toddr_in_mux = |
148 | SOC_DAPM_ENUM("Input Source" , axg_toddr_sel_enum); |
149 | |
150 | static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = { |
151 | SND_SOC_DAPM_MUX("SRC SEL" , SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux), |
152 | SND_SOC_DAPM_AIF_IN("IN 0" , NULL, 0, SND_SOC_NOPM, 0, 0), |
153 | SND_SOC_DAPM_AIF_IN("IN 1" , NULL, 0, SND_SOC_NOPM, 0, 0), |
154 | SND_SOC_DAPM_AIF_IN("IN 2" , NULL, 0, SND_SOC_NOPM, 0, 0), |
155 | SND_SOC_DAPM_AIF_IN("IN 3" , NULL, 0, SND_SOC_NOPM, 0, 0), |
156 | SND_SOC_DAPM_AIF_IN("IN 4" , NULL, 0, SND_SOC_NOPM, 0, 0), |
157 | SND_SOC_DAPM_AIF_IN("IN 5" , NULL, 0, SND_SOC_NOPM, 0, 0), |
158 | SND_SOC_DAPM_AIF_IN("IN 6" , NULL, 0, SND_SOC_NOPM, 0, 0), |
159 | SND_SOC_DAPM_AIF_IN("IN 7" , NULL, 0, SND_SOC_NOPM, 0, 0), |
160 | }; |
161 | |
162 | static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = { |
163 | { "Capture" , NULL, "SRC SEL" }, |
164 | { "SRC SEL" , "IN 0" , "IN 0" }, |
165 | { "SRC SEL" , "IN 1" , "IN 1" }, |
166 | { "SRC SEL" , "IN 2" , "IN 2" }, |
167 | { "SRC SEL" , "IN 3" , "IN 3" }, |
168 | { "SRC SEL" , "IN 4" , "IN 4" }, |
169 | { "SRC SEL" , "IN 5" , "IN 5" }, |
170 | { "SRC SEL" , "IN 6" , "IN 6" }, |
171 | { "SRC SEL" , "IN 7" , "IN 7" }, |
172 | }; |
173 | |
174 | static const struct snd_soc_component_driver axg_toddr_component_drv = { |
175 | .dapm_widgets = axg_toddr_dapm_widgets, |
176 | .num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets), |
177 | .dapm_routes = axg_toddr_dapm_routes, |
178 | .num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes), |
179 | .open = axg_fifo_pcm_open, |
180 | .close = axg_fifo_pcm_close, |
181 | .hw_params = axg_fifo_pcm_hw_params, |
182 | .hw_free = axg_fifo_pcm_hw_free, |
183 | .pointer = axg_fifo_pcm_pointer, |
184 | .trigger = axg_fifo_pcm_trigger, |
185 | .legacy_dai_naming = 1, |
186 | }; |
187 | |
188 | static const struct axg_fifo_match_data axg_toddr_match_data = { |
189 | .field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23), |
190 | .component_drv = &axg_toddr_component_drv, |
191 | .dai_drv = &axg_toddr_dai_drv |
192 | }; |
193 | |
194 | static int g12a_toddr_dai_startup(struct snd_pcm_substream *substream, |
195 | struct snd_soc_dai *dai) |
196 | { |
197 | struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); |
198 | int ret; |
199 | |
200 | ret = axg_toddr_dai_startup(substream, dai); |
201 | if (ret) |
202 | return ret; |
203 | |
204 | /* |
205 | * Make sure the first channel ends up in the at beginning of the output |
206 | * As weird as it looks, without this the first channel may be misplaced |
207 | * in memory, with a random shift of 2 channels. |
208 | */ |
209 | regmap_update_bits(map: fifo->map, FIFO_CTRL0, CTRL0_TODDR_SYNC_CH, |
210 | CTRL0_TODDR_SYNC_CH); |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | static const struct snd_soc_dai_ops g12a_toddr_ops = { |
216 | .prepare = g12a_toddr_dai_prepare, |
217 | .hw_params = axg_toddr_dai_hw_params, |
218 | .startup = g12a_toddr_dai_startup, |
219 | .shutdown = axg_toddr_dai_shutdown, |
220 | .pcm_new = axg_toddr_pcm_new, |
221 | }; |
222 | |
223 | static struct snd_soc_dai_driver g12a_toddr_dai_drv = { |
224 | .name = "TODDR" , |
225 | .capture = { |
226 | .stream_name = "Capture" , |
227 | .channels_min = 1, |
228 | .channels_max = AXG_FIFO_CH_MAX, |
229 | .rates = SNDRV_PCM_RATE_CONTINUOUS, |
230 | .rate_min = 5515, |
231 | .rate_max = 384000, |
232 | .formats = AXG_FIFO_FORMATS, |
233 | }, |
234 | .ops = &g12a_toddr_ops, |
235 | }; |
236 | |
237 | static const struct snd_soc_component_driver g12a_toddr_component_drv = { |
238 | .dapm_widgets = axg_toddr_dapm_widgets, |
239 | .num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets), |
240 | .dapm_routes = axg_toddr_dapm_routes, |
241 | .num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes), |
242 | .open = axg_fifo_pcm_open, |
243 | .close = axg_fifo_pcm_close, |
244 | .hw_params = g12a_fifo_pcm_hw_params, |
245 | .hw_free = axg_fifo_pcm_hw_free, |
246 | .pointer = axg_fifo_pcm_pointer, |
247 | .trigger = axg_fifo_pcm_trigger, |
248 | .legacy_dai_naming = 1, |
249 | }; |
250 | |
251 | static const struct axg_fifo_match_data g12a_toddr_match_data = { |
252 | .field_threshold = REG_FIELD(FIFO_CTRL1, 16, 23), |
253 | .component_drv = &g12a_toddr_component_drv, |
254 | .dai_drv = &g12a_toddr_dai_drv |
255 | }; |
256 | |
257 | static const char * const sm1_toddr_sel_texts[] = { |
258 | "IN 0" , "IN 1" , "IN 2" , "IN 3" , "IN 4" , "IN 5" , "IN 6" , "IN 7" , |
259 | "IN 8" , "IN 9" , "IN 10" , "IN 11" , "IN 12" , "IN 13" , "IN 14" , "IN 15" |
260 | }; |
261 | |
262 | static SOC_ENUM_SINGLE_DECL(sm1_toddr_sel_enum, FIFO_CTRL1, CTRL1_SEL_SHIFT, |
263 | sm1_toddr_sel_texts); |
264 | |
265 | static const struct snd_kcontrol_new sm1_toddr_in_mux = |
266 | SOC_DAPM_ENUM("Input Source" , sm1_toddr_sel_enum); |
267 | |
268 | static const struct snd_soc_dapm_widget sm1_toddr_dapm_widgets[] = { |
269 | SND_SOC_DAPM_MUX("SRC SEL" , SND_SOC_NOPM, 0, 0, &sm1_toddr_in_mux), |
270 | SND_SOC_DAPM_AIF_IN("IN 0" , NULL, 0, SND_SOC_NOPM, 0, 0), |
271 | SND_SOC_DAPM_AIF_IN("IN 1" , NULL, 0, SND_SOC_NOPM, 0, 0), |
272 | SND_SOC_DAPM_AIF_IN("IN 2" , NULL, 0, SND_SOC_NOPM, 0, 0), |
273 | SND_SOC_DAPM_AIF_IN("IN 3" , NULL, 0, SND_SOC_NOPM, 0, 0), |
274 | SND_SOC_DAPM_AIF_IN("IN 4" , NULL, 0, SND_SOC_NOPM, 0, 0), |
275 | SND_SOC_DAPM_AIF_IN("IN 5" , NULL, 0, SND_SOC_NOPM, 0, 0), |
276 | SND_SOC_DAPM_AIF_IN("IN 6" , NULL, 0, SND_SOC_NOPM, 0, 0), |
277 | SND_SOC_DAPM_AIF_IN("IN 7" , NULL, 0, SND_SOC_NOPM, 0, 0), |
278 | SND_SOC_DAPM_AIF_IN("IN 8" , NULL, 0, SND_SOC_NOPM, 0, 0), |
279 | SND_SOC_DAPM_AIF_IN("IN 9" , NULL, 0, SND_SOC_NOPM, 0, 0), |
280 | SND_SOC_DAPM_AIF_IN("IN 10" , NULL, 0, SND_SOC_NOPM, 0, 0), |
281 | SND_SOC_DAPM_AIF_IN("IN 11" , NULL, 0, SND_SOC_NOPM, 0, 0), |
282 | SND_SOC_DAPM_AIF_IN("IN 12" , NULL, 0, SND_SOC_NOPM, 0, 0), |
283 | SND_SOC_DAPM_AIF_IN("IN 13" , NULL, 0, SND_SOC_NOPM, 0, 0), |
284 | SND_SOC_DAPM_AIF_IN("IN 14" , NULL, 0, SND_SOC_NOPM, 0, 0), |
285 | SND_SOC_DAPM_AIF_IN("IN 15" , NULL, 0, SND_SOC_NOPM, 0, 0), |
286 | }; |
287 | |
288 | static const struct snd_soc_dapm_route sm1_toddr_dapm_routes[] = { |
289 | { "Capture" , NULL, "SRC SEL" }, |
290 | { "SRC SEL" , "IN 0" , "IN 0" }, |
291 | { "SRC SEL" , "IN 1" , "IN 1" }, |
292 | { "SRC SEL" , "IN 2" , "IN 2" }, |
293 | { "SRC SEL" , "IN 3" , "IN 3" }, |
294 | { "SRC SEL" , "IN 4" , "IN 4" }, |
295 | { "SRC SEL" , "IN 5" , "IN 5" }, |
296 | { "SRC SEL" , "IN 6" , "IN 6" }, |
297 | { "SRC SEL" , "IN 7" , "IN 7" }, |
298 | { "SRC SEL" , "IN 8" , "IN 8" }, |
299 | { "SRC SEL" , "IN 9" , "IN 9" }, |
300 | { "SRC SEL" , "IN 10" , "IN 10" }, |
301 | { "SRC SEL" , "IN 11" , "IN 11" }, |
302 | { "SRC SEL" , "IN 12" , "IN 12" }, |
303 | { "SRC SEL" , "IN 13" , "IN 13" }, |
304 | { "SRC SEL" , "IN 14" , "IN 14" }, |
305 | { "SRC SEL" , "IN 15" , "IN 15" }, |
306 | }; |
307 | |
308 | static const struct snd_soc_component_driver sm1_toddr_component_drv = { |
309 | .dapm_widgets = sm1_toddr_dapm_widgets, |
310 | .num_dapm_widgets = ARRAY_SIZE(sm1_toddr_dapm_widgets), |
311 | .dapm_routes = sm1_toddr_dapm_routes, |
312 | .num_dapm_routes = ARRAY_SIZE(sm1_toddr_dapm_routes), |
313 | .open = axg_fifo_pcm_open, |
314 | .close = axg_fifo_pcm_close, |
315 | .hw_params = g12a_fifo_pcm_hw_params, |
316 | .hw_free = axg_fifo_pcm_hw_free, |
317 | .pointer = axg_fifo_pcm_pointer, |
318 | .trigger = axg_fifo_pcm_trigger, |
319 | .legacy_dai_naming = 1, |
320 | }; |
321 | |
322 | static const struct axg_fifo_match_data sm1_toddr_match_data = { |
323 | .field_threshold = REG_FIELD(FIFO_CTRL1, 12, 23), |
324 | .component_drv = &sm1_toddr_component_drv, |
325 | .dai_drv = &g12a_toddr_dai_drv |
326 | }; |
327 | |
328 | static const struct of_device_id axg_toddr_of_match[] = { |
329 | { |
330 | .compatible = "amlogic,axg-toddr" , |
331 | .data = &axg_toddr_match_data, |
332 | }, { |
333 | .compatible = "amlogic,g12a-toddr" , |
334 | .data = &g12a_toddr_match_data, |
335 | }, { |
336 | .compatible = "amlogic,sm1-toddr" , |
337 | .data = &sm1_toddr_match_data, |
338 | }, {} |
339 | }; |
340 | MODULE_DEVICE_TABLE(of, axg_toddr_of_match); |
341 | |
342 | static struct platform_driver axg_toddr_pdrv = { |
343 | .probe = axg_fifo_probe, |
344 | .driver = { |
345 | .name = "axg-toddr" , |
346 | .of_match_table = axg_toddr_of_match, |
347 | }, |
348 | }; |
349 | module_platform_driver(axg_toddr_pdrv); |
350 | |
351 | MODULE_DESCRIPTION("Amlogic AXG capture fifo driver" ); |
352 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>" ); |
353 | MODULE_LICENSE("GPL v2" ); |
354 | |