Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2//
  3// Copyright (c) 2020 BayLibre, SAS.
  4// Author: Jerome Brunet <jbrunet@baylibre.com>
  5
  6#include <linux/bitfield.h>
  7#include <linux/clk.h>
  8#include <linux/module.h>
  9#include <linux/of_platform.h>
 10#include <linux/regmap.h>
 11#include <linux/reset.h>
 12#include <sound/soc.h>
 13#include <sound/soc-dai.h>
 14
 15#include <dt-bindings/sound/meson-aiu.h>
 16#include "aiu.h"
 17#include "aiu-fifo.h"
 18
 19#define AIU_I2S_MISC_958_SRC_SHIFT 3
 20
 21static const char * const aiu_spdif_encode_sel_texts[] = {
 22	"SPDIF", "I2S",
 23};
 24
 25static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC,
 26			    AIU_I2S_MISC_958_SRC_SHIFT,
 27			    aiu_spdif_encode_sel_texts);
 28
 29static const struct snd_kcontrol_new aiu_spdif_encode_mux =
 30	SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum);
 31
 32static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = {
 33	SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0,
 34			 &aiu_spdif_encode_mux),
 35};
 36
 37static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = {
 38	{ "I2S Encoder Playback", NULL, "I2S FIFO Playback" },
 39	{ "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" },
 40	{ "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" },
 41	{ "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" },
 42};
 43
 44int aiu_of_xlate_dai_name(struct snd_soc_component *component,
 45			  const struct of_phandle_args *args,
 46			  const char **dai_name,
 47			  unsigned int component_id)
 48{
 49	struct snd_soc_dai *dai;
 50	int id;
 51
 52	if (args->args_count != 2)
 53		return -EINVAL;
 54
 55	if (args->args[0] != component_id)
 56		return -EINVAL;
 57
 58	id = args->args[1];
 59
 60	if (id < 0 || id >= component->num_dai)
 61		return -EINVAL;
 62
 63	for_each_component_dais(component, dai) {
 64		if (id == 0)
 65			break;
 66		id--;
 67	}
 68
 69	*dai_name = dai->driver->name;
 70
 71	return 0;
 72}
 73
 74static int aiu_cpu_of_xlate_dai_name(struct snd_soc_component *component,
 75				     const struct of_phandle_args *args,
 76				     const char **dai_name)
 77{
 78	return aiu_of_xlate_dai_name(component, args, dai_name, AIU_CPU);
 79}
 80
 81static int aiu_cpu_component_probe(struct snd_soc_component *component)
 82{
 83	struct aiu *aiu = snd_soc_component_get_drvdata(component);
 84
 85	/* Required for the SPDIF Source control operation */
 86	return clk_prepare_enable(aiu->i2s.clks[PCLK].clk);
 87}
 88
 89static void aiu_cpu_component_remove(struct snd_soc_component *component)
 90{
 91	struct aiu *aiu = snd_soc_component_get_drvdata(component);
 92
 93	clk_disable_unprepare(aiu->i2s.clks[PCLK].clk);
 94}
 95
 96static const struct snd_soc_component_driver aiu_cpu_component = {
 97	.name			= "AIU CPU",
 98	.dapm_widgets		= aiu_cpu_dapm_widgets,
 99	.num_dapm_widgets	= ARRAY_SIZE(aiu_cpu_dapm_widgets),
100	.dapm_routes		= aiu_cpu_dapm_routes,
101	.num_dapm_routes	= ARRAY_SIZE(aiu_cpu_dapm_routes),
102	.of_xlate_dai_name	= aiu_cpu_of_xlate_dai_name,
103	.pointer		= aiu_fifo_pointer,
104	.probe			= aiu_cpu_component_probe,
105	.remove			= aiu_cpu_component_remove,
106#ifdef CONFIG_DEBUG_FS
107	.debugfs_prefix		= "cpu",
108#endif
109};
110
111static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = {
112	[CPU_I2S_FIFO] = {
113		.name = "I2S FIFO",
114		.playback = {
115			.stream_name	= "I2S FIFO Playback",
116			.channels_min	= 2,
117			.channels_max	= 8,
118			.rates		= SNDRV_PCM_RATE_CONTINUOUS,
119			.rate_min	= 5512,
120			.rate_max	= 192000,
121			.formats	= AIU_FORMATS,
122		},
123		.ops		= &aiu_fifo_i2s_dai_ops,
124		.pcm_new	= aiu_fifo_pcm_new,
125		.probe		= aiu_fifo_i2s_dai_probe,
126		.remove		= aiu_fifo_dai_remove,
127	},
128	[CPU_SPDIF_FIFO] = {
129		.name = "SPDIF FIFO",
130		.playback = {
131			.stream_name	= "SPDIF FIFO Playback",
132			.channels_min	= 2,
133			.channels_max	= 2,
134			.rates		= SNDRV_PCM_RATE_CONTINUOUS,
135			.rate_min	= 5512,
136			.rate_max	= 192000,
137			.formats	= AIU_FORMATS,
138		},
139		.ops		= &aiu_fifo_spdif_dai_ops,
140		.pcm_new	= aiu_fifo_pcm_new,
141		.probe		= aiu_fifo_spdif_dai_probe,
142		.remove		= aiu_fifo_dai_remove,
143	},
144	[CPU_I2S_ENCODER] = {
145		.name = "I2S Encoder",
146		.playback = {
147			.stream_name = "I2S Encoder Playback",
148			.channels_min = 2,
149			.channels_max = 8,
150			.rates = SNDRV_PCM_RATE_8000_192000,
151			.formats = AIU_FORMATS,
152		},
153		.ops = &aiu_encoder_i2s_dai_ops,
154	},
155	[CPU_SPDIF_ENCODER] = {
156		.name = "SPDIF Encoder",
157		.playback = {
158			.stream_name = "SPDIF Encoder Playback",
159			.channels_min = 2,
160			.channels_max = 2,
161			.rates = (SNDRV_PCM_RATE_32000  |
162				  SNDRV_PCM_RATE_44100  |
163				  SNDRV_PCM_RATE_48000  |
164				  SNDRV_PCM_RATE_88200  |
165				  SNDRV_PCM_RATE_96000  |
166				  SNDRV_PCM_RATE_176400 |
167				  SNDRV_PCM_RATE_192000),
168			.formats = AIU_FORMATS,
169		},
170		.ops = &aiu_encoder_spdif_dai_ops,
171	}
172};
173
174static const struct regmap_config aiu_regmap_cfg = {
175	.reg_bits	= 32,
176	.val_bits	= 32,
177	.reg_stride	= 4,
178	.max_register	= 0x2ac,
179};
180
181static int aiu_clk_bulk_get(struct device *dev,
182			    const char * const *ids,
183			    unsigned int num,
184			    struct aiu_interface *interface)
185{
186	struct clk_bulk_data *clks;
187	int i, ret;
188
189	clks = devm_kcalloc(dev, num, sizeof(*clks), GFP_KERNEL);
190	if (!clks)
191		return -ENOMEM;
192
193	for (i = 0; i < num; i++)
194		clks[i].id = ids[i];
195
196	ret = devm_clk_bulk_get(dev, num, clks);
197	if (ret < 0)
198		return ret;
199
200	interface->clks = clks;
201	interface->clk_num = num;
202	return 0;
203}
204
205static const char * const aiu_i2s_ids[] = {
206	[PCLK]	= "i2s_pclk",
207	[AOCLK]	= "i2s_aoclk",
208	[MCLK]	= "i2s_mclk",
209	[MIXER]	= "i2s_mixer",
210};
211
212static const char * const aiu_spdif_ids[] = {
213	[PCLK]	= "spdif_pclk",
214	[AOCLK]	= "spdif_aoclk",
215	[MCLK]	= "spdif_mclk_sel"
216};
217
218static int aiu_clk_get(struct device *dev)
219{
220	struct aiu *aiu = dev_get_drvdata(dev);
221	int ret;
222
223	aiu->pclk = devm_clk_get(dev, "pclk");
224	if (IS_ERR(aiu->pclk))
225		return dev_err_probe(dev, PTR_ERR(aiu->pclk), "Can't get the aiu pclk\n");
226
227	aiu->spdif_mclk = devm_clk_get(dev, "spdif_mclk");
228	if (IS_ERR(aiu->spdif_mclk))
229		return dev_err_probe(dev, PTR_ERR(aiu->spdif_mclk),
230				     "Can't get the aiu spdif master clock\n");
231
232	ret = aiu_clk_bulk_get(dev, aiu_i2s_ids, ARRAY_SIZE(aiu_i2s_ids),
233			       &aiu->i2s);
234	if (ret)
235		return dev_err_probe(dev, ret, "Can't get the i2s clocks\n");
236
237	ret = aiu_clk_bulk_get(dev, aiu_spdif_ids, ARRAY_SIZE(aiu_spdif_ids),
238			       &aiu->spdif);
239	if (ret)
240		return dev_err_probe(dev, ret, "Can't get the spdif clocks\n");
241
242	ret = clk_prepare_enable(aiu->pclk);
243	if (ret) {
244		dev_err(dev, "peripheral clock enable failed\n");
245		return ret;
246	}
247
248	ret = devm_add_action_or_reset(dev,
249				       (void(*)(void *))clk_disable_unprepare,
250				       aiu->pclk);
251	if (ret)
252		dev_err(dev, "failed to add reset action on pclk");
253
254	return ret;
255}
256
257static int aiu_probe(struct platform_device *pdev)
258{
259	struct device *dev = &pdev->dev;
260	void __iomem *regs;
261	struct regmap *map;
262	struct aiu *aiu;
263	int ret;
264
265	aiu = devm_kzalloc(dev, sizeof(*aiu), GFP_KERNEL);
266	if (!aiu)
267		return -ENOMEM;
268
269	aiu->platform = device_get_match_data(dev);
270	if (!aiu->platform)
271		return -ENODEV;
272
273	platform_set_drvdata(pdev, aiu);
274
275	ret = device_reset(dev);
276	if (ret)
277		return dev_err_probe(dev, ret, "Failed to reset device\n");
278
279	regs = devm_platform_ioremap_resource(pdev, 0);
280	if (IS_ERR(regs))
281		return PTR_ERR(regs);
282
283	map = devm_regmap_init_mmio(dev, regs, &aiu_regmap_cfg);
284	if (IS_ERR(map)) {
285		dev_err(dev, "failed to init regmap: %ld\n",
286			PTR_ERR(map));
287		return PTR_ERR(map);
288	}
289
290	aiu->i2s.irq = platform_get_irq_byname(pdev, "i2s");
291	if (aiu->i2s.irq < 0)
292		return aiu->i2s.irq;
293
294	aiu->spdif.irq = platform_get_irq_byname(pdev, "spdif");
295	if (aiu->spdif.irq < 0)
296		return aiu->spdif.irq;
297
298	ret = aiu_clk_get(dev);
299	if (ret)
300		return ret;
301
302	/* Register the cpu component of the aiu */
303	ret = snd_soc_register_component(dev, &aiu_cpu_component,
304					 aiu_cpu_dai_drv,
305					 ARRAY_SIZE(aiu_cpu_dai_drv));
306	if (ret) {
307		dev_err(dev, "Failed to register cpu component\n");
308		return ret;
309	}
310
311	/* Register the hdmi codec control component */
312	ret = aiu_hdmi_ctrl_register_component(dev);
313	if (ret) {
314		dev_err(dev, "Failed to register hdmi control component\n");
315		goto err;
316	}
317
318	/* Register the internal dac control component on gxl */
319	if (aiu->platform->has_acodec) {
320		ret = aiu_acodec_ctrl_register_component(dev);
321		if (ret) {
322			dev_err(dev,
323			    "Failed to register acodec control component\n");
324			goto err;
325		}
326	}
327
328	return 0;
329err:
330	snd_soc_unregister_component(dev);
331	return ret;
332}
333
334static int aiu_remove(struct platform_device *pdev)
335{
336	snd_soc_unregister_component(&pdev->dev);
337
338	return 0;
339}
340
341static const struct aiu_platform_data aiu_gxbb_pdata = {
342	.has_acodec = false,
343	.has_clk_ctrl_more_i2s_div = true,
344};
345
346static const struct aiu_platform_data aiu_gxl_pdata = {
347	.has_acodec = true,
348	.has_clk_ctrl_more_i2s_div = true,
349};
350
351static const struct aiu_platform_data aiu_meson8_pdata = {
352	.has_acodec = false,
353	.has_clk_ctrl_more_i2s_div = false,
354};
355
356static const struct of_device_id aiu_of_match[] = {
357	{ .compatible = "amlogic,aiu-gxbb", .data = &aiu_gxbb_pdata },
358	{ .compatible = "amlogic,aiu-gxl", .data = &aiu_gxl_pdata },
359	{ .compatible = "amlogic,aiu-meson8", .data = &aiu_meson8_pdata },
360	{ .compatible = "amlogic,aiu-meson8b", .data = &aiu_meson8_pdata },
361	{}
362};
363MODULE_DEVICE_TABLE(of, aiu_of_match);
364
365static struct platform_driver aiu_pdrv = {
366	.probe = aiu_probe,
367	.remove = aiu_remove,
368	.driver = {
369		.name = "meson-aiu",
370		.of_match_table = aiu_of_match,
371	},
372};
373module_platform_driver(aiu_pdrv);
374
375MODULE_DESCRIPTION("Meson AIU Driver");
376MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
377MODULE_LICENSE("GPL v2");