xref: /openbmc/linux/sound/soc/samsung/odroid.c (revision aba611fc)
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