1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Rockchip machine ASoC driver for boards using a RT5645/RT5650 CODEC. |
4 | * |
5 | * Copyright (c) 2015, ROCKCHIP CORPORATION. All rights reserved. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/delay.h> |
12 | #include <sound/core.h> |
13 | #include <sound/jack.h> |
14 | #include <sound/pcm.h> |
15 | #include <sound/pcm_params.h> |
16 | #include <sound/soc.h> |
17 | #include "rockchip_i2s.h" |
18 | #include "../codecs/rt5645.h" |
19 | |
20 | #define DRV_NAME "rockchip-snd-rt5645" |
21 | |
22 | static struct snd_soc_jack headset_jack; |
23 | static struct snd_soc_jack_pin headset_jack_pins[] = { |
24 | { |
25 | .pin = "Headphones" , |
26 | .mask = SND_JACK_HEADPHONE, |
27 | }, |
28 | { |
29 | .pin = "Headset Mic" , |
30 | .mask = SND_JACK_MICROPHONE, |
31 | }, |
32 | }; |
33 | |
34 | static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { |
35 | SND_SOC_DAPM_HP("Headphones" , NULL), |
36 | SND_SOC_DAPM_SPK("Speakers" , NULL), |
37 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
38 | SND_SOC_DAPM_MIC("Int Mic" , NULL), |
39 | }; |
40 | |
41 | static const struct snd_soc_dapm_route rk_audio_map[] = { |
42 | /* Input Lines */ |
43 | {"DMIC L2" , NULL, "Int Mic" }, |
44 | {"DMIC R2" , NULL, "Int Mic" }, |
45 | {"RECMIXL" , NULL, "Headset Mic" }, |
46 | {"RECMIXR" , NULL, "Headset Mic" }, |
47 | |
48 | /* Output Lines */ |
49 | {"Headphones" , NULL, "HPOR" }, |
50 | {"Headphones" , NULL, "HPOL" }, |
51 | {"Speakers" , NULL, "SPOL" }, |
52 | {"Speakers" , NULL, "SPOR" }, |
53 | }; |
54 | |
55 | static const struct snd_kcontrol_new rk_mc_controls[] = { |
56 | SOC_DAPM_PIN_SWITCH("Headphones" ), |
57 | SOC_DAPM_PIN_SWITCH("Speakers" ), |
58 | SOC_DAPM_PIN_SWITCH("Headset Mic" ), |
59 | SOC_DAPM_PIN_SWITCH("Int Mic" ), |
60 | }; |
61 | |
62 | static int rk_aif1_hw_params(struct snd_pcm_substream *substream, |
63 | struct snd_pcm_hw_params *params) |
64 | { |
65 | int ret = 0; |
66 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
67 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
68 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
69 | int mclk; |
70 | |
71 | switch (params_rate(p: params)) { |
72 | case 8000: |
73 | case 16000: |
74 | case 24000: |
75 | case 32000: |
76 | case 48000: |
77 | case 64000: |
78 | case 96000: |
79 | mclk = 12288000; |
80 | break; |
81 | case 11025: |
82 | case 22050: |
83 | case 44100: |
84 | case 88200: |
85 | mclk = 11289600; |
86 | break; |
87 | default: |
88 | return -EINVAL; |
89 | } |
90 | |
91 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, clk_id: 0, freq: mclk, |
92 | SND_SOC_CLOCK_OUT); |
93 | if (ret < 0) { |
94 | dev_err(codec_dai->dev, "Can't set codec clock %d\n" , ret); |
95 | return ret; |
96 | } |
97 | |
98 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: mclk, |
99 | SND_SOC_CLOCK_IN); |
100 | if (ret < 0) { |
101 | dev_err(codec_dai->dev, "Can't set codec clock %d\n" , ret); |
102 | return ret; |
103 | } |
104 | |
105 | return ret; |
106 | } |
107 | |
108 | static int rk_init(struct snd_soc_pcm_runtime *runtime) |
109 | { |
110 | struct snd_soc_card *card = runtime->card; |
111 | int ret; |
112 | |
113 | /* Enable Headset and 4 Buttons Jack detection */ |
114 | ret = snd_soc_card_jack_new_pins(card, id: "Headset Jack" , |
115 | type: SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | |
116 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | |
117 | SND_JACK_BTN_2 | SND_JACK_BTN_3, |
118 | jack: &headset_jack, |
119 | pins: headset_jack_pins, |
120 | ARRAY_SIZE(headset_jack_pins)); |
121 | if (ret) { |
122 | dev_err(card->dev, "New Headset Jack failed! (%d)\n" , ret); |
123 | return ret; |
124 | } |
125 | |
126 | return rt5645_set_jack_detect(snd_soc_rtd_to_codec(runtime, 0)->component, |
127 | hp_jack: &headset_jack, |
128 | mic_jack: &headset_jack, |
129 | btn_jack: &headset_jack); |
130 | } |
131 | |
132 | static const struct snd_soc_ops rk_aif1_ops = { |
133 | .hw_params = rk_aif1_hw_params, |
134 | }; |
135 | |
136 | SND_SOC_DAILINK_DEFS(pcm, |
137 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
138 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1" )), |
139 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
140 | |
141 | static struct snd_soc_dai_link rk_dailink = { |
142 | .name = "rt5645" , |
143 | .stream_name = "rt5645 PCM" , |
144 | .init = rk_init, |
145 | .ops = &rk_aif1_ops, |
146 | /* set rt5645 as slave */ |
147 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
148 | SND_SOC_DAIFMT_CBS_CFS, |
149 | SND_SOC_DAILINK_REG(pcm), |
150 | }; |
151 | |
152 | static struct snd_soc_card snd_soc_card_rk = { |
153 | .name = "I2S-RT5650" , |
154 | .owner = THIS_MODULE, |
155 | .dai_link = &rk_dailink, |
156 | .num_links = 1, |
157 | .dapm_widgets = rk_dapm_widgets, |
158 | .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets), |
159 | .dapm_routes = rk_audio_map, |
160 | .num_dapm_routes = ARRAY_SIZE(rk_audio_map), |
161 | .controls = rk_mc_controls, |
162 | .num_controls = ARRAY_SIZE(rk_mc_controls), |
163 | }; |
164 | |
165 | static int snd_rk_mc_probe(struct platform_device *pdev) |
166 | { |
167 | int ret = 0; |
168 | struct snd_soc_card *card = &snd_soc_card_rk; |
169 | struct device_node *np = pdev->dev.of_node; |
170 | |
171 | /* register the soc card */ |
172 | card->dev = &pdev->dev; |
173 | |
174 | rk_dailink.codecs->of_node = of_parse_phandle(np, |
175 | phandle_name: "rockchip,audio-codec" , index: 0); |
176 | if (!rk_dailink.codecs->of_node) { |
177 | dev_err(&pdev->dev, |
178 | "Property 'rockchip,audio-codec' missing or invalid\n" ); |
179 | return -EINVAL; |
180 | } |
181 | |
182 | rk_dailink.cpus->of_node = of_parse_phandle(np, |
183 | phandle_name: "rockchip,i2s-controller" , index: 0); |
184 | if (!rk_dailink.cpus->of_node) { |
185 | dev_err(&pdev->dev, |
186 | "Property 'rockchip,i2s-controller' missing or invalid\n" ); |
187 | ret = -EINVAL; |
188 | goto put_codec_of_node; |
189 | } |
190 | |
191 | rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; |
192 | |
193 | ret = snd_soc_of_parse_card_name(card, propname: "rockchip,model" ); |
194 | if (ret) { |
195 | dev_err(&pdev->dev, |
196 | "Soc parse card name failed %d\n" , ret); |
197 | goto put_cpu_of_node; |
198 | } |
199 | |
200 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
201 | if (ret) { |
202 | dev_err(&pdev->dev, |
203 | "Soc register card failed %d\n" , ret); |
204 | goto put_cpu_of_node; |
205 | } |
206 | |
207 | return ret; |
208 | |
209 | put_cpu_of_node: |
210 | of_node_put(node: rk_dailink.cpus->of_node); |
211 | rk_dailink.cpus->of_node = NULL; |
212 | put_codec_of_node: |
213 | of_node_put(node: rk_dailink.codecs->of_node); |
214 | rk_dailink.codecs->of_node = NULL; |
215 | |
216 | return ret; |
217 | } |
218 | |
219 | static void snd_rk_mc_remove(struct platform_device *pdev) |
220 | { |
221 | of_node_put(node: rk_dailink.cpus->of_node); |
222 | rk_dailink.cpus->of_node = NULL; |
223 | of_node_put(node: rk_dailink.codecs->of_node); |
224 | rk_dailink.codecs->of_node = NULL; |
225 | } |
226 | |
227 | static const struct of_device_id rockchip_rt5645_of_match[] = { |
228 | { .compatible = "rockchip,rockchip-audio-rt5645" , }, |
229 | {}, |
230 | }; |
231 | |
232 | MODULE_DEVICE_TABLE(of, rockchip_rt5645_of_match); |
233 | |
234 | static struct platform_driver snd_rk_mc_driver = { |
235 | .probe = snd_rk_mc_probe, |
236 | .remove_new = snd_rk_mc_remove, |
237 | .driver = { |
238 | .name = DRV_NAME, |
239 | .pm = &snd_soc_pm_ops, |
240 | .of_match_table = rockchip_rt5645_of_match, |
241 | }, |
242 | }; |
243 | |
244 | module_platform_driver(snd_rk_mc_driver); |
245 | |
246 | MODULE_AUTHOR("Xing Zheng <zhengxing@rock-chips.com>" ); |
247 | MODULE_DESCRIPTION("Rockchip rt5645 machine ASoC driver" ); |
248 | MODULE_LICENSE("GPL v2" ); |
249 | MODULE_ALIAS("platform:" DRV_NAME); |
250 | |