Commit | Line | Data |
---|---|---|
32ee40b5 CR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // | |
3 | // Copyright(c) 2021-2022 Intel Corporation. All rights reserved. | |
4 | // | |
5 | // Authors: Cezary Rojewski <cezary.rojewski@intel.com> | |
6 | // Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> | |
7 | // | |
8 | ||
9 | #include <linux/input.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/platform_device.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 <sound/soc-acpi.h> | |
18 | #include "../../../codecs/nau8825.h" | |
70c10191 | 19 | #include "../utils.h" |
32ee40b5 CR |
20 | |
21 | #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" | |
22 | ||
23 | static int | |
24 | avs_nau8825_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) | |
25 | { | |
26 | struct snd_soc_dapm_context *dapm = w->dapm; | |
27 | struct snd_soc_card *card = dapm->card; | |
28 | struct snd_soc_dai *codec_dai; | |
29 | int ret; | |
30 | ||
31 | codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); | |
32 | if (!codec_dai) { | |
33 | dev_err(card->dev, "Codec dai not found\n"); | |
34 | return -EINVAL; | |
35 | } | |
36 | ||
6206b2e7 CR |
37 | if (SND_SOC_DAPM_EVENT_ON(event)) |
38 | ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_MCLK, 24000000, | |
39 | SND_SOC_CLOCK_IN); | |
40 | else | |
32ee40b5 | 41 | ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); |
6206b2e7 CR |
42 | if (ret < 0) |
43 | dev_err(card->dev, "Set sysclk failed: %d\n", ret); | |
32ee40b5 | 44 | |
6206b2e7 | 45 | return ret; |
32ee40b5 CR |
46 | } |
47 | ||
48 | static const struct snd_kcontrol_new card_controls[] = { | |
49 | SOC_DAPM_PIN_SWITCH("Headphone Jack"), | |
50 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
51 | }; | |
52 | ||
53 | static const struct snd_soc_dapm_widget card_widgets[] = { | |
54 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | |
55 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
56 | SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_nau8825_clock_control, | |
57 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), | |
58 | }; | |
59 | ||
60 | static const struct snd_soc_dapm_route card_base_routes[] = { | |
61 | { "Headphone Jack", NULL, "HPOL" }, | |
62 | { "Headphone Jack", NULL, "HPOR" }, | |
63 | ||
64 | { "MIC", NULL, "Headset Mic" }, | |
65 | ||
66 | { "Headphone Jack", NULL, "Platform Clock" }, | |
67 | { "Headset Mic", NULL, "Platform Clock" }, | |
68 | }; | |
69 | ||
70 | static struct snd_soc_jack_pin card_headset_pins[] = { | |
71 | { | |
72 | .pin = "Headphone Jack", | |
73 | .mask = SND_JACK_HEADPHONE, | |
74 | }, | |
75 | { | |
76 | .pin = "Headset Mic", | |
77 | .mask = SND_JACK_MICROPHONE, | |
78 | }, | |
79 | }; | |
80 | ||
81 | static int avs_nau8825_codec_init(struct snd_soc_pcm_runtime *runtime) | |
82 | { | |
9febcd7a | 83 | struct snd_soc_card *card = runtime->card; |
32ee40b5 CR |
84 | struct snd_soc_jack_pin *pins; |
85 | struct snd_soc_jack *jack; | |
32ee40b5 CR |
86 | int num_pins, ret; |
87 | ||
88 | jack = snd_soc_card_get_drvdata(card); | |
89 | num_pins = ARRAY_SIZE(card_headset_pins); | |
90 | ||
91 | pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); | |
92 | if (!pins) | |
93 | return -ENOMEM; | |
94 | ||
95 | /* | |
96 | * 4 buttons here map to the google Reference headset. | |
97 | * The use of these buttons can be decided by the user space. | |
98 | */ | |
99 | ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0 | | |
100 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, | |
101 | jack, pins, num_pins); | |
102 | if (ret) | |
103 | return ret; | |
104 | ||
105 | snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); | |
106 | snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); | |
107 | snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); | |
108 | snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); | |
109 | ||
5d2d1a48 | 110 | return snd_soc_component_set_jack(snd_soc_rtd_to_codec(runtime, 0)->component, jack, NULL); |
9febcd7a | 111 | } |
32ee40b5 | 112 | |
9febcd7a AS |
113 | static void avs_nau8825_codec_exit(struct snd_soc_pcm_runtime *rtd) |
114 | { | |
5d2d1a48 | 115 | snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, NULL, NULL); |
32ee40b5 CR |
116 | } |
117 | ||
118 | static int | |
119 | avs_nau8825_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) | |
120 | { | |
121 | struct snd_interval *rate, *channels; | |
122 | struct snd_mask *fmt; | |
123 | ||
124 | rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); | |
125 | channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); | |
126 | fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); | |
127 | ||
128 | /* The ADSP will convert the FE rate to 48k, stereo */ | |
129 | rate->min = rate->max = 48000; | |
130 | channels->min = channels->max = 2; | |
131 | ||
132 | /* set SSP to 24 bit */ | |
133 | snd_mask_none(fmt); | |
134 | snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
139 | static int avs_nau8825_trigger(struct snd_pcm_substream *substream, int cmd) | |
140 | { | |
141 | struct snd_pcm_runtime *runtime = substream->runtime; | |
5d2d1a48 KM |
142 | struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); |
143 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); | |
32ee40b5 CR |
144 | int ret = 0; |
145 | ||
146 | switch (cmd) { | |
147 | case SNDRV_PCM_TRIGGER_START: | |
148 | ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); | |
149 | if (ret < 0) { | |
150 | dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); | |
151 | break; | |
152 | } | |
153 | ||
154 | ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); | |
155 | if (ret < 0) | |
156 | dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); | |
157 | break; | |
158 | ||
159 | case SNDRV_PCM_TRIGGER_RESUME: | |
160 | ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); | |
161 | if (ret < 0) | |
162 | dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); | |
163 | break; | |
164 | } | |
165 | ||
166 | return ret; | |
167 | } | |
168 | ||
169 | ||
170 | static const struct snd_soc_ops avs_nau8825_ops = { | |
171 | .trigger = avs_nau8825_trigger, | |
172 | }; | |
173 | ||
174 | static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, | |
70c10191 | 175 | int tdm_slot, struct snd_soc_dai_link **dai_link) |
32ee40b5 CR |
176 | { |
177 | struct snd_soc_dai_link_component *platform; | |
178 | struct snd_soc_dai_link *dl; | |
179 | ||
180 | dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); | |
181 | platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); | |
182 | if (!dl || !platform) | |
183 | return -ENOMEM; | |
184 | ||
185 | platform->name = platform_name; | |
186 | ||
70c10191 AS |
187 | dl->name = devm_kasprintf(dev, GFP_KERNEL, |
188 | AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); | |
32ee40b5 CR |
189 | dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); |
190 | dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); | |
191 | if (!dl->name || !dl->cpus || !dl->codecs) | |
192 | return -ENOMEM; | |
193 | ||
70c10191 AS |
194 | dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, |
195 | AVS_STRING_FMT("SSP", " Pin", ssp_port, tdm_slot)); | |
32ee40b5 CR |
196 | dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10508825:00"); |
197 | dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, SKL_NUVOTON_CODEC_DAI); | |
198 | if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) | |
199 | return -ENOMEM; | |
200 | ||
201 | dl->num_cpus = 1; | |
202 | dl->num_codecs = 1; | |
203 | dl->platforms = platform; | |
204 | dl->num_platforms = 1; | |
205 | dl->id = 0; | |
206 | dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; | |
207 | dl->init = avs_nau8825_codec_init; | |
9febcd7a | 208 | dl->exit = avs_nau8825_codec_exit; |
32ee40b5 CR |
209 | dl->be_hw_params_fixup = avs_nau8825_be_fixup; |
210 | dl->ops = &avs_nau8825_ops; | |
211 | dl->nonatomic = 1; | |
212 | dl->no_pcm = 1; | |
213 | dl->dpcm_capture = 1; | |
214 | dl->dpcm_playback = 1; | |
215 | ||
216 | *dai_link = dl; | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
32ee40b5 CR |
221 | static int avs_card_suspend_pre(struct snd_soc_card *card) |
222 | { | |
9febcd7a AS |
223 | struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); |
224 | ||
225 | return snd_soc_component_set_jack(codec_dai->component, NULL, NULL); | |
32ee40b5 CR |
226 | } |
227 | ||
228 | static int avs_card_resume_post(struct snd_soc_card *card) | |
229 | { | |
230 | struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); | |
231 | struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); | |
ec4b2099 | 232 | int stream = SNDRV_PCM_STREAM_PLAYBACK; |
32ee40b5 CR |
233 | |
234 | if (!codec_dai) { | |
235 | dev_err(card->dev, "Codec dai not found\n"); | |
236 | return -EINVAL; | |
237 | } | |
238 | ||
ec4b2099 KM |
239 | if (snd_soc_dai_stream_active(codec_dai, stream) && |
240 | snd_soc_dai_get_widget(codec_dai, stream)->active) | |
32ee40b5 CR |
241 | snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); |
242 | ||
9febcd7a | 243 | return snd_soc_component_set_jack(codec_dai->component, jack, NULL); |
32ee40b5 CR |
244 | } |
245 | ||
246 | static int avs_nau8825_probe(struct platform_device *pdev) | |
247 | { | |
32ee40b5 CR |
248 | struct snd_soc_dai_link *dai_link; |
249 | struct snd_soc_acpi_mach *mach; | |
250 | struct snd_soc_card *card; | |
251 | struct snd_soc_jack *jack; | |
252 | struct device *dev = &pdev->dev; | |
253 | const char *pname; | |
70c10191 | 254 | int ssp_port, tdm_slot, ret; |
32ee40b5 CR |
255 | |
256 | mach = dev_get_platdata(dev); | |
257 | pname = mach->mach_params.platform; | |
32ee40b5 | 258 | |
70c10191 AS |
259 | ret = avs_mach_get_ssp_tdm(dev, mach, &ssp_port, &tdm_slot); |
260 | if (ret) | |
261 | return ret; | |
262 | ||
263 | ret = avs_create_dai_link(dev, pname, ssp_port, tdm_slot, &dai_link); | |
32ee40b5 CR |
264 | if (ret) { |
265 | dev_err(dev, "Failed to create dai link: %d", ret); | |
266 | return ret; | |
267 | } | |
268 | ||
32ee40b5 CR |
269 | jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); |
270 | card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); | |
271 | if (!jack || !card) | |
272 | return -ENOMEM; | |
273 | ||
274 | card->name = "avs_nau8825"; | |
275 | card->dev = dev; | |
276 | card->owner = THIS_MODULE; | |
32ee40b5 CR |
277 | card->suspend_pre = avs_card_suspend_pre; |
278 | card->resume_post = avs_card_resume_post; | |
279 | card->dai_link = dai_link; | |
280 | card->num_links = 1; | |
281 | card->controls = card_controls; | |
282 | card->num_controls = ARRAY_SIZE(card_controls); | |
283 | card->dapm_widgets = card_widgets; | |
284 | card->num_dapm_widgets = ARRAY_SIZE(card_widgets); | |
ae7d6682 BL |
285 | card->dapm_routes = card_base_routes; |
286 | card->num_dapm_routes = ARRAY_SIZE(card_base_routes); | |
32ee40b5 CR |
287 | card->fully_routed = true; |
288 | snd_soc_card_set_drvdata(card, jack); | |
289 | ||
290 | ret = snd_soc_fixup_dai_links_platform_name(card, pname); | |
291 | if (ret) | |
292 | return ret; | |
293 | ||
294 | return devm_snd_soc_register_card(dev, card); | |
295 | } | |
296 | ||
297 | static struct platform_driver avs_nau8825_driver = { | |
298 | .probe = avs_nau8825_probe, | |
299 | .driver = { | |
300 | .name = "avs_nau8825", | |
301 | .pm = &snd_soc_pm_ops, | |
302 | }, | |
303 | }; | |
304 | ||
305 | module_platform_driver(avs_nau8825_driver) | |
306 | ||
307 | MODULE_LICENSE("GPL"); | |
308 | MODULE_ALIAS("platform:avs_nau8825"); |