1// SPDX-License-Identifier: GPL-2.0+
2//
3// smdk_spdif.c - S/PDIF audio for SMDK
4//
5// Copyright (C) 2010 Samsung Electronics Co., Ltd.
6
7#include <linux/clk.h>
8#include <linux/module.h>
9
10#include <sound/soc.h>
11
12#include "spdif.h"
13
14/* Audio clock settings are belonged to board specific part. Every
15 * board can set audio source clock setting which is matched with H/W
16 * like this function-'set_audio_clock_heirachy'.
17 */
18static int set_audio_clock_heirachy(struct platform_device *pdev)
19{
20	struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
21	int ret = 0;
22
23	fout_epll = clk_get(NULL, "fout_epll");
24	if (IS_ERR(fout_epll)) {
25		printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
26				__func__);
27		return -EINVAL;
28	}
29
30	mout_epll = clk_get(NULL, "mout_epll");
31	if (IS_ERR(mout_epll)) {
32		printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
33				__func__);
34		ret = -EINVAL;
35		goto out1;
36	}
37
38	sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
39	if (IS_ERR(sclk_audio0)) {
40		printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
41				__func__);
42		ret = -EINVAL;
43		goto out2;
44	}
45
46	sclk_spdif = clk_get(NULL, "sclk_spdif");
47	if (IS_ERR(sclk_spdif)) {
48		printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
49				__func__);
50		ret = -EINVAL;
51		goto out3;
52	}
53
54	/* Set audio clock hierarchy for S/PDIF */
55	clk_set_parent(mout_epll, fout_epll);
56	clk_set_parent(sclk_audio0, mout_epll);
57	clk_set_parent(sclk_spdif, sclk_audio0);
58
59	clk_put(sclk_spdif);
60out3:
61	clk_put(sclk_audio0);
62out2:
63	clk_put(mout_epll);
64out1:
65	clk_put(fout_epll);
66
67	return ret;
68}
69
70/* We should haved to set clock directly on this part because of clock
71 * scheme of Samsudng SoCs did not support to set rates from abstrct
72 * clock of it's hierarchy.
73 */
74static int set_audio_clock_rate(unsigned long epll_rate,
75				unsigned long audio_rate)
76{
77	struct clk *fout_epll, *sclk_spdif;
78
79	fout_epll = clk_get(NULL, "fout_epll");
80	if (IS_ERR(fout_epll)) {
81		printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
82		return -ENOENT;
83	}
84
85	clk_set_rate(fout_epll, epll_rate);
86	clk_put(fout_epll);
87
88	sclk_spdif = clk_get(NULL, "sclk_spdif");
89	if (IS_ERR(sclk_spdif)) {
90		printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
91		return -ENOENT;
92	}
93
94	clk_set_rate(sclk_spdif, audio_rate);
95	clk_put(sclk_spdif);
96
97	return 0;
98}
99
100static int smdk_hw_params(struct snd_pcm_substream *substream,
101		struct snd_pcm_hw_params *params)
102{
103	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
104	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
105	unsigned long pll_out, rclk_rate;
106	int ret, ratio;
107
108	switch (params_rate(params)) {
109	case 44100:
110		pll_out = 45158400;
111		break;
112	case 32000:
113	case 48000:
114	case 96000:
115		pll_out = 49152000;
116		break;
117	default:
118		return -EINVAL;
119	}
120
121	/* Setting ratio to 512fs helps to use S/PDIF with HDMI without
122	 * modify S/PDIF ASoC machine driver.
123	 */
124	ratio = 512;
125	rclk_rate = params_rate(params) * ratio;
126
127	/* Set audio source clock rates */
128	ret = set_audio_clock_rate(pll_out, rclk_rate);
129	if (ret < 0)
130		return ret;
131
132	/* Set S/PDIF uses internal source clock */
133	ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
134					rclk_rate, SND_SOC_CLOCK_IN);
135	if (ret < 0)
136		return ret;
137
138	return ret;
139}
140
141static const struct snd_soc_ops smdk_spdif_ops = {
142	.hw_params = smdk_hw_params,
143};
144
145SND_SOC_DAILINK_DEFS(spdif,
146	DAILINK_COMP_ARRAY(COMP_CPU("samsung-spdif")),
147	DAILINK_COMP_ARRAY(COMP_CODEC("spdif-dit", "dit-hifi")),
148	DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-spdif")));
149
150static struct snd_soc_dai_link smdk_dai = {
151	.name = "S/PDIF",
152	.stream_name = "S/PDIF PCM Playback",
153	.ops = &smdk_spdif_ops,
154	SND_SOC_DAILINK_REG(spdif),
155};
156
157static struct snd_soc_card smdk = {
158	.name = "SMDK-S/PDIF",
159	.owner = THIS_MODULE,
160	.dai_link = &smdk_dai,
161	.num_links = 1,
162};
163
164static struct platform_device *smdk_snd_spdif_dit_device;
165static struct platform_device *smdk_snd_spdif_device;
166
167static int __init smdk_init(void)
168{
169	int ret;
170
171	smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
172	if (!smdk_snd_spdif_dit_device)
173		return -ENOMEM;
174
175	ret = platform_device_add(smdk_snd_spdif_dit_device);
176	if (ret)
177		goto err1;
178
179	smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
180	if (!smdk_snd_spdif_device) {
181		ret = -ENOMEM;
182		goto err2;
183	}
184
185	platform_set_drvdata(smdk_snd_spdif_device, &smdk);
186
187	ret = platform_device_add(smdk_snd_spdif_device);
188	if (ret)
189		goto err3;
190
191	/* Set audio clock hierarchy manually */
192	ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
193	if (ret)
194		goto err4;
195
196	return 0;
197err4:
198	platform_device_del(smdk_snd_spdif_device);
199err3:
200	platform_device_put(smdk_snd_spdif_device);
201err2:
202	platform_device_del(smdk_snd_spdif_dit_device);
203err1:
204	platform_device_put(smdk_snd_spdif_dit_device);
205	return ret;
206}
207
208static void __exit smdk_exit(void)
209{
210	platform_device_unregister(smdk_snd_spdif_device);
211	platform_device_unregister(smdk_snd_spdif_dit_device);
212}
213
214module_init(smdk_init);
215module_exit(smdk_exit);
216
217MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
218MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
219MODULE_LICENSE("GPL");
220