19952f691SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2eaae2ea7SRomain Perier /*
3eaae2ea7SRomain Perier * Rockchip machine ASoC driver for RK3288 boards that have an HDMI and analog
4eaae2ea7SRomain Perier * audio output
5eaae2ea7SRomain Perier *
6eaae2ea7SRomain Perier * Copyright (c) 2016, Collabora Ltd.
7eaae2ea7SRomain Perier *
8eaae2ea7SRomain Perier * Authors: Sjoerd Simons <sjoerd.simons@collabora.com>,
9eaae2ea7SRomain Perier * Romain Perier <romain.perier@collabora.com>
10eaae2ea7SRomain Perier */
11eaae2ea7SRomain Perier
12eaae2ea7SRomain Perier #include <linux/module.h>
13eaae2ea7SRomain Perier #include <linux/platform_device.h>
14eaae2ea7SRomain Perier #include <linux/slab.h>
15eaae2ea7SRomain Perier #include <linux/gpio.h>
16eaae2ea7SRomain Perier #include <linux/of_gpio.h>
17eaae2ea7SRomain Perier #include <sound/core.h>
18eaae2ea7SRomain Perier #include <sound/jack.h>
19eaae2ea7SRomain Perier #include <sound/pcm.h>
20eaae2ea7SRomain Perier #include <sound/pcm_params.h>
21eaae2ea7SRomain Perier #include <sound/soc.h>
22eaae2ea7SRomain Perier #include <sound/soc-dapm.h>
23eaae2ea7SRomain Perier
24eaae2ea7SRomain Perier #include "rockchip_i2s.h"
25eaae2ea7SRomain Perier
26eaae2ea7SRomain Perier #define DRV_NAME "rk3288-snd-hdmi-analog"
27eaae2ea7SRomain Perier
28eaae2ea7SRomain Perier struct rk_drvdata {
29eaae2ea7SRomain Perier int gpio_hp_en;
30eaae2ea7SRomain Perier int gpio_hp_det;
31eaae2ea7SRomain Perier };
32eaae2ea7SRomain Perier
rk_hp_power(struct snd_soc_dapm_widget * w,struct snd_kcontrol * k,int event)33eaae2ea7SRomain Perier static int rk_hp_power(struct snd_soc_dapm_widget *w,
34eaae2ea7SRomain Perier struct snd_kcontrol *k, int event)
35eaae2ea7SRomain Perier {
36eaae2ea7SRomain Perier struct rk_drvdata *machine = snd_soc_card_get_drvdata(w->dapm->card);
37eaae2ea7SRomain Perier
38eaae2ea7SRomain Perier if (!gpio_is_valid(machine->gpio_hp_en))
39eaae2ea7SRomain Perier return 0;
40eaae2ea7SRomain Perier
41eaae2ea7SRomain Perier gpio_set_value_cansleep(machine->gpio_hp_en,
42eaae2ea7SRomain Perier SND_SOC_DAPM_EVENT_ON(event));
43eaae2ea7SRomain Perier
44eaae2ea7SRomain Perier return 0;
45eaae2ea7SRomain Perier }
46eaae2ea7SRomain Perier
47eaae2ea7SRomain Perier static struct snd_soc_jack headphone_jack;
48eaae2ea7SRomain Perier static struct snd_soc_jack_pin headphone_jack_pins[] = {
49eaae2ea7SRomain Perier {
50eaae2ea7SRomain Perier .pin = "Analog",
51eaae2ea7SRomain Perier .mask = SND_JACK_HEADPHONE
52eaae2ea7SRomain Perier },
53eaae2ea7SRomain Perier };
54eaae2ea7SRomain Perier
55eaae2ea7SRomain Perier static const struct snd_soc_dapm_widget rk_dapm_widgets[] = {
56eaae2ea7SRomain Perier SND_SOC_DAPM_HP("Analog", rk_hp_power),
57eaae2ea7SRomain Perier SND_SOC_DAPM_LINE("HDMI", NULL),
58eaae2ea7SRomain Perier };
59eaae2ea7SRomain Perier
60eaae2ea7SRomain Perier static const struct snd_kcontrol_new rk_mc_controls[] = {
61eaae2ea7SRomain Perier SOC_DAPM_PIN_SWITCH("Analog"),
62eaae2ea7SRomain Perier SOC_DAPM_PIN_SWITCH("HDMI"),
63eaae2ea7SRomain Perier };
64eaae2ea7SRomain Perier
rk_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)65eaae2ea7SRomain Perier static int rk_hw_params(struct snd_pcm_substream *substream,
66eaae2ea7SRomain Perier struct snd_pcm_hw_params *params)
67eaae2ea7SRomain Perier {
68eaae2ea7SRomain Perier int ret = 0;
695c5eb29eSKuninori Morimoto struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
70a7ff5268SKuninori Morimoto struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
71a7ff5268SKuninori Morimoto struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
72eaae2ea7SRomain Perier int mclk;
73eaae2ea7SRomain Perier
74eaae2ea7SRomain Perier switch (params_rate(params)) {
75eaae2ea7SRomain Perier case 8000:
76eaae2ea7SRomain Perier case 16000:
77eaae2ea7SRomain Perier case 24000:
78eaae2ea7SRomain Perier case 32000:
79eaae2ea7SRomain Perier case 48000:
80eaae2ea7SRomain Perier case 64000:
81eaae2ea7SRomain Perier case 96000:
82eaae2ea7SRomain Perier mclk = 12288000;
83eaae2ea7SRomain Perier break;
842e589fdcSRomain Perier case 192000:
852e589fdcSRomain Perier mclk = 24576000;
862e589fdcSRomain Perier break;
87eaae2ea7SRomain Perier case 11025:
88eaae2ea7SRomain Perier case 22050:
89eaae2ea7SRomain Perier case 44100:
90eaae2ea7SRomain Perier case 88200:
91eaae2ea7SRomain Perier mclk = 11289600;
92eaae2ea7SRomain Perier break;
93eaae2ea7SRomain Perier default:
94eaae2ea7SRomain Perier return -EINVAL;
95eaae2ea7SRomain Perier }
96eaae2ea7SRomain Perier
97eaae2ea7SRomain Perier ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
98eaae2ea7SRomain Perier SND_SOC_CLOCK_OUT);
99eaae2ea7SRomain Perier
100eaae2ea7SRomain Perier if (ret && ret != -ENOTSUPP) {
101eaae2ea7SRomain Perier dev_err(codec_dai->dev, "Can't set cpu clock %d\n", ret);
102eaae2ea7SRomain Perier return ret;
103eaae2ea7SRomain Perier }
104eaae2ea7SRomain Perier
105eaae2ea7SRomain Perier ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
106eaae2ea7SRomain Perier SND_SOC_CLOCK_IN);
107eaae2ea7SRomain Perier if (ret && ret != -ENOTSUPP) {
108eaae2ea7SRomain Perier dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
109eaae2ea7SRomain Perier return ret;
110eaae2ea7SRomain Perier }
111eaae2ea7SRomain Perier
112eaae2ea7SRomain Perier return 0;
113eaae2ea7SRomain Perier }
114eaae2ea7SRomain Perier
115eaae2ea7SRomain Perier static struct snd_soc_jack_gpio rk_hp_jack_gpio = {
116eaae2ea7SRomain Perier .name = "Headphone detection",
117eaae2ea7SRomain Perier .report = SND_JACK_HEADPHONE,
118eaae2ea7SRomain Perier .debounce_time = 150
119eaae2ea7SRomain Perier };
120eaae2ea7SRomain Perier
rk_init(struct snd_soc_pcm_runtime * runtime)121eaae2ea7SRomain Perier static int rk_init(struct snd_soc_pcm_runtime *runtime)
122eaae2ea7SRomain Perier {
123eaae2ea7SRomain Perier struct rk_drvdata *machine = snd_soc_card_get_drvdata(runtime->card);
124eaae2ea7SRomain Perier
125eaae2ea7SRomain Perier /* Enable Headset Jack detection */
126eaae2ea7SRomain Perier if (gpio_is_valid(machine->gpio_hp_det)) {
12719aed2d6SAkihiko Odaki snd_soc_card_jack_new_pins(runtime->card, "Headphone Jack",
128eaae2ea7SRomain Perier SND_JACK_HEADPHONE, &headphone_jack,
129eaae2ea7SRomain Perier headphone_jack_pins,
130eaae2ea7SRomain Perier ARRAY_SIZE(headphone_jack_pins));
131eaae2ea7SRomain Perier rk_hp_jack_gpio.gpio = machine->gpio_hp_det;
132eaae2ea7SRomain Perier snd_soc_jack_add_gpios(&headphone_jack, 1, &rk_hp_jack_gpio);
133eaae2ea7SRomain Perier }
134eaae2ea7SRomain Perier
135eaae2ea7SRomain Perier return 0;
136eaae2ea7SRomain Perier }
137eaae2ea7SRomain Perier
138c12a4a40SBhumika Goyal static const struct snd_soc_ops rk_ops = {
139eaae2ea7SRomain Perier .hw_params = rk_hw_params,
140eaae2ea7SRomain Perier };
141eaae2ea7SRomain Perier
142e0d129d4SKuninori Morimoto SND_SOC_DAILINK_DEFS(audio,
143e0d129d4SKuninori Morimoto DAILINK_COMP_ARRAY(COMP_EMPTY()),
144e0d129d4SKuninori Morimoto DAILINK_COMP_ARRAY(COMP_CODEC(NULL, NULL),
145418e12fcSKuninori Morimoto COMP_CODEC("hdmi-audio-codec.2.auto", "i2s-hifi")),
146418e12fcSKuninori Morimoto DAILINK_COMP_ARRAY(COMP_EMPTY()));
147eaae2ea7SRomain Perier
148eaae2ea7SRomain Perier static struct snd_soc_dai_link rk_dailink = {
149eaae2ea7SRomain Perier .name = "Codecs",
150eaae2ea7SRomain Perier .stream_name = "Audio",
151eaae2ea7SRomain Perier .init = rk_init,
152eaae2ea7SRomain Perier .ops = &rk_ops,
153eaae2ea7SRomain Perier /* Set codecs as slave */
154eaae2ea7SRomain Perier .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
155eaae2ea7SRomain Perier SND_SOC_DAIFMT_CBS_CFS,
156e0d129d4SKuninori Morimoto SND_SOC_DAILINK_REG(audio),
157eaae2ea7SRomain Perier };
158eaae2ea7SRomain Perier
159eaae2ea7SRomain Perier static struct snd_soc_card snd_soc_card_rk = {
160eaae2ea7SRomain Perier .name = "ROCKCHIP-I2S",
161eaae2ea7SRomain Perier .dai_link = &rk_dailink,
162eaae2ea7SRomain Perier .num_links = 1,
163eaae2ea7SRomain Perier .num_aux_devs = 0,
164eaae2ea7SRomain Perier .dapm_widgets = rk_dapm_widgets,
165eaae2ea7SRomain Perier .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),
166eaae2ea7SRomain Perier .controls = rk_mc_controls,
167eaae2ea7SRomain Perier .num_controls = ARRAY_SIZE(rk_mc_controls),
168eaae2ea7SRomain Perier };
169eaae2ea7SRomain Perier
snd_rk_mc_probe(struct platform_device * pdev)170eaae2ea7SRomain Perier static int snd_rk_mc_probe(struct platform_device *pdev)
171eaae2ea7SRomain Perier {
172*59a6cc5cSPierre-Louis Bossart int ret;
173eaae2ea7SRomain Perier struct snd_soc_card *card = &snd_soc_card_rk;
174eaae2ea7SRomain Perier struct device_node *np = pdev->dev.of_node;
175eaae2ea7SRomain Perier struct rk_drvdata *machine;
176eaae2ea7SRomain Perier struct of_phandle_args args;
177eaae2ea7SRomain Perier
178eaae2ea7SRomain Perier machine = devm_kzalloc(&pdev->dev, sizeof(struct rk_drvdata),
179eaae2ea7SRomain Perier GFP_KERNEL);
180eaae2ea7SRomain Perier if (!machine)
181eaae2ea7SRomain Perier return -ENOMEM;
182eaae2ea7SRomain Perier
183eaae2ea7SRomain Perier card->dev = &pdev->dev;
184eaae2ea7SRomain Perier
185eaae2ea7SRomain Perier machine->gpio_hp_det = of_get_named_gpio(np,
186eaae2ea7SRomain Perier "rockchip,hp-det-gpios", 0);
187eaae2ea7SRomain Perier if (!gpio_is_valid(machine->gpio_hp_det) && machine->gpio_hp_det != -ENODEV)
188eaae2ea7SRomain Perier return machine->gpio_hp_det;
189eaae2ea7SRomain Perier
190eaae2ea7SRomain Perier machine->gpio_hp_en = of_get_named_gpio(np,
191eaae2ea7SRomain Perier "rockchip,hp-en-gpios", 0);
192eaae2ea7SRomain Perier if (!gpio_is_valid(machine->gpio_hp_en) && machine->gpio_hp_en != -ENODEV)
193eaae2ea7SRomain Perier return machine->gpio_hp_en;
194eaae2ea7SRomain Perier
195eaae2ea7SRomain Perier if (gpio_is_valid(machine->gpio_hp_en)) {
196eaae2ea7SRomain Perier ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_en,
197eaae2ea7SRomain Perier GPIOF_OUT_INIT_LOW, "hp_en");
198eaae2ea7SRomain Perier if (ret) {
199eaae2ea7SRomain Perier dev_err(card->dev, "cannot get hp_en gpio\n");
200eaae2ea7SRomain Perier return ret;
201eaae2ea7SRomain Perier }
202eaae2ea7SRomain Perier }
203eaae2ea7SRomain Perier
204eaae2ea7SRomain Perier ret = snd_soc_of_parse_card_name(card, "rockchip,model");
205eaae2ea7SRomain Perier if (ret) {
206eaae2ea7SRomain Perier dev_err(card->dev, "SoC parse card name failed %d\n", ret);
207eaae2ea7SRomain Perier return ret;
208eaae2ea7SRomain Perier }
209eaae2ea7SRomain Perier
210eaae2ea7SRomain Perier rk_dailink.codecs[0].of_node = of_parse_phandle(np,
211eaae2ea7SRomain Perier "rockchip,audio-codec",
212eaae2ea7SRomain Perier 0);
213eaae2ea7SRomain Perier if (!rk_dailink.codecs[0].of_node) {
214eaae2ea7SRomain Perier dev_err(&pdev->dev,
215eaae2ea7SRomain Perier "Property 'rockchip,audio-codec' missing or invalid\n");
216eaae2ea7SRomain Perier return -EINVAL;
217eaae2ea7SRomain Perier }
218eaae2ea7SRomain Perier ret = of_parse_phandle_with_fixed_args(np, "rockchip,audio-codec",
219eaae2ea7SRomain Perier 0, 0, &args);
220eaae2ea7SRomain Perier if (ret) {
221eaae2ea7SRomain Perier dev_err(&pdev->dev,
222eaae2ea7SRomain Perier "Unable to parse property 'rockchip,audio-codec'\n");
223eaae2ea7SRomain Perier return ret;
224eaae2ea7SRomain Perier }
225eaae2ea7SRomain Perier
226eaae2ea7SRomain Perier ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name);
227eaae2ea7SRomain Perier if (ret) {
228eaae2ea7SRomain Perier dev_err(&pdev->dev, "Unable to get codec_dai_name\n");
229eaae2ea7SRomain Perier return ret;
230eaae2ea7SRomain Perier }
231eaae2ea7SRomain Perier
232e0d129d4SKuninori Morimoto rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller",
233eaae2ea7SRomain Perier 0);
234e0d129d4SKuninori Morimoto if (!rk_dailink.cpus->of_node) {
235eaae2ea7SRomain Perier dev_err(&pdev->dev,
236eaae2ea7SRomain Perier "Property 'rockchip,i2s-controller' missing or invalid\n");
237eaae2ea7SRomain Perier return -EINVAL;
238eaae2ea7SRomain Perier }
239eaae2ea7SRomain Perier
240418e12fcSKuninori Morimoto rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;
241418e12fcSKuninori Morimoto
242eaae2ea7SRomain Perier ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing");
243eaae2ea7SRomain Perier if (ret) {
244eaae2ea7SRomain Perier dev_err(&pdev->dev,
245eaae2ea7SRomain Perier "Unable to parse 'rockchip,routing' property\n");
246eaae2ea7SRomain Perier return ret;
247eaae2ea7SRomain Perier }
248eaae2ea7SRomain Perier
249eaae2ea7SRomain Perier snd_soc_card_set_drvdata(card, machine);
250eaae2ea7SRomain Perier
251eaae2ea7SRomain Perier ret = devm_snd_soc_register_card(&pdev->dev, card);
252b3a66d22SKuninori Morimoto if (ret)
253b3a66d22SKuninori Morimoto return dev_err_probe(&pdev->dev, ret,
254b3a66d22SKuninori Morimoto "Soc register card failed\n");
255eaae2ea7SRomain Perier
256*59a6cc5cSPierre-Louis Bossart return 0;
257eaae2ea7SRomain Perier }
258eaae2ea7SRomain Perier
259eaae2ea7SRomain Perier static const struct of_device_id rockchip_sound_of_match[] = {
260eaae2ea7SRomain Perier { .compatible = "rockchip,rk3288-hdmi-analog", },
261eaae2ea7SRomain Perier {},
262eaae2ea7SRomain Perier };
263eaae2ea7SRomain Perier
264eaae2ea7SRomain Perier MODULE_DEVICE_TABLE(of, rockchip_sound_of_match);
265eaae2ea7SRomain Perier
266eaae2ea7SRomain Perier static struct platform_driver rockchip_sound_driver = {
267eaae2ea7SRomain Perier .probe = snd_rk_mc_probe,
268eaae2ea7SRomain Perier .driver = {
269eaae2ea7SRomain Perier .name = DRV_NAME,
270eaae2ea7SRomain Perier .pm = &snd_soc_pm_ops,
271eaae2ea7SRomain Perier .of_match_table = rockchip_sound_of_match,
272eaae2ea7SRomain Perier },
273eaae2ea7SRomain Perier };
274eaae2ea7SRomain Perier
275eaae2ea7SRomain Perier module_platform_driver(rockchip_sound_driver);
276eaae2ea7SRomain Perier
277eaae2ea7SRomain Perier MODULE_AUTHOR("Sjoerd Simons <sjoerd.simons@collabora.com>");
278eaae2ea7SRomain Perier MODULE_DESCRIPTION("Rockchip RK3288 machine ASoC driver");
279eaae2ea7SRomain Perier MODULE_LICENSE("GPL v2");
280eaae2ea7SRomain Perier MODULE_ALIAS("platform:" DRV_NAME);
281