diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2007-12-23 13:50:57 -0500 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2008-01-31 11:29:44 -0500 |
commit | d0ce9946c52e7bdf95afb09553775cf28b752254 (patch) | |
tree | 684edc99cfd1def12b87abb5431c6b8f0ea2f716 /sound/pci/oxygen/oxygen_lib.c | |
parent | a9b3aa8a0a203b9b62e15c465ba7d4797a6a2c79 (diff) |
[ALSA] add CMI8788 driver
Add the snd-oxygen driver for the C-Media CMI8788 (Oxygen) chip, used on
the Asound A-8788, AuzenTech X-Meridian, Bgears b-Enspirer,
Club3D Theatron DTS, HT-Omega Claro, Razer Barracuda AC-1,
Sondigo Inferno, and TempoTec HIFIER sound cards.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/pci/oxygen/oxygen_lib.c')
-rw-r--r-- | sound/pci/oxygen/oxygen_lib.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c new file mode 100644 index 000000000000..6180cc858e60 --- /dev/null +++ b/sound/pci/oxygen/oxygen_lib.c | |||
@@ -0,0 +1,361 @@ | |||
1 | /* | ||
2 | * C-Media CMI8788 driver - main driver module | ||
3 | * | ||
4 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> | ||
5 | * | ||
6 | * | ||
7 | * This driver is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License, version 2. | ||
9 | * | ||
10 | * This driver is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this driver; if not, write to the Free Software | ||
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
18 | */ | ||
19 | |||
20 | #include <sound/driver.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | #include <linux/mutex.h> | ||
24 | #include <linux/pci.h> | ||
25 | #include <sound/ac97_codec.h> | ||
26 | #include <sound/asoundef.h> | ||
27 | #include <sound/core.h> | ||
28 | #include <sound/info.h> | ||
29 | #include <sound/mpu401.h> | ||
30 | #include <sound/pcm.h> | ||
31 | #include "oxygen.h" | ||
32 | |||
33 | MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); | ||
34 | MODULE_DESCRIPTION("C-Media CMI8788 helper library"); | ||
35 | MODULE_LICENSE("GPL"); | ||
36 | |||
37 | |||
38 | static irqreturn_t oxygen_interrupt(int dummy, void *dev_id) | ||
39 | { | ||
40 | struct oxygen *chip = dev_id; | ||
41 | unsigned int status, clear, elapsed_streams, i; | ||
42 | |||
43 | status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS); | ||
44 | if (!status) | ||
45 | return IRQ_NONE; | ||
46 | |||
47 | spin_lock(&chip->reg_lock); | ||
48 | |||
49 | clear = status & (OXYGEN_CHANNEL_A | | ||
50 | OXYGEN_CHANNEL_B | | ||
51 | OXYGEN_CHANNEL_C | | ||
52 | OXYGEN_CHANNEL_SPDIF | | ||
53 | OXYGEN_CHANNEL_MULTICH | | ||
54 | OXYGEN_CHANNEL_AC97 | | ||
55 | OXYGEN_INT_SPDIF_IN_CHANGE | | ||
56 | OXYGEN_INT_GPIO); | ||
57 | if (clear) { | ||
58 | if (clear & OXYGEN_INT_SPDIF_IN_CHANGE) | ||
59 | chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_CHANGE; | ||
60 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, | ||
61 | chip->interrupt_mask & ~clear); | ||
62 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, | ||
63 | chip->interrupt_mask); | ||
64 | } | ||
65 | |||
66 | elapsed_streams = status & chip->pcm_running; | ||
67 | |||
68 | spin_unlock(&chip->reg_lock); | ||
69 | |||
70 | for (i = 0; i < PCM_COUNT; ++i) | ||
71 | if ((elapsed_streams & (1 << i)) && chip->streams[i]) | ||
72 | snd_pcm_period_elapsed(chip->streams[i]); | ||
73 | |||
74 | if (status & OXYGEN_INT_SPDIF_IN_CHANGE) { | ||
75 | spin_lock(&chip->reg_lock); | ||
76 | i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); | ||
77 | if (i & OXYGEN_SPDIF_IN_CHANGE) { | ||
78 | oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i); | ||
79 | schedule_work(&chip->spdif_input_bits_work); | ||
80 | } | ||
81 | spin_unlock(&chip->reg_lock); | ||
82 | } | ||
83 | |||
84 | if (status & OXYGEN_INT_GPIO) | ||
85 | ; | ||
86 | |||
87 | if ((status & OXYGEN_INT_MIDI) && chip->midi) | ||
88 | snd_mpu401_uart_interrupt(0, chip->midi->private_data); | ||
89 | |||
90 | return IRQ_HANDLED; | ||
91 | } | ||
92 | |||
93 | static void oxygen_spdif_input_bits_changed(struct work_struct *work) | ||
94 | { | ||
95 | struct oxygen *chip = container_of(work, struct oxygen, | ||
96 | spdif_input_bits_work); | ||
97 | |||
98 | spin_lock_irq(&chip->reg_lock); | ||
99 | oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_IN_INVERT); | ||
100 | spin_unlock_irq(&chip->reg_lock); | ||
101 | msleep(1); | ||
102 | if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) | ||
103 | & OXYGEN_SPDIF_IN_VALID)) { | ||
104 | spin_lock_irq(&chip->reg_lock); | ||
105 | oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL, | ||
106 | OXYGEN_SPDIF_IN_INVERT); | ||
107 | spin_unlock_irq(&chip->reg_lock); | ||
108 | msleep(1); | ||
109 | if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) | ||
110 | & OXYGEN_SPDIF_IN_VALID)) { | ||
111 | spin_lock_irq(&chip->reg_lock); | ||
112 | oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, | ||
113 | OXYGEN_SPDIF_IN_INVERT); | ||
114 | spin_unlock_irq(&chip->reg_lock); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | if (chip->spdif_input_bits_ctl) { | ||
119 | spin_lock_irq(&chip->reg_lock); | ||
120 | chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE; | ||
121 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, | ||
122 | chip->interrupt_mask); | ||
123 | spin_unlock_irq(&chip->reg_lock); | ||
124 | |||
125 | snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, | ||
126 | &chip->spdif_input_bits_ctl->id); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | #ifdef CONFIG_PROC_FS | ||
131 | static void oxygen_proc_read(struct snd_info_entry *entry, | ||
132 | struct snd_info_buffer *buffer) | ||
133 | { | ||
134 | struct oxygen *chip = entry->private_data; | ||
135 | int i, j; | ||
136 | |||
137 | snd_iprintf(buffer, "CMI8788\n\n"); | ||
138 | for (i = 0; i < 0x100; i += 0x10) { | ||
139 | snd_iprintf(buffer, "%02x:", i); | ||
140 | for (j = 0; j < 0x10; ++j) | ||
141 | snd_iprintf(buffer, " %02x", oxygen_read8(chip, i + j)); | ||
142 | snd_iprintf(buffer, "\n"); | ||
143 | } | ||
144 | if (mutex_lock_interruptible(&chip->mutex) < 0) | ||
145 | return; | ||
146 | snd_iprintf(buffer, "\nAC97\n"); | ||
147 | for (i = 0; i < 0x80; i += 0x10) { | ||
148 | snd_iprintf(buffer, "%02x:", i); | ||
149 | for (j = 0; j < 0x10; j += 2) | ||
150 | snd_iprintf(buffer, " %04x", | ||
151 | oxygen_read_ac97(chip, 0, i + j)); | ||
152 | snd_iprintf(buffer, "\n"); | ||
153 | } | ||
154 | mutex_unlock(&chip->mutex); | ||
155 | } | ||
156 | |||
157 | static void __devinit oxygen_proc_init(struct oxygen *chip) | ||
158 | { | ||
159 | struct snd_info_entry *entry; | ||
160 | |||
161 | if (!snd_card_proc_new(chip->card, "cmi8788", &entry)) | ||
162 | snd_info_set_text_ops(entry, chip, oxygen_proc_read); | ||
163 | } | ||
164 | #else | ||
165 | #define oxygen_proc_init(chip) | ||
166 | #endif | ||
167 | |||
168 | static void __devinit oxygen_init(struct oxygen *chip) | ||
169 | { | ||
170 | unsigned int i; | ||
171 | |||
172 | chip->dac_routing = 1; | ||
173 | for (i = 0; i < 8; ++i) | ||
174 | chip->dac_volume[i] = 0xff; | ||
175 | chip->spdif_playback_enable = 1; | ||
176 | chip->spdif_bits = OXYGEN_SPDIF_C | OXYGEN_SPDIF_ORIGINAL | | ||
177 | (IEC958_AES1_CON_PCM_CODER << OXYGEN_SPDIF_CATEGORY_SHIFT); | ||
178 | chip->spdif_pcm_bits = chip->spdif_bits; | ||
179 | |||
180 | if (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_REVISION_2) | ||
181 | chip->revision = 2; | ||
182 | else | ||
183 | chip->revision = 1; | ||
184 | |||
185 | if (chip->revision == 1) | ||
186 | oxygen_set_bits8(chip, OXYGEN_MISC, OXYGEN_MISC_MAGIC); | ||
187 | |||
188 | oxygen_set_bits8(chip, OXYGEN_FUNCTION, | ||
189 | OXYGEN_FUNCTION_RESET_CODEC | | ||
190 | OXYGEN_FUNCTION_ENABLE_SPI_4_5); | ||
191 | oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT, 0x010a); | ||
192 | oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, 0x010a); | ||
193 | oxygen_write16(chip, OXYGEN_I2S_B_FORMAT, 0x010a); | ||
194 | oxygen_write16(chip, OXYGEN_I2S_C_FORMAT, 0x010a); | ||
195 | oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_MAGIC2); | ||
196 | oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits); | ||
197 | oxygen_write16(chip, OXYGEN_PLAY_ROUTING, 0x6c00); | ||
198 | oxygen_write8(chip, OXYGEN_REC_ROUTING, 0x10); | ||
199 | oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0x00); | ||
200 | oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING, 0xe4); | ||
201 | |||
202 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); | ||
203 | oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); | ||
204 | |||
205 | oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0x00); | ||
206 | oxygen_clear_bits16(chip, OXYGEN_AC97_OUT_CONFIG, | ||
207 | OXYGEN_AC97_OUT_MAGIC3); | ||
208 | oxygen_set_bits16(chip, OXYGEN_AC97_IN_CONFIG, | ||
209 | OXYGEN_AC97_IN_MAGIC3); | ||
210 | oxygen_write_ac97(chip, 0, AC97_RESET, 0); | ||
211 | msleep(1); | ||
212 | oxygen_ac97_set_bits(chip, 0, 0x70, 0x0300); | ||
213 | oxygen_ac97_set_bits(chip, 0, 0x64, 0x8043); | ||
214 | oxygen_ac97_set_bits(chip, 0, 0x62, 0x180f); | ||
215 | oxygen_write_ac97(chip, 0, AC97_MASTER, 0x0000); | ||
216 | oxygen_write_ac97(chip, 0, AC97_PC_BEEP, 0x8000); | ||
217 | oxygen_write_ac97(chip, 0, AC97_MIC, 0x8808); | ||
218 | oxygen_write_ac97(chip, 0, AC97_LINE, 0x0808); | ||
219 | oxygen_write_ac97(chip, 0, AC97_CD, 0x8808); | ||
220 | oxygen_write_ac97(chip, 0, AC97_VIDEO, 0x8808); | ||
221 | oxygen_write_ac97(chip, 0, AC97_AUX, 0x8808); | ||
222 | oxygen_write_ac97(chip, 0, AC97_REC_GAIN, 0x8000); | ||
223 | oxygen_write_ac97(chip, 0, AC97_CENTER_LFE_MASTER, 0x8080); | ||
224 | oxygen_write_ac97(chip, 0, AC97_SURROUND_MASTER, 0x8080); | ||
225 | oxygen_ac97_clear_bits(chip, 0, 0x72, 0x0001); | ||
226 | /* power down unused ADCs and DACs */ | ||
227 | oxygen_ac97_set_bits(chip, 0, AC97_POWERDOWN, | ||
228 | AC97_PD_PR0 | AC97_PD_PR1); | ||
229 | oxygen_ac97_set_bits(chip, 0, AC97_EXTENDED_STATUS, | ||
230 | AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK); | ||
231 | } | ||
232 | |||
233 | static void oxygen_card_free(struct snd_card *card) | ||
234 | { | ||
235 | struct oxygen *chip = card->private_data; | ||
236 | |||
237 | spin_lock_irq(&chip->reg_lock); | ||
238 | chip->interrupt_mask = 0; | ||
239 | chip->pcm_running = 0; | ||
240 | oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); | ||
241 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); | ||
242 | spin_unlock_irq(&chip->reg_lock); | ||
243 | if (chip->irq >= 0) { | ||
244 | free_irq(chip->irq, chip); | ||
245 | synchronize_irq(chip->irq); | ||
246 | } | ||
247 | flush_scheduled_work(); | ||
248 | chip->model->cleanup(chip); | ||
249 | mutex_destroy(&chip->mutex); | ||
250 | pci_release_regions(chip->pci); | ||
251 | pci_disable_device(chip->pci); | ||
252 | } | ||
253 | |||
254 | int __devinit oxygen_pci_probe(struct pci_dev *pci, int index, char *id, | ||
255 | const struct oxygen_model *model) | ||
256 | { | ||
257 | struct snd_card *card; | ||
258 | struct oxygen *chip; | ||
259 | int err; | ||
260 | |||
261 | card = snd_card_new(index, id, model->owner, sizeof *chip); | ||
262 | if (!card) | ||
263 | return -ENOMEM; | ||
264 | |||
265 | chip = card->private_data; | ||
266 | chip->card = card; | ||
267 | chip->pci = pci; | ||
268 | chip->irq = -1; | ||
269 | chip->model = model; | ||
270 | spin_lock_init(&chip->reg_lock); | ||
271 | mutex_init(&chip->mutex); | ||
272 | INIT_WORK(&chip->spdif_input_bits_work, | ||
273 | oxygen_spdif_input_bits_changed); | ||
274 | |||
275 | err = pci_enable_device(pci); | ||
276 | if (err < 0) | ||
277 | goto err_card; | ||
278 | |||
279 | err = pci_request_regions(pci, model->chip); | ||
280 | if (err < 0) { | ||
281 | snd_printk(KERN_ERR "cannot reserve PCI resources\n"); | ||
282 | goto err_pci_enable; | ||
283 | } | ||
284 | |||
285 | if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) || | ||
286 | pci_resource_len(pci, 0) < 0x100) { | ||
287 | snd_printk(KERN_ERR "invalid PCI I/O range\n"); | ||
288 | err = -ENXIO; | ||
289 | goto err_pci_regions; | ||
290 | } | ||
291 | chip->addr = pci_resource_start(pci, 0); | ||
292 | |||
293 | pci_set_master(pci); | ||
294 | snd_card_set_dev(card, &pci->dev); | ||
295 | card->private_free = oxygen_card_free; | ||
296 | |||
297 | oxygen_init(chip); | ||
298 | model->init(chip); | ||
299 | |||
300 | err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED, | ||
301 | model->chip, chip); | ||
302 | if (err < 0) { | ||
303 | snd_printk(KERN_ERR "cannot grab interrupt %d\n", pci->irq); | ||
304 | goto err_card; | ||
305 | } | ||
306 | chip->irq = pci->irq; | ||
307 | |||
308 | strcpy(card->driver, model->chip); | ||
309 | strcpy(card->shortname, model->shortname); | ||
310 | sprintf(card->longname, "%s (rev %u) at %#lx, irq %i", | ||
311 | model->longname, chip->revision, chip->addr, chip->irq); | ||
312 | strcpy(card->mixername, model->chip); | ||
313 | snd_component_add(card, model->chip); | ||
314 | |||
315 | err = oxygen_pcm_init(chip); | ||
316 | if (err < 0) | ||
317 | goto err_card; | ||
318 | |||
319 | err = oxygen_mixer_init(chip); | ||
320 | if (err < 0) | ||
321 | goto err_card; | ||
322 | |||
323 | if (oxygen_read8(chip, OXYGEN_MISC) & OXYGEN_MISC_MIDI) { | ||
324 | err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI, | ||
325 | chip->addr + OXYGEN_MPU401, | ||
326 | MPU401_INFO_INTEGRATED, 0, 0, | ||
327 | &chip->midi); | ||
328 | if (err < 0) | ||
329 | goto err_card; | ||
330 | } | ||
331 | |||
332 | oxygen_proc_init(chip); | ||
333 | |||
334 | spin_lock_irq(&chip->reg_lock); | ||
335 | chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE; | ||
336 | oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); | ||
337 | spin_unlock_irq(&chip->reg_lock); | ||
338 | |||
339 | err = snd_card_register(card); | ||
340 | if (err < 0) | ||
341 | goto err_card; | ||
342 | |||
343 | pci_set_drvdata(pci, card); | ||
344 | return 0; | ||
345 | |||
346 | err_pci_regions: | ||
347 | pci_release_regions(pci); | ||
348 | err_pci_enable: | ||
349 | pci_disable_device(pci); | ||
350 | err_card: | ||
351 | snd_card_free(card); | ||
352 | return err; | ||
353 | } | ||
354 | EXPORT_SYMBOL(oxygen_pci_probe); | ||
355 | |||
356 | void __devexit oxygen_pci_remove(struct pci_dev *pci) | ||
357 | { | ||
358 | snd_card_free(pci_get_drvdata(pci)); | ||
359 | pci_set_drvdata(pci, NULL); | ||
360 | } | ||
361 | EXPORT_SYMBOL(oxygen_pci_remove); | ||