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