162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// smdk_spdif.c - S/PDIF audio for SMDK 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Copyright (C) 2010 Samsung Electronics Co., Ltd. 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/clk.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <sound/soc.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "spdif.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/* Audio clock settings are belonged to board specific part. Every 1562306a36Sopenharmony_ci * board can set audio source clock setting which is matched with H/W 1662306a36Sopenharmony_ci * like this function-'set_audio_clock_heirachy'. 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_cistatic int set_audio_clock_heirachy(struct platform_device *pdev) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif; 2162306a36Sopenharmony_ci int ret = 0; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci fout_epll = clk_get(NULL, "fout_epll"); 2462306a36Sopenharmony_ci if (IS_ERR(fout_epll)) { 2562306a36Sopenharmony_ci printk(KERN_WARNING "%s: Cannot find fout_epll.\n", 2662306a36Sopenharmony_ci __func__); 2762306a36Sopenharmony_ci return -EINVAL; 2862306a36Sopenharmony_ci } 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci mout_epll = clk_get(NULL, "mout_epll"); 3162306a36Sopenharmony_ci if (IS_ERR(mout_epll)) { 3262306a36Sopenharmony_ci printk(KERN_WARNING "%s: Cannot find mout_epll.\n", 3362306a36Sopenharmony_ci __func__); 3462306a36Sopenharmony_ci ret = -EINVAL; 3562306a36Sopenharmony_ci goto out1; 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci sclk_audio0 = clk_get(&pdev->dev, "sclk_audio"); 3962306a36Sopenharmony_ci if (IS_ERR(sclk_audio0)) { 4062306a36Sopenharmony_ci printk(KERN_WARNING "%s: Cannot find sclk_audio.\n", 4162306a36Sopenharmony_ci __func__); 4262306a36Sopenharmony_ci ret = -EINVAL; 4362306a36Sopenharmony_ci goto out2; 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci sclk_spdif = clk_get(NULL, "sclk_spdif"); 4762306a36Sopenharmony_ci if (IS_ERR(sclk_spdif)) { 4862306a36Sopenharmony_ci printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n", 4962306a36Sopenharmony_ci __func__); 5062306a36Sopenharmony_ci ret = -EINVAL; 5162306a36Sopenharmony_ci goto out3; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci /* Set audio clock hierarchy for S/PDIF */ 5562306a36Sopenharmony_ci clk_set_parent(mout_epll, fout_epll); 5662306a36Sopenharmony_ci clk_set_parent(sclk_audio0, mout_epll); 5762306a36Sopenharmony_ci clk_set_parent(sclk_spdif, sclk_audio0); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci clk_put(sclk_spdif); 6062306a36Sopenharmony_ciout3: 6162306a36Sopenharmony_ci clk_put(sclk_audio0); 6262306a36Sopenharmony_ciout2: 6362306a36Sopenharmony_ci clk_put(mout_epll); 6462306a36Sopenharmony_ciout1: 6562306a36Sopenharmony_ci clk_put(fout_epll); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci return ret; 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/* We should haved to set clock directly on this part because of clock 7162306a36Sopenharmony_ci * scheme of Samsudng SoCs did not support to set rates from abstrct 7262306a36Sopenharmony_ci * clock of it's hierarchy. 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_cistatic int set_audio_clock_rate(unsigned long epll_rate, 7562306a36Sopenharmony_ci unsigned long audio_rate) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct clk *fout_epll, *sclk_spdif; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci fout_epll = clk_get(NULL, "fout_epll"); 8062306a36Sopenharmony_ci if (IS_ERR(fout_epll)) { 8162306a36Sopenharmony_ci printk(KERN_ERR "%s: failed to get fout_epll\n", __func__); 8262306a36Sopenharmony_ci return -ENOENT; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci clk_set_rate(fout_epll, epll_rate); 8662306a36Sopenharmony_ci clk_put(fout_epll); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci sclk_spdif = clk_get(NULL, "sclk_spdif"); 8962306a36Sopenharmony_ci if (IS_ERR(sclk_spdif)) { 9062306a36Sopenharmony_ci printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__); 9162306a36Sopenharmony_ci return -ENOENT; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci clk_set_rate(sclk_spdif, audio_rate); 9562306a36Sopenharmony_ci clk_put(sclk_spdif); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return 0; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int smdk_hw_params(struct snd_pcm_substream *substream, 10162306a36Sopenharmony_ci struct snd_pcm_hw_params *params) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 10462306a36Sopenharmony_ci struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); 10562306a36Sopenharmony_ci unsigned long pll_out, rclk_rate; 10662306a36Sopenharmony_ci int ret, ratio; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci switch (params_rate(params)) { 10962306a36Sopenharmony_ci case 44100: 11062306a36Sopenharmony_ci pll_out = 45158400; 11162306a36Sopenharmony_ci break; 11262306a36Sopenharmony_ci case 32000: 11362306a36Sopenharmony_ci case 48000: 11462306a36Sopenharmony_ci case 96000: 11562306a36Sopenharmony_ci pll_out = 49152000; 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci default: 11862306a36Sopenharmony_ci return -EINVAL; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* Setting ratio to 512fs helps to use S/PDIF with HDMI without 12262306a36Sopenharmony_ci * modify S/PDIF ASoC machine driver. 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci ratio = 512; 12562306a36Sopenharmony_ci rclk_rate = params_rate(params) * ratio; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Set audio source clock rates */ 12862306a36Sopenharmony_ci ret = set_audio_clock_rate(pll_out, rclk_rate); 12962306a36Sopenharmony_ci if (ret < 0) 13062306a36Sopenharmony_ci return ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Set S/PDIF uses internal source clock */ 13362306a36Sopenharmony_ci ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, 13462306a36Sopenharmony_ci rclk_rate, SND_SOC_CLOCK_IN); 13562306a36Sopenharmony_ci if (ret < 0) 13662306a36Sopenharmony_ci return ret; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic const struct snd_soc_ops smdk_spdif_ops = { 14262306a36Sopenharmony_ci .hw_params = smdk_hw_params, 14362306a36Sopenharmony_ci}; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ciSND_SOC_DAILINK_DEFS(spdif, 14662306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CPU("samsung-spdif")), 14762306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_CODEC("spdif-dit", "dit-hifi")), 14862306a36Sopenharmony_ci DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-spdif"))); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic struct snd_soc_dai_link smdk_dai = { 15162306a36Sopenharmony_ci .name = "S/PDIF", 15262306a36Sopenharmony_ci .stream_name = "S/PDIF PCM Playback", 15362306a36Sopenharmony_ci .ops = &smdk_spdif_ops, 15462306a36Sopenharmony_ci SND_SOC_DAILINK_REG(spdif), 15562306a36Sopenharmony_ci}; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic struct snd_soc_card smdk = { 15862306a36Sopenharmony_ci .name = "SMDK-S/PDIF", 15962306a36Sopenharmony_ci .owner = THIS_MODULE, 16062306a36Sopenharmony_ci .dai_link = &smdk_dai, 16162306a36Sopenharmony_ci .num_links = 1, 16262306a36Sopenharmony_ci}; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic struct platform_device *smdk_snd_spdif_dit_device; 16562306a36Sopenharmony_cistatic struct platform_device *smdk_snd_spdif_device; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic int __init smdk_init(void) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci int ret; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1); 17262306a36Sopenharmony_ci if (!smdk_snd_spdif_dit_device) 17362306a36Sopenharmony_ci return -ENOMEM; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci ret = platform_device_add(smdk_snd_spdif_dit_device); 17662306a36Sopenharmony_ci if (ret) 17762306a36Sopenharmony_ci goto err1; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1); 18062306a36Sopenharmony_ci if (!smdk_snd_spdif_device) { 18162306a36Sopenharmony_ci ret = -ENOMEM; 18262306a36Sopenharmony_ci goto err2; 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci platform_set_drvdata(smdk_snd_spdif_device, &smdk); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ret = platform_device_add(smdk_snd_spdif_device); 18862306a36Sopenharmony_ci if (ret) 18962306a36Sopenharmony_ci goto err3; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* Set audio clock hierarchy manually */ 19262306a36Sopenharmony_ci ret = set_audio_clock_heirachy(smdk_snd_spdif_device); 19362306a36Sopenharmony_ci if (ret) 19462306a36Sopenharmony_ci goto err4; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_cierr4: 19862306a36Sopenharmony_ci platform_device_del(smdk_snd_spdif_device); 19962306a36Sopenharmony_cierr3: 20062306a36Sopenharmony_ci platform_device_put(smdk_snd_spdif_device); 20162306a36Sopenharmony_cierr2: 20262306a36Sopenharmony_ci platform_device_del(smdk_snd_spdif_dit_device); 20362306a36Sopenharmony_cierr1: 20462306a36Sopenharmony_ci platform_device_put(smdk_snd_spdif_dit_device); 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic void __exit smdk_exit(void) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci platform_device_unregister(smdk_snd_spdif_device); 21162306a36Sopenharmony_ci platform_device_unregister(smdk_snd_spdif_dit_device); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cimodule_init(smdk_init); 21562306a36Sopenharmony_cimodule_exit(smdk_exit); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ciMODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>"); 21862306a36Sopenharmony_ciMODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF"); 21962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 220