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
27 struct rk_drvdata {
28 struct gpio_desc *gpio_hp_en;
29 };
30
rk_hp_power(struct snd_soc_dapm_widget * w,struct snd_kcontrol * k,int event)31 static 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(w->dapm->card);
35
36 gpiod_set_value_cansleep(machine->gpio_hp_en,
37 SND_SOC_DAPM_EVENT_ON(event));
38
39 return 0;
40 }
41
42 static struct snd_soc_jack headphone_jack;
43 static struct snd_soc_jack_pin headphone_jack_pins[] = {
44 {
45 .pin = "Analog",
46 .mask = SND_JACK_HEADPHONE
47 },
48 };
49
50 static 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
55 static const struct snd_kcontrol_new rk_mc_controls[] = {
56 SOC_DAPM_PIN_SWITCH("Analog"),
57 SOC_DAPM_PIN_SWITCH("HDMI"),
58 };
59
rk_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)60 static 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(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(cpu_dai, 0, 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(codec_dai, 0, 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
110 static 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
rk_init(struct snd_soc_pcm_runtime * runtime)116 static 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(dev->of_node, "rockchip,hp-det-gpios")) {
123 rk_hp_jack_gpio.gpiod_dev = dev;
124 snd_soc_card_jack_new_pins(runtime->card, "Headphone Jack",
125 SND_JACK_HEADPHONE, &headphone_jack,
126 headphone_jack_pins,
127 ARRAY_SIZE(headphone_jack_pins));
128 snd_soc_jack_add_gpios(&headphone_jack, 1, &rk_hp_jack_gpio);
129 }
130
131 return 0;
132 }
133
134 static const struct snd_soc_ops rk_ops = {
135 .hw_params = rk_hw_params,
136 };
137
138 SND_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
144 static 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
155 static 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
snd_rk_mc_probe(struct platform_device * pdev)166 static 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(&pdev->dev, 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(&pdev->dev, "rockchip,hp-en", GPIOD_OUT_LOW);
182 if (IS_ERR(machine->gpio_hp_en))
183 return PTR_ERR(machine->gpio_hp_en);
184 gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en");
185
186 ret = snd_soc_of_parse_card_name(card, "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 "rockchip,audio-codec",
194 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, "rockchip,audio-codec",
201 0, 0, &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, &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, "rockchip,i2s-controller",
215 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, "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, machine);
232
233 ret = devm_snd_soc_register_card(&pdev->dev, card);
234 if (ret)
235 return dev_err_probe(&pdev->dev, ret,
236 "Soc register card failed\n");
237
238 return 0;
239 }
240
241 static const struct of_device_id rockchip_sound_of_match[] = {
242 { .compatible = "rockchip,rk3288-hdmi-analog", },
243 {},
244 };
245
246 MODULE_DEVICE_TABLE(of, rockchip_sound_of_match);
247
248 static 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
257 module_platform_driver(rockchip_sound_driver);
258
259 MODULE_AUTHOR("Sjoerd Simons <sjoerd.simons@collabora.com>");
260 MODULE_DESCRIPTION("Rockchip RK3288 machine ASoC driver");
261 MODULE_LICENSE("GPL v2");
262 MODULE_ALIAS("platform:" DRV_NAME);
263