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 | |
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(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 | |
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 | |
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(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 | |
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 | |
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(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 | |
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 | |
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(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 | |
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 | |