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 */ 24static 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); 66out3: 67 clk_put(sclk_audio0); 68out2: 69 clk_put(mout_epll); 70out1: 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 */ 80static 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 106static 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 */ 139 ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, 140 rclk_rate, SND_SOC_CLOCK_IN); 141 if (ret < 0) 142 return ret; 143 144 return ret; 145} 146 147static struct snd_soc_ops smdk_spdif_ops = { 148 .hw_params = smdk_hw_params, 149}; 150 151static struct snd_soc_dai_link smdk_dai = { 152 .name = "S/PDIF", 153 .stream_name = "S/PDIF PCM Playback", 154 .platform_name = "samsung-spdif", 155 .cpu_dai_name = "samsung-spdif", 156 .codec_dai_name = "dit-hifi", 157 .codec_name = "spdif-dit", 158 .ops = &smdk_spdif_ops, 159}; 160 161static 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 168static struct platform_device *smdk_snd_spdif_dit_device; 169static struct platform_device *smdk_snd_spdif_device; 170 171static 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; 201err4: 202 platform_device_del(smdk_snd_spdif_device); 203err3: 204 platform_device_put(smdk_snd_spdif_device); 205err2: 206 platform_device_del(smdk_snd_spdif_dit_device); 207err1: 208 platform_device_put(smdk_snd_spdif_dit_device); 209 return ret; 210} 211 212static 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 218module_init(smdk_init); 219module_exit(smdk_exit); 220 221MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>"); 222MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF"); 223MODULE_LICENSE("GPL"); 224