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

source code of linux/sound/soc/rockchip/rk3288_hdmi_analog.c