Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.10.11.
  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 * This driver implements the frontend playback DAI of AXG and G12A based SoCs
  8 */
  9
 10#include <linux/clk.h>
 11#include <linux/regmap.h>
 12#include <linux/module.h>
 13#include <linux/of_platform.h>
 14#include <sound/soc.h>
 15#include <sound/soc-dai.h>
 16
 17#include "axg-fifo.h"
 18
 19#define CTRL0_FRDDR_PP_MODE		BIT(30)
 20#define CTRL0_SEL1_EN_SHIFT		3
 21#define CTRL0_SEL2_SHIFT		4
 22#define CTRL0_SEL2_EN_SHIFT		7
 23#define CTRL0_SEL3_SHIFT		8
 24#define CTRL0_SEL3_EN_SHIFT		11
 25#define CTRL1_FRDDR_FORCE_FINISH	BIT(12)
 26#define CTRL2_SEL1_SHIFT		0
 27#define CTRL2_SEL1_EN_SHIFT		4
 28#define CTRL2_SEL2_SHIFT		8
 29#define CTRL2_SEL2_EN_SHIFT		12
 30#define CTRL2_SEL3_SHIFT		16
 31#define CTRL2_SEL3_EN_SHIFT		20
 32
 33static int g12a_frddr_dai_prepare(struct snd_pcm_substream *substream,
 34				  struct snd_soc_dai *dai)
 35{
 36	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
 37
 38	/* Reset the read pointer to the FIFO_INIT_ADDR */
 39	regmap_update_bits(fifo->map, FIFO_CTRL1,
 40			   CTRL1_FRDDR_FORCE_FINISH, 0);
 41	regmap_update_bits(fifo->map, FIFO_CTRL1,
 42			   CTRL1_FRDDR_FORCE_FINISH, CTRL1_FRDDR_FORCE_FINISH);
 43	regmap_update_bits(fifo->map, FIFO_CTRL1,
 44			   CTRL1_FRDDR_FORCE_FINISH, 0);
 45
 46	return 0;
 47}
 48
 49static int axg_frddr_dai_startup(struct snd_pcm_substream *substream,
 50				 struct snd_soc_dai *dai)
 51{
 52	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
 53	unsigned int fifo_depth, fifo_threshold;
 54	int ret;
 55
 56	/* Enable pclk to access registers and clock the fifo ip */
 57	ret = clk_prepare_enable(fifo->pclk);
 58	if (ret)
 59		return ret;
 60
 61	/* Apply single buffer mode to the interface */
 62	regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0);
 63
 64	/*
 65	 * TODO: We could adapt the fifo depth and the fifo threshold
 66	 * depending on the expected memory throughput and lantencies
 67	 * For now, we'll just use the same values as the vendor kernel
 68	 * Depth and threshold are zero based.
 69	 */
 70	fifo_depth = AXG_FIFO_MIN_CNT - 1;
 71	fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1;
 72	regmap_update_bits(fifo->map, FIFO_CTRL1,
 73			   CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK,
 74			   CTRL1_FRDDR_DEPTH(fifo_depth) |
 75			   CTRL1_THRESHOLD(fifo_threshold));
 76
 77	return 0;
 78}
 79
 80static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream,
 81				   struct snd_soc_dai *dai)
 82{
 83	struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai);
 84
 85	clk_disable_unprepare(fifo->pclk);
 86}
 87
 88static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd,
 89			     struct snd_soc_dai *dai)
 90{
 91	return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK);
 92}
 93
 94static const struct snd_soc_dai_ops axg_frddr_ops = {
 95	.startup	= axg_frddr_dai_startup,
 96	.shutdown	= axg_frddr_dai_shutdown,
 97};
 98
 99static struct snd_soc_dai_driver axg_frddr_dai_drv = {
100	.name = "FRDDR",
101	.playback = {
102		.stream_name	= "Playback",
103		.channels_min	= 1,
104		.channels_max	= AXG_FIFO_CH_MAX,
105		.rates		= AXG_FIFO_RATES,
106		.formats	= AXG_FIFO_FORMATS,
107	},
108	.ops		= &axg_frddr_ops,
109	.pcm_new	= axg_frddr_pcm_new,
110};
111
112static const char * const axg_frddr_sel_texts[] = {
113	"OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", "OUT 5", "OUT 6", "OUT 7",
114};
115
116static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
117			    axg_frddr_sel_texts);
118
119static const struct snd_kcontrol_new axg_frddr_out_demux =
120	SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum);
121
122static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = {
123	SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0,
124			   &axg_frddr_out_demux),
125	SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
126	SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
127	SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
128	SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
129	SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
130	SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
131	SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
132	SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
133};
134
135static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = {
136	{ "SINK SEL", NULL, "Playback" },
137	{ "OUT 0", "OUT 0",  "SINK SEL" },
138	{ "OUT 1", "OUT 1",  "SINK SEL" },
139	{ "OUT 2", "OUT 2",  "SINK SEL" },
140	{ "OUT 3", "OUT 3",  "SINK SEL" },
141	{ "OUT 4", "OUT 4",  "SINK SEL" },
142	{ "OUT 5", "OUT 5",  "SINK SEL" },
143	{ "OUT 6", "OUT 6",  "SINK SEL" },
144	{ "OUT 7", "OUT 7",  "SINK SEL" },
145};
146
147static const struct snd_soc_component_driver axg_frddr_component_drv = {
148	.dapm_widgets		= axg_frddr_dapm_widgets,
149	.num_dapm_widgets	= ARRAY_SIZE(axg_frddr_dapm_widgets),
150	.dapm_routes		= axg_frddr_dapm_routes,
151	.num_dapm_routes	= ARRAY_SIZE(axg_frddr_dapm_routes),
152	.ops			= &axg_fifo_pcm_ops
153};
154
155static const struct axg_fifo_match_data axg_frddr_match_data = {
156	.component_drv	= &axg_frddr_component_drv,
157	.dai_drv	= &axg_frddr_dai_drv
158};
159
160static const struct snd_soc_dai_ops g12a_frddr_ops = {
161	.prepare	= g12a_frddr_dai_prepare,
162	.startup	= axg_frddr_dai_startup,
163	.shutdown	= axg_frddr_dai_shutdown,
164};
165
166static struct snd_soc_dai_driver g12a_frddr_dai_drv = {
167	.name = "FRDDR",
168	.playback = {
169		.stream_name	= "Playback",
170		.channels_min	= 1,
171		.channels_max	= AXG_FIFO_CH_MAX,
172		.rates		= AXG_FIFO_RATES,
173		.formats	= AXG_FIFO_FORMATS,
174	},
175	.ops		= &g12a_frddr_ops,
176	.pcm_new	= axg_frddr_pcm_new,
177};
178
179static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT,
180			    axg_frddr_sel_texts);
181static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT,
182			    axg_frddr_sel_texts);
183static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT,
184			    axg_frddr_sel_texts);
185
186static const struct snd_kcontrol_new g12a_frddr_out1_demux =
187	SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum);
188static const struct snd_kcontrol_new g12a_frddr_out2_demux =
189	SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum);
190static const struct snd_kcontrol_new g12a_frddr_out3_demux =
191	SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum);
192
193static const struct snd_kcontrol_new g12a_frddr_out1_enable =
194	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
195				    CTRL0_SEL1_EN_SHIFT, 1, 0);
196static const struct snd_kcontrol_new g12a_frddr_out2_enable =
197	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
198				    CTRL0_SEL2_EN_SHIFT, 1, 0);
199static const struct snd_kcontrol_new g12a_frddr_out3_enable =
200	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0,
201				    CTRL0_SEL3_EN_SHIFT, 1, 0);
202
203static const struct snd_soc_dapm_widget g12a_frddr_dapm_widgets[] = {
204	SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0),
205	SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0),
206	SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0),
207	SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0,
208			    &g12a_frddr_out1_enable),
209	SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0,
210			    &g12a_frddr_out2_enable),
211	SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0,
212			    &g12a_frddr_out3_enable),
213	SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0,
214			   &g12a_frddr_out1_demux),
215	SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0,
216			   &g12a_frddr_out2_demux),
217	SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0,
218			   &g12a_frddr_out3_demux),
219	SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
220	SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
221	SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
222	SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
223	SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
224	SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
225	SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
226	SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
227};
228
229static const struct snd_soc_dapm_route g12a_frddr_dapm_routes[] = {
230	{ "SRC 1", NULL, "Playback" },
231	{ "SRC 2", NULL, "Playback" },
232	{ "SRC 3", NULL, "Playback" },
233	{ "SRC 1 EN", "Switch", "SRC 1" },
234	{ "SRC 2 EN", "Switch", "SRC 2" },
235	{ "SRC 3 EN", "Switch", "SRC 3" },
236	{ "SINK 1 SEL", NULL, "SRC 1 EN" },
237	{ "SINK 2 SEL", NULL, "SRC 2 EN" },
238	{ "SINK 3 SEL", NULL, "SRC 3 EN" },
239	{ "OUT 0", "OUT 0", "SINK 1 SEL" },
240	{ "OUT 1", "OUT 1", "SINK 1 SEL" },
241	{ "OUT 2", "OUT 2", "SINK 1 SEL" },
242	{ "OUT 3", "OUT 3", "SINK 1 SEL" },
243	{ "OUT 4", "OUT 4", "SINK 1 SEL" },
244	{ "OUT 5", "OUT 5", "SINK 1 SEL" },
245	{ "OUT 6", "OUT 6", "SINK 1 SEL" },
246	{ "OUT 7", "OUT 7", "SINK 1 SEL" },
247	{ "OUT 0", "OUT 0", "SINK 2 SEL" },
248	{ "OUT 1", "OUT 1", "SINK 2 SEL" },
249	{ "OUT 2", "OUT 2", "SINK 2 SEL" },
250	{ "OUT 3", "OUT 3", "SINK 2 SEL" },
251	{ "OUT 4", "OUT 4", "SINK 2 SEL" },
252	{ "OUT 5", "OUT 5", "SINK 2 SEL" },
253	{ "OUT 6", "OUT 6", "SINK 2 SEL" },
254	{ "OUT 7", "OUT 7", "SINK 2 SEL" },
255	{ "OUT 0", "OUT 0", "SINK 3 SEL" },
256	{ "OUT 1", "OUT 1", "SINK 3 SEL" },
257	{ "OUT 2", "OUT 2", "SINK 3 SEL" },
258	{ "OUT 3", "OUT 3", "SINK 3 SEL" },
259	{ "OUT 4", "OUT 4", "SINK 3 SEL" },
260	{ "OUT 5", "OUT 5", "SINK 3 SEL" },
261	{ "OUT 6", "OUT 6", "SINK 3 SEL" },
262	{ "OUT 7", "OUT 7", "SINK 3 SEL" },
263};
264
265static const struct snd_soc_component_driver g12a_frddr_component_drv = {
266	.dapm_widgets		= g12a_frddr_dapm_widgets,
267	.num_dapm_widgets	= ARRAY_SIZE(g12a_frddr_dapm_widgets),
268	.dapm_routes		= g12a_frddr_dapm_routes,
269	.num_dapm_routes	= ARRAY_SIZE(g12a_frddr_dapm_routes),
270	.ops			= &g12a_fifo_pcm_ops
271};
272
273static const struct axg_fifo_match_data g12a_frddr_match_data = {
274	.component_drv	= &g12a_frddr_component_drv,
275	.dai_drv	= &g12a_frddr_dai_drv
276};
277
278/* On SM1, the output selection in on CTRL2 */
279static const struct snd_kcontrol_new sm1_frddr_out1_enable =
280	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
281				    CTRL2_SEL1_EN_SHIFT, 1, 0);
282static const struct snd_kcontrol_new sm1_frddr_out2_enable =
283	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
284				    CTRL2_SEL2_EN_SHIFT, 1, 0);
285static const struct snd_kcontrol_new sm1_frddr_out3_enable =
286	SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL2,
287				    CTRL2_SEL3_EN_SHIFT, 1, 0);
288
289static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel1_enum, FIFO_CTRL2, CTRL2_SEL1_SHIFT,
290			    axg_frddr_sel_texts);
291static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel2_enum, FIFO_CTRL2, CTRL2_SEL2_SHIFT,
292			    axg_frddr_sel_texts);
293static SOC_ENUM_SINGLE_DECL(sm1_frddr_sel3_enum, FIFO_CTRL2, CTRL2_SEL3_SHIFT,
294			    axg_frddr_sel_texts);
295
296static const struct snd_kcontrol_new sm1_frddr_out1_demux =
297	SOC_DAPM_ENUM("Output Src 1", sm1_frddr_sel1_enum);
298static const struct snd_kcontrol_new sm1_frddr_out2_demux =
299	SOC_DAPM_ENUM("Output Src 2", sm1_frddr_sel2_enum);
300static const struct snd_kcontrol_new sm1_frddr_out3_demux =
301	SOC_DAPM_ENUM("Output Src 3", sm1_frddr_sel3_enum);
302
303static const struct snd_soc_dapm_widget sm1_frddr_dapm_widgets[] = {
304	SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0),
305	SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0),
306	SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0),
307	SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0,
308			    &sm1_frddr_out1_enable),
309	SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0,
310			    &sm1_frddr_out2_enable),
311	SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0,
312			    &sm1_frddr_out3_enable),
313	SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0,
314			   &sm1_frddr_out1_demux),
315	SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0,
316			   &sm1_frddr_out2_demux),
317	SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0,
318			   &sm1_frddr_out3_demux),
319	SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0),
320	SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0),
321	SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0),
322	SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0),
323	SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0),
324	SND_SOC_DAPM_AIF_OUT("OUT 5", NULL, 0, SND_SOC_NOPM, 0, 0),
325	SND_SOC_DAPM_AIF_OUT("OUT 6", NULL, 0, SND_SOC_NOPM, 0, 0),
326	SND_SOC_DAPM_AIF_OUT("OUT 7", NULL, 0, SND_SOC_NOPM, 0, 0),
327};
328
329static const struct snd_soc_component_driver sm1_frddr_component_drv = {
330	.dapm_widgets		= sm1_frddr_dapm_widgets,
331	.num_dapm_widgets	= ARRAY_SIZE(sm1_frddr_dapm_widgets),
332	.dapm_routes		= g12a_frddr_dapm_routes,
333	.num_dapm_routes	= ARRAY_SIZE(g12a_frddr_dapm_routes),
334	.ops			= &g12a_fifo_pcm_ops
335};
336
337static const struct axg_fifo_match_data sm1_frddr_match_data = {
338	.component_drv	= &sm1_frddr_component_drv,
339	.dai_drv	= &g12a_frddr_dai_drv
340};
341
342static const struct of_device_id axg_frddr_of_match[] = {
343	{
344		.compatible = "amlogic,axg-frddr",
345		.data = &axg_frddr_match_data,
346	}, {
347		.compatible = "amlogic,g12a-frddr",
348		.data = &g12a_frddr_match_data,
349	}, {
350		.compatible = "amlogic,sm1-frddr",
351		.data = &sm1_frddr_match_data,
352	}, {}
353};
354MODULE_DEVICE_TABLE(of, axg_frddr_of_match);
355
356static struct platform_driver axg_frddr_pdrv = {
357	.probe = axg_fifo_probe,
358	.driver = {
359		.name = "axg-frddr",
360		.of_match_table = axg_frddr_of_match,
361	},
362};
363module_platform_driver(axg_frddr_pdrv);
364
365MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver");
366MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
367MODULE_LICENSE("GPL v2");