1208562Srwatson// SPDX-License-Identifier: GPL-2.0+
2208562Srwatson//
3208562Srwatson// smdk_spdif.c - S/PDIF audio for SMDK
4208562Srwatson//
5208562Srwatson// Copyright (C) 2010 Samsung Electronics Co., Ltd.
6208562Srwatson
7208562Srwatson#include <linux/clk.h>
8208562Srwatson#include <linux/module.h>
9208562Srwatson
10208562Srwatson#include <sound/soc.h>
11208562Srwatson
12208562Srwatson#include "spdif.h"
13208562Srwatson
14208562Srwatson/* Audio clock settings are belonged to board specific part. Every
15208562Srwatson * board can set audio source clock setting which is matched with H/W
16208562Srwatson * like this function-'set_audio_clock_heirachy'.
17208562Srwatson */
18208562Srwatsonstatic int set_audio_clock_heirachy(struct platform_device *pdev)
19208562Srwatson{
20208562Srwatson	struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
21208562Srwatson	int ret = 0;
22208562Srwatson
23208562Srwatson	fout_epll = clk_get(NULL, "fout_epll");
24208562Srwatson	if (IS_ERR(fout_epll)) {
25208562Srwatson		printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
26208562Srwatson				__func__);
27208562Srwatson		return -EINVAL;
28208562Srwatson	}
29208562Srwatson
30208562Srwatson	mout_epll = clk_get(NULL, "mout_epll");
31208562Srwatson	if (IS_ERR(mout_epll)) {
32208562Srwatson		printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
33208562Srwatson				__func__);
34208562Srwatson		ret = -EINVAL;
35208562Srwatson		goto out1;
36208562Srwatson	}
37208562Srwatson
38208562Srwatson	sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
39208562Srwatson	if (IS_ERR(sclk_audio0)) {
40208562Srwatson		printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
41208562Srwatson				__func__);
42208562Srwatson		ret = -EINVAL;
43208562Srwatson		goto out2;
44208562Srwatson	}
45208562Srwatson
46208562Srwatson	sclk_spdif = clk_get(NULL, "sclk_spdif");
47208562Srwatson	if (IS_ERR(sclk_spdif)) {
48208562Srwatson		printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
49208562Srwatson				__func__);
50208562Srwatson		ret = -EINVAL;
51208562Srwatson		goto out3;
52208562Srwatson	}
53208562Srwatson
54208562Srwatson	/* Set audio clock hierarchy for S/PDIF */
55208562Srwatson	clk_set_parent(mout_epll, fout_epll);
56208562Srwatson	clk_set_parent(sclk_audio0, mout_epll);
57281400Sngie	clk_set_parent(sclk_spdif, sclk_audio0);
58281400Sngie
59208562Srwatson	clk_put(sclk_spdif);
60208562Srwatsonout3:
61208562Srwatson	clk_put(sclk_audio0);
62208562Srwatsonout2:
63281400Sngie	clk_put(mout_epll);
64208562Srwatsonout1:
65208562Srwatson	clk_put(fout_epll);
66208562Srwatson
67208562Srwatson	return ret;
68208562Srwatson}
69208562Srwatson
70208562Srwatson/* We should haved to set clock directly on this part because of clock
71208562Srwatson * scheme of Samsudng SoCs did not support to set rates from abstrct
72208562Srwatson * clock of it's hierarchy.
73208562Srwatson */
74208562Srwatsonstatic int set_audio_clock_rate(unsigned long epll_rate,
75208562Srwatson				unsigned long audio_rate)
76208562Srwatson{
77208562Srwatson	struct clk *fout_epll, *sclk_spdif;
78281400Sngie
79281400Sngie	fout_epll = clk_get(NULL, "fout_epll");
80281400Sngie	if (IS_ERR(fout_epll)) {
81281400Sngie		printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
82208562Srwatson		return -ENOENT;
83208602Srwatson	}
84208602Srwatson
85208562Srwatson	clk_set_rate(fout_epll, epll_rate);
86208562Srwatson	clk_put(fout_epll);
87208562Srwatson
88281400Sngie	sclk_spdif = clk_get(NULL, "sclk_spdif");
89208562Srwatson	if (IS_ERR(sclk_spdif)) {
90208562Srwatson		printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
91281400Sngie		return -ENOENT;
92208562Srwatson	}
93208562Srwatson
94208562Srwatson	clk_set_rate(sclk_spdif, audio_rate);
95208562Srwatson	clk_put(sclk_spdif);
96208562Srwatson
97208562Srwatson	return 0;
98208562Srwatson}
99208562Srwatson
100208562Srwatsonstatic int smdk_hw_params(struct snd_pcm_substream *substream,
101208562Srwatson		struct snd_pcm_hw_params *params)
102208562Srwatson{
103208562Srwatson	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
104208562Srwatson	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
105208562Srwatson	unsigned long pll_out, rclk_rate;
106208562Srwatson	int ret, ratio;
107208562Srwatson
108208562Srwatson	switch (params_rate(params)) {
109208562Srwatson	case 44100:
110281400Sngie		pll_out = 45158400;
111208562Srwatson		break;
112208602Srwatson	case 32000:
113208602Srwatson	case 48000:
114208602Srwatson	case 96000:
115208602Srwatson		pll_out = 49152000;
116208562Srwatson		break;
117208602Srwatson	default:
118208602Srwatson		return -EINVAL;
119208602Srwatson	}
120208602Srwatson
121208602Srwatson	/* Setting ratio to 512fs helps to use S/PDIF with HDMI without
122208602Srwatson	 * modify S/PDIF ASoC machine driver.
123208602Srwatson	 */
124208602Srwatson	ratio = 512;
125208562Srwatson	rclk_rate = params_rate(params) * ratio;
126208562Srwatson
127208562Srwatson	/* Set audio source clock rates */
128208562Srwatson	ret = set_audio_clock_rate(pll_out, rclk_rate);
129208562Srwatson	if (ret < 0)
130208562Srwatson		return ret;
131208562Srwatson
132208562Srwatson	/* Set S/PDIF uses internal source clock */
133208562Srwatson	ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
134208562Srwatson					rclk_rate, SND_SOC_CLOCK_IN);
135208562Srwatson	if (ret < 0)
136208602Srwatson		return ret;
137208562Srwatson
138208602Srwatson	return ret;
139208562Srwatson}
140208562Srwatson
141208562Srwatsonstatic const struct snd_soc_ops smdk_spdif_ops = {
142208562Srwatson	.hw_params = smdk_hw_params,
143208562Srwatson};
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