diff options
-rw-r--r-- | sound/pci/hda/Kconfig | 13 | ||||
-rw-r--r-- | sound/pci/hda/Makefile | 4 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.c | 1 | ||||
-rw-r--r-- | sound/pci/hda/patch_cirrus.c | 937 |
4 files changed, 955 insertions, 0 deletions
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index b8a77f9b0827..55545e0818b5 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig | |||
@@ -148,6 +148,19 @@ config SND_HDA_ELD | |||
148 | def_bool y | 148 | def_bool y |
149 | depends on SND_HDA_CODEC_INTELHDMI | 149 | depends on SND_HDA_CODEC_INTELHDMI |
150 | 150 | ||
151 | config SND_HDA_CODEC_CIRRUS | ||
152 | bool "Build Cirrus Logic codec support" | ||
153 | depends on SND_HDA_INTEL | ||
154 | default y | ||
155 | help | ||
156 | Say Y here to include Cirrus Logic codec support in | ||
157 | snd-hda-intel driver, such as CS4206. | ||
158 | |||
159 | When the HD-audio driver is built as a module, the codec | ||
160 | support code is also built as another module, | ||
161 | snd-hda-codec-cirrus. | ||
162 | This module is automatically loaded at probing. | ||
163 | |||
151 | config SND_HDA_CODEC_CONEXANT | 164 | config SND_HDA_CODEC_CONEXANT |
152 | bool "Build Conexant HD-audio codec support" | 165 | bool "Build Conexant HD-audio codec support" |
153 | default y | 166 | default y |
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index e3081d4586cc..315a1c4f8998 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile | |||
@@ -13,6 +13,7 @@ snd-hda-codec-analog-objs := patch_analog.o | |||
13 | snd-hda-codec-idt-objs := patch_sigmatel.o | 13 | snd-hda-codec-idt-objs := patch_sigmatel.o |
14 | snd-hda-codec-si3054-objs := patch_si3054.o | 14 | snd-hda-codec-si3054-objs := patch_si3054.o |
15 | snd-hda-codec-atihdmi-objs := patch_atihdmi.o | 15 | snd-hda-codec-atihdmi-objs := patch_atihdmi.o |
16 | snd-hda-codec-cirrus-objs := patch_cirrus.o | ||
16 | snd-hda-codec-ca0110-objs := patch_ca0110.o | 17 | snd-hda-codec-ca0110-objs := patch_ca0110.o |
17 | snd-hda-codec-conexant-objs := patch_conexant.o | 18 | snd-hda-codec-conexant-objs := patch_conexant.o |
18 | snd-hda-codec-via-objs := patch_via.o | 19 | snd-hda-codec-via-objs := patch_via.o |
@@ -41,6 +42,9 @@ endif | |||
41 | ifdef CONFIG_SND_HDA_CODEC_ATIHDMI | 42 | ifdef CONFIG_SND_HDA_CODEC_ATIHDMI |
42 | obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-atihdmi.o | 43 | obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-atihdmi.o |
43 | endif | 44 | endif |
45 | ifdef CONFIG_SND_HDA_CODEC_CIRRUS | ||
46 | obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-cirrus.o | ||
47 | endif | ||
44 | ifdef CONFIG_SND_HDA_CODEC_CA0110 | 48 | ifdef CONFIG_SND_HDA_CODEC_CA0110 |
45 | obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-ca0110.o | 49 | obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-ca0110.o |
46 | endif | 50 | endif |
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 263d124de611..eea91c3bd420 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c | |||
@@ -44,6 +44,7 @@ struct hda_vendor_id { | |||
44 | /* codec vendor labels */ | 44 | /* codec vendor labels */ |
45 | static struct hda_vendor_id hda_vendor_ids[] = { | 45 | static struct hda_vendor_id hda_vendor_ids[] = { |
46 | { 0x1002, "ATI" }, | 46 | { 0x1002, "ATI" }, |
47 | { 0x1013, "Cirrus Logic" }, | ||
47 | { 0x1057, "Motorola" }, | 48 | { 0x1057, "Motorola" }, |
48 | { 0x1095, "Silicon Image" }, | 49 | { 0x1095, "Silicon Image" }, |
49 | { 0x10de, "Nvidia" }, | 50 | { 0x10de, "Nvidia" }, |
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c new file mode 100644 index 000000000000..c4dc12e85732 --- /dev/null +++ b/sound/pci/hda/patch_cirrus.c | |||
@@ -0,0 +1,937 @@ | |||
1 | /* | ||
2 | * HD audio interface patch for Cirrus Logic CS420x chip | ||
3 | * | ||
4 | * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de> | ||
5 | * | ||
6 | * This driver is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This driver is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
19 | */ | ||
20 | |||
21 | #include <linux/init.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/pci.h> | ||
25 | #include <sound/core.h> | ||
26 | #include "hda_codec.h" | ||
27 | #include "hda_local.h" | ||
28 | |||
29 | /* | ||
30 | */ | ||
31 | |||
32 | struct cs_spec { | ||
33 | struct auto_pin_cfg autocfg; | ||
34 | struct hda_multi_out multiout; | ||
35 | struct snd_kcontrol *vmaster_sw; | ||
36 | struct snd_kcontrol *vmaster_vol; | ||
37 | |||
38 | hda_nid_t dac_nid[AUTO_CFG_MAX_OUTS]; | ||
39 | hda_nid_t slave_dig_outs[2]; | ||
40 | |||
41 | unsigned int input_idx[AUTO_PIN_LAST]; | ||
42 | unsigned int capsrc_idx[AUTO_PIN_LAST]; | ||
43 | hda_nid_t adc_nid[AUTO_PIN_LAST]; | ||
44 | unsigned int adc_idx[AUTO_PIN_LAST]; | ||
45 | unsigned int num_inputs; | ||
46 | unsigned int cur_input; | ||
47 | unsigned int automic_idx; | ||
48 | hda_nid_t cur_adc; | ||
49 | unsigned int cur_adc_stream_tag; | ||
50 | unsigned int cur_adc_format; | ||
51 | hda_nid_t dig_in; | ||
52 | |||
53 | struct hda_bind_ctls *capture_bind[2]; | ||
54 | |||
55 | struct hda_pcm pcm_rec[2]; /* PCM information */ | ||
56 | |||
57 | unsigned int hp_detect:1; | ||
58 | unsigned int mic_detect:1; | ||
59 | unsigned int built_up:1; | ||
60 | }; | ||
61 | |||
62 | #define HP_EVENT 1 | ||
63 | #define MIC_EVENT 2 | ||
64 | |||
65 | /* | ||
66 | * PCM callbacks | ||
67 | */ | ||
68 | static int cs_playback_pcm_open(struct hda_pcm_stream *hinfo, | ||
69 | struct hda_codec *codec, | ||
70 | struct snd_pcm_substream *substream) | ||
71 | { | ||
72 | struct cs_spec *spec = codec->spec; | ||
73 | return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, | ||
74 | hinfo); | ||
75 | } | ||
76 | |||
77 | static int cs_playback_pcm_prepare(struct hda_pcm_stream *hinfo, | ||
78 | struct hda_codec *codec, | ||
79 | unsigned int stream_tag, | ||
80 | unsigned int format, | ||
81 | struct snd_pcm_substream *substream) | ||
82 | { | ||
83 | struct cs_spec *spec = codec->spec; | ||
84 | return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, | ||
85 | stream_tag, format, substream); | ||
86 | } | ||
87 | |||
88 | static int cs_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, | ||
89 | struct hda_codec *codec, | ||
90 | struct snd_pcm_substream *substream) | ||
91 | { | ||
92 | struct cs_spec *spec = codec->spec; | ||
93 | return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); | ||
94 | } | ||
95 | |||
96 | /* | ||
97 | * Digital out | ||
98 | */ | ||
99 | static int cs_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, | ||
100 | struct hda_codec *codec, | ||
101 | struct snd_pcm_substream *substream) | ||
102 | { | ||
103 | struct cs_spec *spec = codec->spec; | ||
104 | return snd_hda_multi_out_dig_open(codec, &spec->multiout); | ||
105 | } | ||
106 | |||
107 | static int cs_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, | ||
108 | struct hda_codec *codec, | ||
109 | struct snd_pcm_substream *substream) | ||
110 | { | ||
111 | struct cs_spec *spec = codec->spec; | ||
112 | return snd_hda_multi_out_dig_close(codec, &spec->multiout); | ||
113 | } | ||
114 | |||
115 | static int cs_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, | ||
116 | struct hda_codec *codec, | ||
117 | unsigned int stream_tag, | ||
118 | unsigned int format, | ||
119 | struct snd_pcm_substream *substream) | ||
120 | { | ||
121 | struct cs_spec *spec = codec->spec; | ||
122 | return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, | ||
123 | format, substream); | ||
124 | } | ||
125 | |||
126 | static int cs_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, | ||
127 | struct hda_codec *codec, | ||
128 | struct snd_pcm_substream *substream) | ||
129 | { | ||
130 | struct cs_spec *spec = codec->spec; | ||
131 | return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); | ||
132 | } | ||
133 | |||
134 | /* | ||
135 | * Analog capture | ||
136 | */ | ||
137 | static int cs_capture_pcm_prepare(struct hda_pcm_stream *hinfo, | ||
138 | struct hda_codec *codec, | ||
139 | unsigned int stream_tag, | ||
140 | unsigned int format, | ||
141 | struct snd_pcm_substream *substream) | ||
142 | { | ||
143 | struct cs_spec *spec = codec->spec; | ||
144 | spec->cur_adc = spec->adc_nid[spec->cur_input]; | ||
145 | spec->cur_adc_stream_tag = stream_tag; | ||
146 | spec->cur_adc_format = format; | ||
147 | snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format); | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static int cs_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, | ||
152 | struct hda_codec *codec, | ||
153 | struct snd_pcm_substream *substream) | ||
154 | { | ||
155 | struct cs_spec *spec = codec->spec; | ||
156 | snd_hda_codec_cleanup_stream(codec, spec->cur_adc); | ||
157 | spec->cur_adc = 0; | ||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | /* | ||
162 | */ | ||
163 | static struct hda_pcm_stream cs_pcm_analog_playback = { | ||
164 | .substreams = 1, | ||
165 | .channels_min = 2, | ||
166 | .channels_max = 2, | ||
167 | .ops = { | ||
168 | .open = cs_playback_pcm_open, | ||
169 | .prepare = cs_playback_pcm_prepare, | ||
170 | .cleanup = cs_playback_pcm_cleanup | ||
171 | }, | ||
172 | }; | ||
173 | |||
174 | static struct hda_pcm_stream cs_pcm_analog_capture = { | ||
175 | .substreams = 1, | ||
176 | .channels_min = 2, | ||
177 | .channels_max = 2, | ||
178 | .ops = { | ||
179 | .prepare = cs_capture_pcm_prepare, | ||
180 | .cleanup = cs_capture_pcm_cleanup | ||
181 | }, | ||
182 | }; | ||
183 | |||
184 | static struct hda_pcm_stream cs_pcm_digital_playback = { | ||
185 | .substreams = 1, | ||
186 | .channels_min = 2, | ||
187 | .channels_max = 2, | ||
188 | .ops = { | ||
189 | .open = cs_dig_playback_pcm_open, | ||
190 | .close = cs_dig_playback_pcm_close, | ||
191 | .prepare = cs_dig_playback_pcm_prepare, | ||
192 | .cleanup = cs_dig_playback_pcm_cleanup | ||
193 | }, | ||
194 | }; | ||
195 | |||
196 | static struct hda_pcm_stream cs_pcm_digital_capture = { | ||
197 | .substreams = 1, | ||
198 | .channels_min = 2, | ||
199 | .channels_max = 2, | ||
200 | }; | ||
201 | |||
202 | static int cs_build_pcms(struct hda_codec *codec) | ||
203 | { | ||
204 | struct cs_spec *spec = codec->spec; | ||
205 | struct hda_pcm *info = spec->pcm_rec; | ||
206 | |||
207 | codec->pcm_info = info; | ||
208 | codec->num_pcms = 0; | ||
209 | |||
210 | info->name = "Cirrus Analog"; | ||
211 | info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cs_pcm_analog_playback; | ||
212 | info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dac_nid[0]; | ||
213 | info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = | ||
214 | spec->multiout.max_channels; | ||
215 | info->stream[SNDRV_PCM_STREAM_CAPTURE] = cs_pcm_analog_capture; | ||
216 | info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_inputs; | ||
217 | info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = | ||
218 | spec->adc_nid[spec->cur_input]; | ||
219 | codec->num_pcms++; | ||
220 | |||
221 | if (!spec->multiout.dig_out_nid && !spec->dig_in) | ||
222 | return 0; | ||
223 | |||
224 | info++; | ||
225 | info->name = "Cirrus Digital"; | ||
226 | info->pcm_type = spec->autocfg.dig_out_type[0]; | ||
227 | if (!info->pcm_type) | ||
228 | info->pcm_type = HDA_PCM_TYPE_SPDIF; | ||
229 | if (spec->multiout.dig_out_nid) { | ||
230 | info->stream[SNDRV_PCM_STREAM_PLAYBACK] = | ||
231 | cs_pcm_digital_playback; | ||
232 | info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = | ||
233 | spec->multiout.dig_out_nid; | ||
234 | } | ||
235 | if (spec->dig_in) { | ||
236 | info->stream[SNDRV_PCM_STREAM_CAPTURE] = | ||
237 | cs_pcm_digital_capture; | ||
238 | info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in; | ||
239 | } | ||
240 | codec->num_pcms++; | ||
241 | |||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | static hda_nid_t get_dac(struct hda_codec *codec, hda_nid_t pin) | ||
246 | { | ||
247 | hda_nid_t dac; | ||
248 | if (!pin) | ||
249 | return 0; | ||
250 | if (snd_hda_get_connections(codec, pin, &dac, 1) != 1) | ||
251 | return 0; | ||
252 | return dac; | ||
253 | } | ||
254 | |||
255 | static const char *dir_sfx[2] = { "Playback", "Capture" }; | ||
256 | |||
257 | static int add_mute(struct hda_codec *codec, const char *name, int index, | ||
258 | unsigned int pval, int dir, struct snd_kcontrol **kctlp) | ||
259 | { | ||
260 | char tmp[32]; | ||
261 | struct snd_kcontrol_new knew = | ||
262 | HDA_CODEC_MUTE_IDX(tmp, index, 0, 0, HDA_OUTPUT); | ||
263 | knew.private_value = pval; | ||
264 | snprintf(tmp, sizeof(tmp), "%s %s Switch", name, dir_sfx[dir]); | ||
265 | *kctlp = snd_ctl_new1(&knew, codec); | ||
266 | return snd_hda_ctl_add(codec, *kctlp); | ||
267 | } | ||
268 | |||
269 | static int add_volume(struct hda_codec *codec, const char *name, | ||
270 | int index, unsigned int pval, int dir, | ||
271 | struct snd_kcontrol **kctlp) | ||
272 | { | ||
273 | char tmp[32]; | ||
274 | struct snd_kcontrol_new knew = | ||
275 | HDA_CODEC_VOLUME_IDX(tmp, index, 0, 0, HDA_OUTPUT); | ||
276 | knew.private_value = pval; | ||
277 | snprintf(tmp, sizeof(tmp), "%s %s Volume", name, dir_sfx[dir]); | ||
278 | *kctlp = snd_ctl_new1(&knew, codec); | ||
279 | return snd_hda_ctl_add(codec, *kctlp); | ||
280 | } | ||
281 | |||
282 | static void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac) | ||
283 | { | ||
284 | unsigned int caps; | ||
285 | |||
286 | /* set the upper-limit for mixer amp to 0dB */ | ||
287 | caps = query_amp_caps(codec, dac, HDA_OUTPUT); | ||
288 | caps &= ~(0x7f << AC_AMPCAP_NUM_STEPS_SHIFT); | ||
289 | caps |= ((caps >> AC_AMPCAP_OFFSET_SHIFT) & 0x7f) | ||
290 | << AC_AMPCAP_NUM_STEPS_SHIFT; | ||
291 | snd_hda_override_amp_caps(codec, dac, HDA_OUTPUT, caps); | ||
292 | } | ||
293 | |||
294 | static int add_vmaster(struct hda_codec *codec, hda_nid_t dac) | ||
295 | { | ||
296 | struct cs_spec *spec = codec->spec; | ||
297 | unsigned int tlv[4]; | ||
298 | int err; | ||
299 | |||
300 | spec->vmaster_sw = | ||
301 | snd_ctl_make_virtual_master("Master Playback Switch", NULL); | ||
302 | err = snd_hda_ctl_add(codec, spec->vmaster_sw); | ||
303 | if (err < 0) | ||
304 | return err; | ||
305 | |||
306 | snd_hda_set_vmaster_tlv(codec, dac, HDA_OUTPUT, tlv); | ||
307 | spec->vmaster_vol = | ||
308 | snd_ctl_make_virtual_master("Master Playback Volume", tlv); | ||
309 | err = snd_hda_ctl_add(codec, spec->vmaster_vol); | ||
310 | if (err < 0) | ||
311 | return err; | ||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | static int add_output(struct hda_codec *codec, hda_nid_t dac, int idx, | ||
316 | int num_ctls, int type) | ||
317 | { | ||
318 | struct cs_spec *spec = codec->spec; | ||
319 | const char *name; | ||
320 | int err, index; | ||
321 | struct snd_kcontrol *kctl; | ||
322 | static char *speakers[] = { | ||
323 | "Front Speaker", "Surround Speaker", "Bass Speaker" | ||
324 | }; | ||
325 | static char *line_outs[] = { | ||
326 | "Front Line-Out", "Surround Line-Out", "Bass Line-Out" | ||
327 | }; | ||
328 | |||
329 | fix_volume_caps(codec, dac); | ||
330 | if (!spec->vmaster_sw) { | ||
331 | err = add_vmaster(codec, dac); | ||
332 | if (err < 0) | ||
333 | return err; | ||
334 | } | ||
335 | |||
336 | index = 0; | ||
337 | switch (type) { | ||
338 | case AUTO_PIN_HP_OUT: | ||
339 | name = "Headphone"; | ||
340 | index = idx; | ||
341 | break; | ||
342 | case AUTO_PIN_SPEAKER_OUT: | ||
343 | if (num_ctls > 1) | ||
344 | name = speakers[idx]; | ||
345 | else | ||
346 | name = "Speaker"; | ||
347 | break; | ||
348 | default: | ||
349 | if (num_ctls > 1) | ||
350 | name = line_outs[idx]; | ||
351 | else | ||
352 | name = "Line-Out"; | ||
353 | break; | ||
354 | } | ||
355 | |||
356 | err = add_mute(codec, name, index, | ||
357 | HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT), 0, &kctl); | ||
358 | if (err < 0) | ||
359 | return err; | ||
360 | err = snd_ctl_add_slave(spec->vmaster_sw, kctl); | ||
361 | if (err < 0) | ||
362 | return err; | ||
363 | |||
364 | err = add_volume(codec, name, index, | ||
365 | HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT), 0, &kctl); | ||
366 | if (err < 0) | ||
367 | return err; | ||
368 | err = snd_ctl_add_slave(spec->vmaster_vol, kctl); | ||
369 | if (err < 0) | ||
370 | return err; | ||
371 | |||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | static int build_output(struct hda_codec *codec) | ||
376 | { | ||
377 | struct cs_spec *spec = codec->spec; | ||
378 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
379 | int i, err, extra_nids; | ||
380 | hda_nid_t dac; | ||
381 | |||
382 | for (i = 0; i < cfg->line_outs; i++) { | ||
383 | dac = get_dac(codec, cfg->line_out_pins[i]); | ||
384 | if (!dac) | ||
385 | break; | ||
386 | spec->dac_nid[i] = dac; | ||
387 | err = add_output(codec, dac, i, cfg->line_outs, | ||
388 | cfg->line_out_type); | ||
389 | if (err < 0) | ||
390 | return err; | ||
391 | } | ||
392 | spec->multiout.num_dacs = i; | ||
393 | spec->multiout.dac_nids = spec->dac_nid; | ||
394 | spec->multiout.max_channels = i * 2; | ||
395 | |||
396 | /* add HP and speakers */ | ||
397 | extra_nids = 0; | ||
398 | for (i = 0; i < cfg->hp_outs; i++) { | ||
399 | dac = get_dac(codec, cfg->hp_pins[i]); | ||
400 | if (!dac) | ||
401 | break; | ||
402 | if (!i) | ||
403 | spec->multiout.hp_nid = dac; | ||
404 | else | ||
405 | spec->multiout.extra_out_nid[extra_nids++] = dac; | ||
406 | err = add_output(codec, dac, i, cfg->hp_outs, AUTO_PIN_HP_OUT); | ||
407 | if (err < 0) | ||
408 | return err; | ||
409 | } | ||
410 | for (i = 0; i < cfg->speaker_outs; i++) { | ||
411 | dac = get_dac(codec, cfg->speaker_pins[i]); | ||
412 | if (!dac) | ||
413 | break; | ||
414 | spec->multiout.extra_out_nid[extra_nids++] = dac; | ||
415 | err = add_output(codec, dac, i, cfg->speaker_outs, | ||
416 | AUTO_PIN_SPEAKER_OUT); | ||
417 | if (err < 0) | ||
418 | return err; | ||
419 | } | ||
420 | |||
421 | if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) { | ||
422 | cfg->speaker_outs = cfg->line_outs; | ||
423 | memcpy(cfg->speaker_pins, cfg->line_out_pins, | ||
424 | sizeof(cfg->speaker_pins)); | ||
425 | cfg->line_outs = 0; | ||
426 | } | ||
427 | |||
428 | return 0; | ||
429 | } | ||
430 | |||
431 | /* | ||
432 | */ | ||
433 | |||
434 | static struct snd_kcontrol_new cs_capture_ctls[] = { | ||
435 | HDA_BIND_SW("Capture Switch", 0), | ||
436 | HDA_BIND_VOL("Capture Volume", 0), | ||
437 | }; | ||
438 | |||
439 | static int change_cur_input(struct hda_codec *codec, unsigned int idx) | ||
440 | { | ||
441 | struct cs_spec *spec = codec->spec; | ||
442 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
443 | |||
444 | if (spec->cur_input == idx) | ||
445 | return 0; | ||
446 | if (spec->cur_adc && spec->cur_adc != spec->adc_nid[idx]) { | ||
447 | /* stream is running, let's swap the current ADC */ | ||
448 | snd_hda_codec_cleanup_stream(codec, spec->cur_adc); | ||
449 | spec->cur_adc = spec->adc_nid[idx]; | ||
450 | snd_hda_codec_setup_stream(codec, spec->cur_adc, | ||
451 | spec->cur_adc_stream_tag, 0, | ||
452 | spec->cur_adc_format); | ||
453 | } | ||
454 | snd_hda_codec_write(codec, spec->cur_adc, 0, | ||
455 | AC_VERB_SET_CONNECT_SEL, | ||
456 | spec->adc_idx[idx]); | ||
457 | spec->cur_input = idx; | ||
458 | return 1; | ||
459 | } | ||
460 | |||
461 | static int cs_capture_source_info(struct snd_kcontrol *kcontrol, | ||
462 | struct snd_ctl_elem_info *uinfo) | ||
463 | { | ||
464 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); | ||
465 | struct cs_spec *spec = codec->spec; | ||
466 | unsigned int idx; | ||
467 | |||
468 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
469 | uinfo->count = 1; | ||
470 | uinfo->value.enumerated.items = spec->num_inputs; | ||
471 | if (uinfo->value.enumerated.item >= spec->num_inputs) | ||
472 | uinfo->value.enumerated.item = spec->num_inputs - 1; | ||
473 | idx = spec->input_idx[uinfo->value.enumerated.item]; | ||
474 | strcpy(uinfo->value.enumerated.name, auto_pin_cfg_labels[idx]); | ||
475 | return 0; | ||
476 | } | ||
477 | |||
478 | static int cs_capture_source_get(struct snd_kcontrol *kcontrol, | ||
479 | struct snd_ctl_elem_value *ucontrol) | ||
480 | { | ||
481 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); | ||
482 | struct cs_spec *spec = codec->spec; | ||
483 | ucontrol->value.enumerated.item[0] = spec->capsrc_idx[spec->cur_input]; | ||
484 | return 0; | ||
485 | } | ||
486 | |||
487 | static int cs_capture_source_put(struct snd_kcontrol *kcontrol, | ||
488 | struct snd_ctl_elem_value *ucontrol) | ||
489 | { | ||
490 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); | ||
491 | struct cs_spec *spec = codec->spec; | ||
492 | unsigned int idx = ucontrol->value.enumerated.item[0]; | ||
493 | |||
494 | if (idx >= spec->num_inputs) | ||
495 | return -EINVAL; | ||
496 | idx = spec->input_idx[idx]; | ||
497 | return change_cur_input(codec, idx); | ||
498 | } | ||
499 | |||
500 | static struct snd_kcontrol_new cs_capture_source = { | ||
501 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
502 | .name = "Capture Source", | ||
503 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
504 | .info = cs_capture_source_info, | ||
505 | .get = cs_capture_source_get, | ||
506 | .put = cs_capture_source_put, | ||
507 | }; | ||
508 | |||
509 | |||
510 | static int is_ext_mic(struct hda_codec *codec, unsigned int idx) | ||
511 | { | ||
512 | struct cs_spec *spec = codec->spec; | ||
513 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
514 | hda_nid_t pin = cfg->input_pins[idx]; | ||
515 | unsigned int val = snd_hda_query_pin_caps(codec, pin); | ||
516 | if (!(val & AC_PINCAP_PRES_DETECT)) | ||
517 | return 0; | ||
518 | val = snd_hda_codec_get_pincfg(codec, pin); | ||
519 | return (get_defcfg_connect(val) == AC_JACK_PORT_COMPLEX); | ||
520 | } | ||
521 | |||
522 | static hda_nid_t get_adc(struct hda_codec *codec, hda_nid_t pin, | ||
523 | unsigned int *idxp) | ||
524 | { | ||
525 | int i; | ||
526 | hda_nid_t nid; | ||
527 | |||
528 | nid = codec->start_nid; | ||
529 | for (i = 0; i < codec->num_nodes; i++, nid++) { | ||
530 | hda_nid_t pins[2]; | ||
531 | unsigned int type; | ||
532 | int j, nums; | ||
533 | type = (get_wcaps(codec, nid) & AC_WCAP_TYPE) | ||
534 | >> AC_WCAP_TYPE_SHIFT; | ||
535 | if (type != AC_WID_AUD_IN) | ||
536 | continue; | ||
537 | nums = snd_hda_get_connections(codec, nid, pins, | ||
538 | ARRAY_SIZE(pins)); | ||
539 | if (nums <= 0) | ||
540 | continue; | ||
541 | for (j = 0; j < nums; j++) { | ||
542 | if (pins[j] == pin) { | ||
543 | *idxp = j; | ||
544 | return nid; | ||
545 | } | ||
546 | } | ||
547 | } | ||
548 | return 0; | ||
549 | } | ||
550 | |||
551 | static struct hda_bind_ctls *make_bind_capture(struct hda_codec *codec, | ||
552 | struct hda_ctl_ops *ops) | ||
553 | { | ||
554 | struct cs_spec *spec = codec->spec; | ||
555 | struct hda_bind_ctls *bind; | ||
556 | int i, n; | ||
557 | |||
558 | bind = kzalloc(sizeof(*bind) + sizeof(long) * (spec->num_inputs + 1), | ||
559 | GFP_KERNEL); | ||
560 | if (!bind) | ||
561 | return NULL; | ||
562 | bind->ops = ops; | ||
563 | n = 0; | ||
564 | for (i = 0; i < AUTO_PIN_LAST; i++) { | ||
565 | if (!spec->adc_nid[i]) | ||
566 | continue; | ||
567 | bind->values[n++] = | ||
568 | HDA_COMPOSE_AMP_VAL(spec->adc_nid[i], 3, | ||
569 | spec->adc_idx[i], HDA_INPUT); | ||
570 | } | ||
571 | return bind; | ||
572 | } | ||
573 | |||
574 | static int build_input(struct hda_codec *codec) | ||
575 | { | ||
576 | struct cs_spec *spec = codec->spec; | ||
577 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
578 | int i, n, err; | ||
579 | |||
580 | for (i = 0; i < AUTO_PIN_LAST; i++) { | ||
581 | hda_nid_t pin = cfg->input_pins[i]; | ||
582 | struct snd_kcontrol *kctl; | ||
583 | if (!pin) | ||
584 | continue; | ||
585 | spec->input_idx[spec->num_inputs] = i; | ||
586 | spec->capsrc_idx[i] = spec->num_inputs++; | ||
587 | spec->cur_input = i; | ||
588 | spec->adc_nid[i] = get_adc(codec, pin, &spec->adc_idx[i]); | ||
589 | } | ||
590 | if (!spec->num_inputs) | ||
591 | return 0; | ||
592 | |||
593 | /* check whether the automatic mic switch is available */ | ||
594 | if (spec->num_inputs == 2 && | ||
595 | spec->adc_nid[AUTO_PIN_MIC] && spec->adc_nid[AUTO_PIN_FRONT_MIC]) { | ||
596 | if (is_ext_mic(codec, cfg->input_pins[AUTO_PIN_FRONT_MIC])) { | ||
597 | if (!is_ext_mic(codec, cfg->input_pins[AUTO_PIN_MIC])) { | ||
598 | spec->mic_detect = 1; | ||
599 | spec->automic_idx = AUTO_PIN_FRONT_MIC; | ||
600 | } | ||
601 | } else { | ||
602 | if (is_ext_mic(codec, cfg->input_pins[AUTO_PIN_MIC])) { | ||
603 | spec->mic_detect = 1; | ||
604 | spec->automic_idx = AUTO_PIN_MIC; | ||
605 | } | ||
606 | } | ||
607 | } | ||
608 | |||
609 | /* make bind-capture */ | ||
610 | spec->capture_bind[0] = make_bind_capture(codec, &snd_hda_bind_sw); | ||
611 | spec->capture_bind[1] = make_bind_capture(codec, &snd_hda_bind_vol); | ||
612 | for (i = 0; i < 2; i++) { | ||
613 | struct snd_kcontrol *kctl; | ||
614 | if (!spec->capture_bind[i]) | ||
615 | return -ENOMEM; | ||
616 | kctl = snd_ctl_new1(&cs_capture_ctls[i], codec); | ||
617 | if (!kctl) | ||
618 | return -ENOMEM; | ||
619 | kctl->private_value = (long)spec->capture_bind[i]; | ||
620 | err = snd_hda_ctl_add(codec, kctl); | ||
621 | if (err < 0) | ||
622 | return err; | ||
623 | } | ||
624 | |||
625 | if (spec->num_inputs > 1 && !spec->mic_detect) { | ||
626 | err = snd_hda_ctl_add(codec, | ||
627 | snd_ctl_new1(&cs_capture_source, codec)); | ||
628 | if (err < 0) | ||
629 | return err; | ||
630 | } | ||
631 | |||
632 | return 0; | ||
633 | } | ||
634 | |||
635 | static int build_digital_output(struct hda_codec *codec) | ||
636 | { | ||
637 | struct cs_spec *spec = codec->spec; | ||
638 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
639 | hda_nid_t nid; | ||
640 | int err; | ||
641 | |||
642 | if (!cfg->dig_outs) | ||
643 | return 0; | ||
644 | if (snd_hda_get_connections(codec, cfg->dig_out_pins[0], &nid, 1) < 1) | ||
645 | return 0; | ||
646 | spec->multiout.dig_out_nid = nid; | ||
647 | err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); | ||
648 | if (err < 0) | ||
649 | return err; | ||
650 | err = snd_hda_create_spdif_share_sw(codec, &spec->multiout); | ||
651 | if (err < 0) | ||
652 | return err; | ||
653 | spec->multiout.share_spdif = 1; | ||
654 | if (cfg->dig_outs > 1 && | ||
655 | snd_hda_get_connections(codec, cfg->dig_out_pins[1], &nid, 1) > 0) { | ||
656 | spec->slave_dig_outs[0] = nid; | ||
657 | codec->slave_dig_outs = spec->slave_dig_outs; | ||
658 | } | ||
659 | return 0; | ||
660 | } | ||
661 | |||
662 | static int build_digital_input(struct hda_codec *codec) | ||
663 | { | ||
664 | struct cs_spec *spec = codec->spec; | ||
665 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
666 | int idx; | ||
667 | |||
668 | if (!cfg->dig_in_pin) | ||
669 | return 0; | ||
670 | spec->dig_in = get_adc(codec, cfg->dig_in_pin, &idx); | ||
671 | if (!spec->dig_in) | ||
672 | return 0; | ||
673 | return snd_hda_create_spdif_in_ctls(codec, spec->dig_in); | ||
674 | } | ||
675 | |||
676 | static void cs_automute(struct hda_codec *codec) | ||
677 | { | ||
678 | struct cs_spec *spec = codec->spec; | ||
679 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
680 | unsigned int caps, present, hp_present; | ||
681 | hda_nid_t nid; | ||
682 | int i; | ||
683 | |||
684 | hp_present = 0; | ||
685 | for (i = 0; i < cfg->hp_outs; i++) { | ||
686 | nid = cfg->hp_pins[i]; | ||
687 | caps = snd_hda_query_pin_caps(codec, nid); | ||
688 | if (!(caps & AC_PINCAP_PRES_DETECT)) | ||
689 | continue; | ||
690 | if (caps & AC_PINCAP_TRIG_REQ) | ||
691 | snd_hda_codec_read(codec, nid, 0, | ||
692 | AC_VERB_SET_PIN_SENSE, 0); | ||
693 | present = snd_hda_codec_read(codec, nid, 0, | ||
694 | AC_VERB_GET_PIN_SENSE, 0); | ||
695 | hp_present |= (present & AC_PINSENSE_PRESENCE) != 0; | ||
696 | if (hp_present) | ||
697 | break; | ||
698 | } | ||
699 | for (i = 0; i < cfg->speaker_outs; i++) { | ||
700 | nid = cfg->speaker_pins[i]; | ||
701 | snd_hda_codec_write(codec, nid, 0, | ||
702 | AC_VERB_SET_PIN_WIDGET_CONTROL, | ||
703 | hp_present ? 0 : PIN_OUT); | ||
704 | } | ||
705 | } | ||
706 | |||
707 | static void cs_automic(struct hda_codec *codec) | ||
708 | { | ||
709 | struct cs_spec *spec = codec->spec; | ||
710 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
711 | hda_nid_t nid; | ||
712 | unsigned int caps, present; | ||
713 | |||
714 | nid = cfg->input_pins[spec->automic_idx]; | ||
715 | caps = snd_hda_query_pin_caps(codec, nid); | ||
716 | if (caps & AC_PINCAP_TRIG_REQ) | ||
717 | snd_hda_codec_read(codec, nid, 0, AC_VERB_SET_PIN_SENSE, 0); | ||
718 | present = snd_hda_codec_read(codec, nid, 0, | ||
719 | AC_VERB_GET_PIN_SENSE, 0); | ||
720 | if (present & AC_PINSENSE_PRESENCE) | ||
721 | change_cur_input(codec, spec->automic_idx); | ||
722 | else { | ||
723 | unsigned int imic = (spec->automic_idx == AUTO_PIN_MIC) ? | ||
724 | AUTO_PIN_FRONT_MIC : AUTO_PIN_MIC; | ||
725 | change_cur_input(codec, imic); | ||
726 | } | ||
727 | } | ||
728 | |||
729 | /* | ||
730 | */ | ||
731 | |||
732 | static void init_output(struct hda_codec *codec) | ||
733 | { | ||
734 | struct cs_spec *spec = codec->spec; | ||
735 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
736 | int i; | ||
737 | |||
738 | /* mute first */ | ||
739 | for (i = 0; i < spec->multiout.num_dacs; i++) | ||
740 | snd_hda_codec_write(codec, spec->multiout.dac_nids[i], 0, | ||
741 | AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); | ||
742 | if (spec->multiout.hp_nid) | ||
743 | snd_hda_codec_write(codec, spec->multiout.hp_nid, 0, | ||
744 | AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); | ||
745 | for (i = 0; i < ARRAY_SIZE(spec->multiout.extra_out_nid); i++) { | ||
746 | if (!spec->multiout.extra_out_nid[i]) | ||
747 | break; | ||
748 | snd_hda_codec_write(codec, spec->multiout.extra_out_nid[i], 0, | ||
749 | AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); | ||
750 | } | ||
751 | |||
752 | /* set appropriate pin controls */ | ||
753 | for (i = 0; i < cfg->line_outs; i++) | ||
754 | snd_hda_codec_write(codec, cfg->line_out_pins[i], 0, | ||
755 | AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); | ||
756 | for (i = 0; i < cfg->hp_outs; i++) { | ||
757 | hda_nid_t nid = cfg->hp_pins[i]; | ||
758 | snd_hda_codec_write(codec, nid, 0, | ||
759 | AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP); | ||
760 | if (!cfg->speaker_outs) | ||
761 | continue; | ||
762 | if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) { | ||
763 | snd_hda_codec_write(codec, nid, 0, | ||
764 | AC_VERB_SET_UNSOLICITED_ENABLE, | ||
765 | AC_USRSP_EN | HP_EVENT); | ||
766 | spec->hp_detect = 1; | ||
767 | } | ||
768 | } | ||
769 | for (i = 0; i < cfg->speaker_outs; i++) | ||
770 | snd_hda_codec_write(codec, cfg->speaker_pins[i], 0, | ||
771 | AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); | ||
772 | if (spec->hp_detect) | ||
773 | cs_automute(codec); | ||
774 | } | ||
775 | |||
776 | static void init_input(struct hda_codec *codec) | ||
777 | { | ||
778 | struct cs_spec *spec = codec->spec; | ||
779 | struct auto_pin_cfg *cfg = &spec->autocfg; | ||
780 | int i; | ||
781 | |||
782 | for (i = 0; i < AUTO_PIN_LAST; i++) { | ||
783 | unsigned int ctl; | ||
784 | hda_nid_t pin = cfg->input_pins[i]; | ||
785 | if (!pin || !spec->adc_nid[i]) | ||
786 | continue; | ||
787 | /* set appropriate pin control and mute first */ | ||
788 | ctl = PIN_IN; | ||
789 | if (i <= AUTO_PIN_FRONT_MIC) { | ||
790 | unsigned int caps = snd_hda_query_pin_caps(codec, pin); | ||
791 | caps >>= AC_PINCAP_VREF_SHIFT; | ||
792 | if (caps & AC_PINCAP_VREF_80) | ||
793 | ctl = PIN_VREF80; | ||
794 | } | ||
795 | snd_hda_codec_write(codec, pin, 0, | ||
796 | AC_VERB_SET_PIN_WIDGET_CONTROL, ctl); | ||
797 | snd_hda_codec_write(codec, spec->adc_nid[i], 0, | ||
798 | AC_VERB_SET_AMP_GAIN_MUTE, | ||
799 | AMP_IN_MUTE(spec->adc_idx[i])); | ||
800 | if (spec->mic_detect && spec->automic_idx == i) | ||
801 | snd_hda_codec_write(codec, pin, 0, | ||
802 | AC_VERB_SET_UNSOLICITED_ENABLE, | ||
803 | AC_USRSP_EN | MIC_EVENT); | ||
804 | } | ||
805 | if (spec->mic_detect) | ||
806 | cs_automic(codec); | ||
807 | } | ||
808 | |||
809 | static int cs_init(struct hda_codec *codec) | ||
810 | { | ||
811 | struct cs_spec *spec = codec->spec; | ||
812 | |||
813 | if (!spec->built_up) | ||
814 | return 0; | ||
815 | init_output(codec); | ||
816 | init_input(codec); | ||
817 | return 0; | ||
818 | } | ||
819 | |||
820 | static int cs_build_controls(struct hda_codec *codec) | ||
821 | { | ||
822 | struct cs_spec *spec = codec->spec; | ||
823 | int err; | ||
824 | |||
825 | err = build_output(codec); | ||
826 | if (err < 0) | ||
827 | return err; | ||
828 | err = build_input(codec); | ||
829 | if (err < 0) | ||
830 | return err; | ||
831 | err = build_digital_output(codec); | ||
832 | if (err < 0) | ||
833 | return err; | ||
834 | err = build_digital_input(codec); | ||
835 | if (err < 0) | ||
836 | return err; | ||
837 | spec->built_up = 1; | ||
838 | return cs_init(codec); | ||
839 | } | ||
840 | |||
841 | static void cs_free(struct hda_codec *codec) | ||
842 | { | ||
843 | struct cs_spec *spec = codec->spec; | ||
844 | kfree(spec->capture_bind[0]); | ||
845 | kfree(spec->capture_bind[1]); | ||
846 | kfree(codec->spec); | ||
847 | } | ||
848 | |||
849 | static void cs_unsol_event(struct hda_codec *codec, unsigned int res) | ||
850 | { | ||
851 | switch ((res >> 26) & 0x7f) { | ||
852 | case HP_EVENT: | ||
853 | cs_automute(codec); | ||
854 | break; | ||
855 | case MIC_EVENT: | ||
856 | cs_automic(codec); | ||
857 | break; | ||
858 | } | ||
859 | } | ||
860 | |||
861 | static struct hda_codec_ops cs_patch_ops = { | ||
862 | .build_controls = cs_build_controls, | ||
863 | .build_pcms = cs_build_pcms, | ||
864 | .init = cs_init, | ||
865 | .free = cs_free, | ||
866 | .unsol_event = cs_unsol_event, | ||
867 | }; | ||
868 | |||
869 | static int cs_parse_auto_config(struct hda_codec *codec) | ||
870 | { | ||
871 | struct cs_spec *spec = codec->spec; | ||
872 | int err; | ||
873 | |||
874 | err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); | ||
875 | if (err < 0) | ||
876 | return err; | ||
877 | return 0; | ||
878 | } | ||
879 | |||
880 | |||
881 | static int patch_cs420x(struct hda_codec *codec) | ||
882 | { | ||
883 | struct cs_spec *spec; | ||
884 | int err; | ||
885 | |||
886 | spec = kzalloc(sizeof(*spec), GFP_KERNEL); | ||
887 | if (!spec) | ||
888 | return -ENOMEM; | ||
889 | codec->spec = spec; | ||
890 | |||
891 | err = cs_parse_auto_config(codec); | ||
892 | if (err < 0) | ||
893 | goto error; | ||
894 | |||
895 | codec->patch_ops = cs_patch_ops; | ||
896 | |||
897 | return 0; | ||
898 | |||
899 | error: | ||
900 | kfree(codec->spec); | ||
901 | codec->spec = NULL; | ||
902 | return err; | ||
903 | } | ||
904 | |||
905 | |||
906 | /* | ||
907 | * patch entries | ||
908 | */ | ||
909 | static struct hda_codec_preset snd_hda_preset_cirrus[] = { | ||
910 | { .id = 0x10134206, .name = "CS4206", .patch = patch_cs420x }, | ||
911 | { .id = 0x10134207, .name = "CS4207", .patch = patch_cs420x }, | ||
912 | {} /* terminator */ | ||
913 | }; | ||
914 | |||
915 | MODULE_ALIAS("snd-hda-codec-id:10134206"); | ||
916 | MODULE_ALIAS("snd-hda-codec-id:10134207"); | ||
917 | |||
918 | MODULE_LICENSE("GPL"); | ||
919 | MODULE_DESCRIPTION("Cirrus Logic HD-audio codec"); | ||
920 | |||
921 | static struct hda_codec_preset_list cirrus_list = { | ||
922 | .preset = snd_hda_preset_cirrus, | ||
923 | .owner = THIS_MODULE, | ||
924 | }; | ||
925 | |||
926 | static int __init patch_cirrus_init(void) | ||
927 | { | ||
928 | return snd_hda_add_codec_preset(&cirrus_list); | ||
929 | } | ||
930 | |||
931 | static void __exit patch_cirrus_exit(void) | ||
932 | { | ||
933 | snd_hda_delete_codec_preset(&cirrus_list); | ||
934 | } | ||
935 | |||
936 | module_init(patch_cirrus_init) | ||
937 | module_exit(patch_cirrus_exit) | ||