1aba611fcSSylwester Nawrocki /* 2aba611fcSSylwester Nawrocki * Copyright (C) 2017 Samsung Electronics Co., Ltd. 3aba611fcSSylwester Nawrocki * 4aba611fcSSylwester Nawrocki * This program is free software; you can redistribute it and/or modify 5aba611fcSSylwester Nawrocki * it under the terms of the GNU General Public License version 2 as 6aba611fcSSylwester Nawrocki * published by the Free Software Foundation. 7aba611fcSSylwester Nawrocki */ 8aba611fcSSylwester Nawrocki 9aba611fcSSylwester Nawrocki #include <linux/clk.h> 10aba611fcSSylwester Nawrocki #include <linux/of.h> 11aba611fcSSylwester Nawrocki #include <linux/of_device.h> 12aba611fcSSylwester Nawrocki #include <linux/module.h> 13aba611fcSSylwester Nawrocki #include <sound/soc.h> 14aba611fcSSylwester Nawrocki #include <sound/pcm_params.h> 15aba611fcSSylwester Nawrocki #include "i2s.h" 16aba611fcSSylwester Nawrocki #include "i2s-regs.h" 17aba611fcSSylwester Nawrocki 18aba611fcSSylwester Nawrocki struct odroid_priv { 19aba611fcSSylwester Nawrocki struct snd_soc_card card; 20aba611fcSSylwester Nawrocki struct snd_soc_dai_link dai_link; 21aba611fcSSylwester Nawrocki 22aba611fcSSylwester Nawrocki struct clk *pll; 23aba611fcSSylwester Nawrocki struct clk *rclk; 24aba611fcSSylwester Nawrocki }; 25aba611fcSSylwester Nawrocki 26aba611fcSSylwester Nawrocki static int odroid_card_startup(struct snd_pcm_substream *substream) 27aba611fcSSylwester Nawrocki { 28aba611fcSSylwester Nawrocki struct snd_pcm_runtime *runtime = substream->runtime; 29aba611fcSSylwester Nawrocki 30aba611fcSSylwester Nawrocki snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); 31aba611fcSSylwester Nawrocki return 0; 32aba611fcSSylwester Nawrocki } 33aba611fcSSylwester Nawrocki 34aba611fcSSylwester Nawrocki static int odroid_card_hw_params(struct snd_pcm_substream *substream, 35aba611fcSSylwester Nawrocki struct snd_pcm_hw_params *params) 36aba611fcSSylwester Nawrocki { 37aba611fcSSylwester Nawrocki struct snd_soc_pcm_runtime *rtd = substream->private_data; 38aba611fcSSylwester Nawrocki struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card); 39aba611fcSSylwester Nawrocki unsigned int pll_freq, rclk_freq; 40aba611fcSSylwester Nawrocki int ret; 41aba611fcSSylwester Nawrocki 42aba611fcSSylwester Nawrocki switch (params_rate(params)) { 43aba611fcSSylwester Nawrocki case 32000: 44aba611fcSSylwester Nawrocki case 64000: 45aba611fcSSylwester Nawrocki pll_freq = 131072000U; 46aba611fcSSylwester Nawrocki break; 47aba611fcSSylwester Nawrocki case 44100: 48aba611fcSSylwester Nawrocki case 88200: 49aba611fcSSylwester Nawrocki case 176400: 50aba611fcSSylwester Nawrocki pll_freq = 180633600U; 51aba611fcSSylwester Nawrocki break; 52aba611fcSSylwester Nawrocki case 48000: 53aba611fcSSylwester Nawrocki case 96000: 54aba611fcSSylwester Nawrocki case 192000: 55aba611fcSSylwester Nawrocki pll_freq = 196608000U; 56aba611fcSSylwester Nawrocki break; 57aba611fcSSylwester Nawrocki default: 58aba611fcSSylwester Nawrocki return -EINVAL; 59aba611fcSSylwester Nawrocki } 60aba611fcSSylwester Nawrocki 61aba611fcSSylwester Nawrocki ret = clk_set_rate(priv->pll, pll_freq + 1); 62aba611fcSSylwester Nawrocki if (ret < 0) 63aba611fcSSylwester Nawrocki return ret; 64aba611fcSSylwester Nawrocki 65aba611fcSSylwester Nawrocki rclk_freq = params_rate(params) * 256 * 4; 66aba611fcSSylwester Nawrocki 67aba611fcSSylwester Nawrocki ret = clk_set_rate(priv->rclk, rclk_freq); 68aba611fcSSylwester Nawrocki if (ret < 0) 69aba611fcSSylwester Nawrocki return ret; 70aba611fcSSylwester Nawrocki 71aba611fcSSylwester Nawrocki if (rtd->num_codecs > 1) { 72aba611fcSSylwester Nawrocki struct snd_soc_dai *codec_dai = rtd->codec_dais[1]; 73aba611fcSSylwester Nawrocki 74aba611fcSSylwester Nawrocki ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq, 75aba611fcSSylwester Nawrocki SND_SOC_CLOCK_IN); 76aba611fcSSylwester Nawrocki if (ret < 0) 77aba611fcSSylwester Nawrocki return ret; 78aba611fcSSylwester Nawrocki } 79aba611fcSSylwester Nawrocki 80aba611fcSSylwester Nawrocki return 0; 81aba611fcSSylwester Nawrocki } 82aba611fcSSylwester Nawrocki 83aba611fcSSylwester Nawrocki static const struct snd_soc_ops odroid_card_ops = { 84aba611fcSSylwester Nawrocki .startup = odroid_card_startup, 85aba611fcSSylwester Nawrocki .hw_params = odroid_card_hw_params, 86aba611fcSSylwester Nawrocki }; 87aba611fcSSylwester Nawrocki 88aba611fcSSylwester Nawrocki static void odroid_put_codec_of_nodes(struct snd_soc_dai_link *link) 89aba611fcSSylwester Nawrocki { 90aba611fcSSylwester Nawrocki struct snd_soc_dai_link_component *component = link->codecs; 91aba611fcSSylwester Nawrocki int i; 92aba611fcSSylwester Nawrocki 93aba611fcSSylwester Nawrocki for (i = 0; i < link->num_codecs; i++, component++) { 94aba611fcSSylwester Nawrocki if (!component->of_node) 95aba611fcSSylwester Nawrocki break; 96aba611fcSSylwester Nawrocki of_node_put(component->of_node); 97aba611fcSSylwester Nawrocki } 98aba611fcSSylwester Nawrocki } 99aba611fcSSylwester Nawrocki 100aba611fcSSylwester Nawrocki static int odroid_audio_probe(struct platform_device *pdev) 101aba611fcSSylwester Nawrocki { 102aba611fcSSylwester Nawrocki struct device *dev = &pdev->dev; 103aba611fcSSylwester Nawrocki struct device_node *cpu, *codec; 104aba611fcSSylwester Nawrocki struct odroid_priv *priv; 105aba611fcSSylwester Nawrocki struct snd_soc_dai_link *link; 106aba611fcSSylwester Nawrocki struct snd_soc_card *card; 107aba611fcSSylwester Nawrocki int ret; 108aba611fcSSylwester Nawrocki 109aba611fcSSylwester Nawrocki priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 110aba611fcSSylwester Nawrocki if (!priv) 111aba611fcSSylwester Nawrocki return -ENOMEM; 112aba611fcSSylwester Nawrocki 113aba611fcSSylwester Nawrocki card = &priv->card; 114aba611fcSSylwester Nawrocki card->dev = dev; 115aba611fcSSylwester Nawrocki 116aba611fcSSylwester Nawrocki card->owner = THIS_MODULE; 117aba611fcSSylwester Nawrocki card->fully_routed = true; 118aba611fcSSylwester Nawrocki 119aba611fcSSylwester Nawrocki snd_soc_card_set_drvdata(card, priv); 120aba611fcSSylwester Nawrocki 121aba611fcSSylwester Nawrocki priv->pll = devm_clk_get(dev, "epll"); 122aba611fcSSylwester Nawrocki if (IS_ERR(priv->pll)) 123aba611fcSSylwester Nawrocki return PTR_ERR(priv->pll); 124aba611fcSSylwester Nawrocki 125aba611fcSSylwester Nawrocki priv->rclk = devm_clk_get(dev, "i2s_rclk"); 126aba611fcSSylwester Nawrocki if (IS_ERR(priv->rclk)) 127aba611fcSSylwester Nawrocki return PTR_ERR(priv->rclk); 128aba611fcSSylwester Nawrocki 129aba611fcSSylwester Nawrocki ret = snd_soc_of_parse_card_name(card, "model"); 130aba611fcSSylwester Nawrocki if (ret < 0) 131aba611fcSSylwester Nawrocki return ret; 132aba611fcSSylwester Nawrocki 133aba611fcSSylwester Nawrocki if (of_property_read_bool(dev->of_node, "samsung,audio-widgets")) { 134aba611fcSSylwester Nawrocki ret = snd_soc_of_parse_audio_simple_widgets(card, 135aba611fcSSylwester Nawrocki "samsung,audio-widgets"); 136aba611fcSSylwester Nawrocki if (ret < 0) 137aba611fcSSylwester Nawrocki return ret; 138aba611fcSSylwester Nawrocki } 139aba611fcSSylwester Nawrocki 140aba611fcSSylwester Nawrocki if (of_property_read_bool(dev->of_node, "samsung,audio-routing")) { 141aba611fcSSylwester Nawrocki ret = snd_soc_of_parse_audio_routing(card, 142aba611fcSSylwester Nawrocki "samsung,audio-routing"); 143aba611fcSSylwester Nawrocki if (ret < 0) 144aba611fcSSylwester Nawrocki return ret; 145aba611fcSSylwester Nawrocki } 146aba611fcSSylwester Nawrocki 147aba611fcSSylwester Nawrocki link = &priv->dai_link; 148aba611fcSSylwester Nawrocki 149aba611fcSSylwester Nawrocki link->ops = &odroid_card_ops; 150aba611fcSSylwester Nawrocki link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 151aba611fcSSylwester Nawrocki SND_SOC_DAIFMT_CBS_CFS; 152aba611fcSSylwester Nawrocki 153aba611fcSSylwester Nawrocki card->dai_link = &priv->dai_link; 154aba611fcSSylwester Nawrocki card->num_links = 1; 155aba611fcSSylwester Nawrocki 156aba611fcSSylwester Nawrocki cpu = of_get_child_by_name(dev->of_node, "cpu"); 157aba611fcSSylwester Nawrocki codec = of_get_child_by_name(dev->of_node, "codec"); 158aba611fcSSylwester Nawrocki 159aba611fcSSylwester Nawrocki link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); 160aba611fcSSylwester Nawrocki if (!link->cpu_of_node) { 161aba611fcSSylwester Nawrocki dev_err(dev, "Failed parsing cpu/sound-dai property\n"); 162aba611fcSSylwester Nawrocki return -EINVAL; 163aba611fcSSylwester Nawrocki } 164aba611fcSSylwester Nawrocki 165aba611fcSSylwester Nawrocki ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); 166aba611fcSSylwester Nawrocki if (ret < 0) 167aba611fcSSylwester Nawrocki goto err_put_codec_n; 168aba611fcSSylwester Nawrocki 169aba611fcSSylwester Nawrocki link->platform_of_node = link->cpu_of_node; 170aba611fcSSylwester Nawrocki 171aba611fcSSylwester Nawrocki link->name = "Primary"; 172aba611fcSSylwester Nawrocki link->stream_name = link->name; 173aba611fcSSylwester Nawrocki 174aba611fcSSylwester Nawrocki ret = devm_snd_soc_register_card(dev, card); 175aba611fcSSylwester Nawrocki if (ret < 0) { 176aba611fcSSylwester Nawrocki dev_err(dev, "snd_soc_register_card() failed: %d\n", ret); 177aba611fcSSylwester Nawrocki goto err_put_i2s_n; 178aba611fcSSylwester Nawrocki } 179aba611fcSSylwester Nawrocki 180aba611fcSSylwester Nawrocki return 0; 181aba611fcSSylwester Nawrocki 182aba611fcSSylwester Nawrocki err_put_i2s_n: 183aba611fcSSylwester Nawrocki of_node_put(link->cpu_of_node); 184aba611fcSSylwester Nawrocki err_put_codec_n: 185aba611fcSSylwester Nawrocki odroid_put_codec_of_nodes(link); 186aba611fcSSylwester Nawrocki return ret; 187aba611fcSSylwester Nawrocki } 188aba611fcSSylwester Nawrocki 189aba611fcSSylwester Nawrocki static int odroid_audio_remove(struct platform_device *pdev) 190aba611fcSSylwester Nawrocki { 191aba611fcSSylwester Nawrocki struct odroid_priv *priv = platform_get_drvdata(pdev); 192aba611fcSSylwester Nawrocki 193aba611fcSSylwester Nawrocki of_node_put(priv->dai_link.cpu_of_node); 194aba611fcSSylwester Nawrocki odroid_put_codec_of_nodes(&priv->dai_link); 195aba611fcSSylwester Nawrocki 196aba611fcSSylwester Nawrocki return 0; 197aba611fcSSylwester Nawrocki } 198aba611fcSSylwester Nawrocki 199aba611fcSSylwester Nawrocki static const struct of_device_id odroid_audio_of_match[] = { 200aba611fcSSylwester Nawrocki { .compatible = "samsung,odroid-xu3-audio" }, 201aba611fcSSylwester Nawrocki { .compatible = "samsung,odroid-xu4-audio"}, 202aba611fcSSylwester Nawrocki { }, 203aba611fcSSylwester Nawrocki }; 204aba611fcSSylwester Nawrocki MODULE_DEVICE_TABLE(of, odroid_audio_of_match); 205aba611fcSSylwester Nawrocki 206aba611fcSSylwester Nawrocki static struct platform_driver odroid_audio_driver = { 207aba611fcSSylwester Nawrocki .driver = { 208aba611fcSSylwester Nawrocki .name = "odroid-audio", 209aba611fcSSylwester Nawrocki .of_match_table = odroid_audio_of_match, 210aba611fcSSylwester Nawrocki .pm = &snd_soc_pm_ops, 211aba611fcSSylwester Nawrocki }, 212aba611fcSSylwester Nawrocki .probe = odroid_audio_probe, 213aba611fcSSylwester Nawrocki .remove = odroid_audio_remove, 214aba611fcSSylwester Nawrocki }; 215aba611fcSSylwester Nawrocki module_platform_driver(odroid_audio_driver); 216aba611fcSSylwester Nawrocki 217aba611fcSSylwester Nawrocki MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); 218aba611fcSSylwester Nawrocki MODULE_DESCRIPTION("Odroid XU3/XU4 audio support"); 219aba611fcSSylwester Nawrocki MODULE_LICENSE("GPL v2"); 220