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