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