Commit | Line | Data |
---|---|---|
82cf89de PLB |
1 | /* |
2 | * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and | |
3 | * Cherrytrail-based platforms, with Dialog DA7213 codec | |
4 | * | |
5 | * Copyright (C) 2017 Intel Corporation | |
6 | * Author: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | |
7 | * | |
8 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; version 2 of the License. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | * | |
19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
20 | */ | |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/acpi.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/slab.h> | |
26 | #include <asm/platform_sst_audio.h> | |
27 | #include <sound/pcm.h> | |
28 | #include <sound/pcm_params.h> | |
29 | #include <sound/soc.h> | |
7feb2f78 | 30 | #include <sound/soc-acpi.h> |
82cf89de PLB |
31 | #include "../../codecs/da7213.h" |
32 | #include "../atom/sst-atom-controls.h" | |
82cf89de PLB |
33 | |
34 | static const struct snd_kcontrol_new controls[] = { | |
35 | SOC_DAPM_PIN_SWITCH("Headphone Jack"), | |
36 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
37 | SOC_DAPM_PIN_SWITCH("Mic"), | |
38 | SOC_DAPM_PIN_SWITCH("Aux In"), | |
39 | }; | |
40 | ||
41 | static const struct snd_soc_dapm_widget dapm_widgets[] = { | |
42 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | |
43 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
44 | SND_SOC_DAPM_MIC("Mic", NULL), | |
45 | SND_SOC_DAPM_LINE("Aux In", NULL), | |
46 | }; | |
47 | ||
48 | static const struct snd_soc_dapm_route audio_map[] = { | |
49 | {"Headphone Jack", NULL, "HPL"}, | |
50 | {"Headphone Jack", NULL, "HPR"}, | |
51 | ||
52 | {"AUXL", NULL, "Aux In"}, | |
53 | {"AUXR", NULL, "Aux In"}, | |
54 | ||
55 | /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ | |
56 | {"MIC1", NULL, "Headset Mic"}, | |
57 | {"MIC2", NULL, "Mic"}, | |
58 | ||
59 | /* SOC-codec link */ | |
60 | {"ssp2 Tx", NULL, "codec_out0"}, | |
61 | {"ssp2 Tx", NULL, "codec_out1"}, | |
62 | {"codec_in0", NULL, "ssp2 Rx"}, | |
63 | {"codec_in1", NULL, "ssp2 Rx"}, | |
64 | ||
65 | {"Playback", NULL, "ssp2 Tx"}, | |
66 | {"ssp2 Rx", NULL, "Capture"}, | |
67 | }; | |
68 | ||
69 | static int codec_fixup(struct snd_soc_pcm_runtime *rtd, | |
70 | struct snd_pcm_hw_params *params) | |
71 | { | |
72 | int ret; | |
73 | struct snd_interval *rate = hw_param_interval(params, | |
74 | SNDRV_PCM_HW_PARAM_RATE); | |
75 | struct snd_interval *channels = hw_param_interval(params, | |
76 | SNDRV_PCM_HW_PARAM_CHANNELS); | |
77 | ||
78 | /* The DSP will convert the FE rate to 48k, stereo, 24bits */ | |
79 | rate->min = rate->max = 48000; | |
80 | channels->min = channels->max = 2; | |
81 | ||
82 | /* set SSP2 to 24-bit */ | |
83 | params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); | |
84 | ||
85 | /* | |
86 | * Default mode for SSP configuration is TDM 4 slot, override config | |
87 | * with explicit setting to I2S 2ch 24-bit. The word length is set with | |
88 | * dai_set_tdm_slot() since there is no other API exposed | |
89 | */ | |
90 | ret = snd_soc_dai_set_fmt(rtd->cpu_dai, | |
91 | SND_SOC_DAIFMT_I2S | | |
92 | SND_SOC_DAIFMT_NB_NF | | |
93 | SND_SOC_DAIFMT_CBS_CFS); | |
94 | if (ret < 0) { | |
95 | dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); | |
96 | return ret; | |
97 | } | |
98 | ||
99 | ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); | |
100 | if (ret < 0) { | |
101 | dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); | |
102 | return ret; | |
103 | } | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int aif1_startup(struct snd_pcm_substream *substream) | |
109 | { | |
110 | return snd_pcm_hw_constraint_single(substream->runtime, | |
111 | SNDRV_PCM_HW_PARAM_RATE, 48000); | |
112 | } | |
113 | ||
114 | static int aif1_hw_params(struct snd_pcm_substream *substream, | |
115 | struct snd_pcm_hw_params *params) | |
116 | { | |
117 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
118 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | |
119 | int ret; | |
120 | ||
121 | ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, | |
122 | 19200000, SND_SOC_CLOCK_IN); | |
123 | if (ret < 0) | |
124 | dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); | |
125 | ||
126 | ret = snd_soc_dai_set_pll(codec_dai, 0, | |
127 | DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000); | |
128 | if (ret < 0) { | |
129 | dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); | |
130 | return -EIO; | |
131 | } | |
132 | ||
133 | return ret; | |
134 | } | |
135 | ||
136 | static int aif1_hw_free(struct snd_pcm_substream *substream) | |
137 | { | |
138 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
139 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | |
140 | int ret; | |
141 | ||
142 | ret = snd_soc_dai_set_pll(codec_dai, 0, | |
143 | DA7213_SYSCLK_MCLK, 0, 0); | |
144 | if (ret < 0) { | |
145 | dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); | |
146 | return -EIO; | |
147 | } | |
148 | ||
149 | return ret; | |
150 | } | |
151 | ||
152 | static const struct snd_soc_ops aif1_ops = { | |
153 | .startup = aif1_startup, | |
154 | }; | |
155 | ||
156 | static const struct snd_soc_ops ssp2_ops = { | |
157 | .hw_params = aif1_hw_params, | |
158 | .hw_free = aif1_hw_free, | |
159 | ||
160 | }; | |
161 | ||
162 | static struct snd_soc_dai_link dailink[] = { | |
163 | [MERR_DPCM_AUDIO] = { | |
164 | .name = "Audio Port", | |
165 | .stream_name = "Audio", | |
166 | .cpu_dai_name = "media-cpu-dai", | |
167 | .codec_dai_name = "snd-soc-dummy-dai", | |
168 | .codec_name = "snd-soc-dummy", | |
169 | .platform_name = "sst-mfld-platform", | |
170 | .nonatomic = true, | |
171 | .dynamic = 1, | |
172 | .dpcm_playback = 1, | |
173 | .dpcm_capture = 1, | |
174 | .ops = &aif1_ops, | |
175 | }, | |
176 | [MERR_DPCM_DEEP_BUFFER] = { | |
177 | .name = "Deep-Buffer Audio Port", | |
178 | .stream_name = "Deep-Buffer Audio", | |
179 | .cpu_dai_name = "deepbuffer-cpu-dai", | |
180 | .codec_dai_name = "snd-soc-dummy-dai", | |
181 | .codec_name = "snd-soc-dummy", | |
182 | .platform_name = "sst-mfld-platform", | |
183 | .nonatomic = true, | |
184 | .dynamic = 1, | |
185 | .dpcm_playback = 1, | |
186 | .ops = &aif1_ops, | |
187 | }, | |
82cf89de PLB |
188 | /* CODEC<->CODEC link */ |
189 | /* back ends */ | |
190 | { | |
191 | .name = "SSP2-Codec", | |
149f7757 | 192 | .id = 0, |
82cf89de PLB |
193 | .cpu_dai_name = "ssp2-port", |
194 | .platform_name = "sst-mfld-platform", | |
195 | .no_pcm = 1, | |
196 | .codec_dai_name = "da7213-hifi", | |
197 | .codec_name = "i2c-DLGS7213:00", | |
198 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
199 | | SND_SOC_DAIFMT_CBS_CFS, | |
200 | .be_hw_params_fixup = codec_fixup, | |
201 | .nonatomic = true, | |
202 | .dpcm_playback = 1, | |
203 | .dpcm_capture = 1, | |
204 | .ops = &ssp2_ops, | |
205 | }, | |
206 | }; | |
207 | ||
208 | /* SoC card */ | |
209 | static struct snd_soc_card bytcht_da7213_card = { | |
210 | .name = "bytcht-da7213", | |
211 | .owner = THIS_MODULE, | |
212 | .dai_link = dailink, | |
213 | .num_links = ARRAY_SIZE(dailink), | |
214 | .controls = controls, | |
215 | .num_controls = ARRAY_SIZE(controls), | |
216 | .dapm_widgets = dapm_widgets, | |
217 | .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), | |
218 | .dapm_routes = audio_map, | |
219 | .num_dapm_routes = ARRAY_SIZE(audio_map), | |
220 | }; | |
221 | ||
2be2d579 | 222 | static char codec_name[SND_ACPI_I2C_ID_LEN]; |
82cf89de PLB |
223 | |
224 | static int bytcht_da7213_probe(struct platform_device *pdev) | |
225 | { | |
82cf89de | 226 | struct snd_soc_card *card; |
7feb2f78 | 227 | struct snd_soc_acpi_mach *mach; |
82cf89de PLB |
228 | const char *i2c_name = NULL; |
229 | int dai_index = 0; | |
22a317a0 PLB |
230 | int ret_val = 0; |
231 | int i; | |
82cf89de PLB |
232 | |
233 | mach = (&pdev->dev)->platform_data; | |
234 | card = &bytcht_da7213_card; | |
235 | card->dev = &pdev->dev; | |
236 | ||
237 | /* fix index of codec dai */ | |
82cf89de PLB |
238 | for (i = 0; i < ARRAY_SIZE(dailink); i++) { |
239 | if (!strcmp(dailink[i].codec_name, "i2c-DLGS7213:00")) { | |
240 | dai_index = i; | |
241 | break; | |
242 | } | |
243 | } | |
244 | ||
245 | /* fixup codec name based on HID */ | |
3a147959 | 246 | i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); |
22a317a0 | 247 | if (i2c_name) { |
82cf89de PLB |
248 | snprintf(codec_name, sizeof(codec_name), |
249 | "%s%s", "i2c-", i2c_name); | |
250 | dailink[dai_index].codec_name = codec_name; | |
251 | } | |
252 | ||
253 | ret_val = devm_snd_soc_register_card(&pdev->dev, card); | |
254 | if (ret_val) { | |
255 | dev_err(&pdev->dev, | |
256 | "snd_soc_register_card failed %d\n", ret_val); | |
257 | return ret_val; | |
258 | } | |
259 | platform_set_drvdata(pdev, card); | |
260 | return ret_val; | |
261 | } | |
262 | ||
263 | static struct platform_driver bytcht_da7213_driver = { | |
264 | .driver = { | |
265 | .name = "bytcht_da7213", | |
266 | }, | |
267 | .probe = bytcht_da7213_probe, | |
268 | }; | |
269 | module_platform_driver(bytcht_da7213_driver); | |
270 | ||
271 | MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver"); | |
272 | MODULE_AUTHOR("Pierre-Louis Bossart"); | |
273 | MODULE_LICENSE("GPL v2"); | |
274 | MODULE_ALIAS("platform:bytcht_da7213"); |