Blame view

sound/isa/cmi8328.c 13.6 KB
f99334874   Ondrej Zary   ALSA: introduce s...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  /*
   * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500
   * Copyright (c) 2012 Ondrej Zary
   *
   * AudioExcel AV500 card consists of:
   *  - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM)
   *  - CS4231A - WSS codec
   *  - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401
   */
  
  #include <linux/init.h>
  #include <linux/isa.h>
  #include <linux/module.h>
  #include <linux/gameport.h>
  #include <asm/dma.h>
  #include <sound/core.h>
  #include <sound/wss.h>
  #include <sound/opl3.h>
  #include <sound/mpu401.h>
  #define SNDRV_LEGACY_FIND_FREE_IOPORT
  #define SNDRV_LEGACY_FIND_FREE_IRQ
  #define SNDRV_LEGACY_FIND_FREE_DMA
  #include <sound/initval.h>
  
  MODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>");
  MODULE_DESCRIPTION("C-Media CMI8328");
  MODULE_LICENSE("GPL");
  
  #if defined(CONFIG_GAMEPORT) || defined(CONFIG_GAMEPORT_MODULE)
  #define SUPPORT_JOYSTICK 1
  #endif
  
  /* I/O port is configured by jumpers on the card to one of these */
  static int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 };
  #define CMI8328_MAX	ARRAY_SIZE(cmi8328_ports)
  
  static int index[CMI8328_MAX] =     {[0 ... (CMI8328_MAX-1)] = -1};
  static char *id[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = NULL};
  static long port[CMI8328_MAX] =     {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
  static int irq[CMI8328_MAX] =       {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
  static int dma1[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
  static int dma2[CMI8328_MAX] =      {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
  static long mpuport[CMI8328_MAX] =  {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
  static int mpuirq[CMI8328_MAX] =    {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
  #ifdef SUPPORT_JOYSTICK
  static bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true};
  #endif
  
  module_param_array(index, int, NULL, 0444);
  MODULE_PARM_DESC(index, "Index value for CMI8328 soundcard.");
  module_param_array(id, charp, NULL, 0444);
  MODULE_PARM_DESC(id, "ID string for CMI8328 soundcard.");
  
  module_param_array(port, long, NULL, 0444);
  MODULE_PARM_DESC(port, "Port # for CMI8328 driver.");
  module_param_array(irq, int, NULL, 0444);
  MODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver.");
  module_param_array(dma1, int, NULL, 0444);
  MODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver.");
  module_param_array(dma2, int, NULL, 0444);
  MODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver.");
  
  module_param_array(mpuport, long, NULL, 0444);
  MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver.");
  module_param_array(mpuirq, int, NULL, 0444);
  MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port.");
  #ifdef SUPPORT_JOYSTICK
  module_param_array(gameport, bool, NULL, 0444);
  MODULE_PARM_DESC(gameport, "Enable gameport.");
  #endif
  
  struct snd_cmi8328 {
  	u16 port;
  	u8 cfg[3];
  	u8 wss_cfg;
  	struct snd_card *card;
  	struct snd_wss *wss;
  #ifdef SUPPORT_JOYSTICK
  	struct gameport *gameport;
  #endif
  };
  
  /* CMI8328 configuration registers */
  #define CFG1 0x61
  #define CFG1_SB_DISABLE	(1 << 0)
  #define CFG1_GAMEPORT	(1 << 1)
  /*
   * bit 0:    SB: 0=enabled, 1=disabled
   * bit 1:    gameport: 0=disabled, 1=enabled
   * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11
   * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3
   * bit 7:    SB port: 0=0x220, 1=0x240
   */
  #define CFG2 0x62
  #define CFG2_MPU_ENABLE (1 << 2)
  /*
   * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes,
  			  11=IDE
   * bit 2:    MPU401: 0=disabled, 1=enabled
   * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9,
   * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332,
  			  101=0x334, 110=0x336
   */
  #define CFG3 0x63
  /*
   * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10,
  			 110=11
   * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3
   * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340,
  			  101=0x350, 110=0x360, 111=0x370
   */
  
  static u8 snd_cmi8328_cfg_read(u16 port, u8 reg)
  {
  	outb(0x43, port + 3);
  	outb(0x21, port + 3);
  	outb(reg, port + 3);
  	return inb(port);
  }
  
  static void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val)
  {
  	outb(0x43, port + 3);
  	outb(0x21, port + 3);
  	outb(reg, port + 3);
  	outb(val, port + 3);	/* yes, value goes to the same port as index */
  }
77ed16cc6   Takashi Iwai   ALSA: cmi8328: Fi...
128
  #ifdef CONFIG_PM
f99334874   Ondrej Zary   ALSA: introduce s...
129
130
131
132
133
134
135
136
137
138
139
140
141
  static void snd_cmi8328_cfg_save(u16 port, u8 cfg[])
  {
  	cfg[0] = snd_cmi8328_cfg_read(port, CFG1);
  	cfg[1] = snd_cmi8328_cfg_read(port, CFG2);
  	cfg[2] = snd_cmi8328_cfg_read(port, CFG3);
  }
  
  static void snd_cmi8328_cfg_restore(u16 port, u8 cfg[])
  {
  	snd_cmi8328_cfg_write(port, CFG1, cfg[0]);
  	snd_cmi8328_cfg_write(port, CFG2, cfg[1]);
  	snd_cmi8328_cfg_write(port, CFG3, cfg[2]);
  }
77ed16cc6   Takashi Iwai   ALSA: cmi8328: Fi...
142
  #endif /* CONFIG_PM */
f99334874   Ondrej Zary   ALSA: introduce s...
143

1bff292e9   Bill Pemberton   ALSA: isa: remove...
144
  static int snd_cmi8328_mixer(struct snd_wss *chip)
f99334874   Ondrej Zary   ALSA: introduce s...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  {
  	struct snd_card *card;
  	struct snd_ctl_elem_id id1, id2;
  	int err;
  
  	card = chip->card;
  
  	memset(&id1, 0, sizeof(id1));
  	memset(&id2, 0, sizeof(id2));
  	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
  	/* rename AUX0 switch to CD */
  	strcpy(id1.name, "Aux Playback Switch");
  	strcpy(id2.name, "CD Playback Switch");
  	err = snd_ctl_rename_id(card, &id1, &id2);
  	if (err < 0) {
  		snd_printk(KERN_ERR "error renaming control
  ");
  		return err;
  	}
  	/* rename AUX0 volume to CD */
  	strcpy(id1.name, "Aux Playback Volume");
  	strcpy(id2.name, "CD Playback Volume");
  	err = snd_ctl_rename_id(card, &id1, &id2);
  	if (err < 0) {
  		snd_printk(KERN_ERR "error renaming control
  ");
  		return err;
  	}
  	/* rename AUX1 switch to Synth */
  	strcpy(id1.name, "Aux Playback Switch");
  	id1.index = 1;
  	strcpy(id2.name, "Synth Playback Switch");
  	err = snd_ctl_rename_id(card, &id1, &id2);
  	if (err < 0) {
  		snd_printk(KERN_ERR "error renaming control
  ");
  		return err;
  	}
  	/* rename AUX1 volume to Synth */
  	strcpy(id1.name, "Aux Playback Volume");
  	id1.index = 1;
  	strcpy(id2.name, "Synth Playback Volume");
  	err = snd_ctl_rename_id(card, &id1, &id2);
  	if (err < 0) {
  		snd_printk(KERN_ERR "error renaming control
  ");
  		return err;
  	}
  
  	return 0;
  }
  
  /* find index of an item in "-1"-ended array */
  int array_find(int array[], int item)
  {
  	int i;
  
  	for (i = 0; array[i] != -1; i++)
  		if (array[i] == item)
  			return i;
  
  	return -1;
  }
  /* the same for long */
  int array_find_l(long array[], long item)
  {
  	int i;
  
  	for (i = 0; array[i] != -1; i++)
  		if (array[i] == item)
  			return i;
  
  	return -1;
  }
1bff292e9   Bill Pemberton   ALSA: isa: remove...
219
  static int snd_cmi8328_probe(struct device *pdev, unsigned int ndev)
f99334874   Ondrej Zary   ALSA: introduce s...
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
  {
  	struct snd_card *card;
  	struct snd_opl3 *opl3;
  	struct snd_cmi8328 *cmi;
  #ifdef SUPPORT_JOYSTICK
  	struct resource *res;
  #endif
  	int err, pos;
  	static long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334,
  				   0x336, -1 };
  	static u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 };
  	static int mpu_irqs[] = { 9, 7, 5, 3, -1 };
  	static u8 mpu_irq_bits[] = { 3, 2, 1, 0 };
  	static int irqs[] = { 9, 10, 11, 7, -1 };
  	static u8 irq_bits[] = { 2, 3, 4, 1 };
  	static int dma1s[] = { 3, 1, 0, -1 };
  	static u8 dma_bits[] = { 3, 2, 1 };
  	static int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} };
  	u16 port = cmi8328_ports[ndev];
  	u8 val;
  
  	/* 0xff is invalid configuration (but settable - hope it isn't set) */
  	if (snd_cmi8328_cfg_read(port, CFG1) == 0xff)
  		return -ENODEV;
  	/* the SB disable bit must NEVER EVER be cleared or the WSS dies */
  	snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE);
  	if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE)
  		return -ENODEV;
  	/* disable everything first */
  	snd_cmi8328_cfg_write(port, CFG2, 0);	/* disable CDROM and MPU401 */
  	snd_cmi8328_cfg_write(port, CFG3, 0);	/* disable CDROM IRQ and DMA */
  
  	if (irq[ndev] == SNDRV_AUTO_IRQ) {
  		irq[ndev] = snd_legacy_find_free_irq(irqs);
  		if (irq[ndev] < 0) {
  			snd_printk(KERN_ERR "unable to find a free IRQ
  ");
  			return -EBUSY;
  		}
  	}
  	if (dma1[ndev] == SNDRV_AUTO_DMA) {
  		dma1[ndev] = snd_legacy_find_free_dma(dma1s);
  		if (dma1[ndev] < 0) {
  			snd_printk(KERN_ERR "unable to find a free DMA1
  ");
  			return -EBUSY;
  		}
  	}
  	if (dma2[ndev] == SNDRV_AUTO_DMA) {
  		dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]);
  		if (dma2[ndev] < 0) {
  			snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work
  ");
  			dma2[ndev] = -1;
  		}
  	}
  	/* configure WSS IRQ... */
  	pos = array_find(irqs, irq[ndev]);
  	if (pos < 0) {
  		snd_printk(KERN_ERR "invalid IRQ %d
  ", irq[ndev]);
  		return -EINVAL;
  	}
  	val = irq_bits[pos] << 3;
  	/* ...and DMA... */
  	pos = array_find(dma1s, dma1[ndev]);
  	if (pos < 0) {
  		snd_printk(KERN_ERR "invalid DMA1 %d
  ", dma1[ndev]);
  		return -EINVAL;
  	}
  	val |= dma_bits[pos];
  	/* ...and DMA2 */
  	if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) {
  		pos = array_find(dma2s[dma1[ndev]], dma2[ndev]);
  		if (pos < 0) {
  			snd_printk(KERN_ERR "invalid DMA2 %d
  ", dma2[ndev]);
  			return -EINVAL;
  		}
  		val |= 0x04; /* enable separate capture DMA */
  	}
  	outb(val, port);
4323cc4d5   Takashi Iwai   ALSA: isa: Conver...
303
304
  	err = snd_card_new(pdev, index[ndev], id[ndev], THIS_MODULE,
  			   sizeof(struct snd_cmi8328), &card);
f99334874   Ondrej Zary   ALSA: introduce s...
305
306
307
308
309
310
  	if (err < 0)
  		return err;
  	cmi = card->private_data;
  	cmi->card = card;
  	cmi->port = port;
  	cmi->wss_cfg = val;
f99334874   Ondrej Zary   ALSA: introduce s...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
  
  	err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev],
  			dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss);
  	if (err < 0)
  		goto error;
  
  	err = snd_wss_pcm(cmi->wss, 0, NULL);
  	if (err < 0)
  		goto error;
  
  	err = snd_wss_mixer(cmi->wss);
  	if (err < 0)
  		goto error;
  	err = snd_cmi8328_mixer(cmi->wss);
  	if (err < 0)
  		goto error;
  
  	if (snd_wss_timer(cmi->wss, 0, NULL) < 0)
  		snd_printk(KERN_WARNING "error initializing WSS timer
  ");
  
  	if (mpuport[ndev] == SNDRV_AUTO_PORT) {
  		mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2);
  		if (mpuport[ndev] < 0)
  			snd_printk(KERN_ERR "unable to find a free MPU401 port
  ");
  	}
  	if (mpuirq[ndev] == SNDRV_AUTO_IRQ) {
  		mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs);
  		if (mpuirq[ndev] < 0)
  			snd_printk(KERN_ERR "unable to find a free MPU401 IRQ
  ");
  	}
  	/* enable and configure MPU401 */
  	if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) {
  		val = CFG2_MPU_ENABLE;
  		pos = array_find_l(mpu_ports, mpuport[ndev]);
  		if (pos < 0)
  			snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx
  ",
  								mpuport[ndev]);
  		else {
  			val |= mpu_port_bits[pos] << 5;
  			pos = array_find(mpu_irqs, mpuirq[ndev]);
  			if (pos < 0)
  				snd_printk(KERN_WARNING "invalid MPU401 IRQ %d
  ",
  								mpuirq[ndev]);
  			else {
  				val |= mpu_irq_bits[pos] << 3;
  				snd_cmi8328_cfg_write(port, CFG2, val);
  				if (snd_mpu401_uart_new(card, 0,
  						MPU401_HW_MPU401, mpuport[ndev],
  						0, mpuirq[ndev], NULL) < 0)
  					snd_printk(KERN_ERR "error initializing MPU401
  ");
  			}
  		}
  	}
  	/* OPL3 is hardwired to 0x388 and cannot be disabled */
  	if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0)
  		snd_printk(KERN_ERR "error initializing OPL3
  ");
  	else
  		if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0)
  			snd_printk(KERN_WARNING "error initializing OPL3 hwdep
  ");
  
  	strcpy(card->driver, "CMI8328");
  	strcpy(card->shortname, "C-Media CMI8328");
  	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d",
  		card->shortname, cmi->wss->port, irq[ndev], dma1[ndev],
  		(dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]);
  
  	dev_set_drvdata(pdev, card);
  	err = snd_card_register(card);
  	if (err < 0)
  		goto error;
  #ifdef SUPPORT_JOYSTICK
  	if (!gameport[ndev])
  		return 0;
  	/* gameport is hardwired to 0x200 */
  	res = request_region(0x200, 8, "CMI8328 gameport");
  	if (!res)
  		snd_printk(KERN_WARNING "unable to allocate gameport I/O port
  ");
  	else {
  		struct gameport *gp = cmi->gameport = gameport_allocate_port();
  		if (!cmi->gameport)
  			release_and_free_resource(res);
  		else {
  			gameport_set_name(gp, "CMI8328 Gameport");
  			gameport_set_phys(gp, "%s/gameport0", dev_name(pdev));
  			gameport_set_dev_parent(gp, pdev);
  			gp->io = 0x200;
  			gameport_set_port_data(gp, res);
  			/* Enable gameport */
  			snd_cmi8328_cfg_write(port, CFG1,
  					CFG1_SB_DISABLE | CFG1_GAMEPORT);
  			gameport_register_port(gp);
  		}
  	}
  #endif
  	return 0;
  error:
  	snd_card_free(card);
  
  	return err;
  }
1bff292e9   Bill Pemberton   ALSA: isa: remove...
420
  static int snd_cmi8328_remove(struct device *pdev, unsigned int dev)
f99334874   Ondrej Zary   ALSA: introduce s...
421
422
  {
  	struct snd_card *card = dev_get_drvdata(pdev);
f99334874   Ondrej Zary   ALSA: introduce s...
423
  	struct snd_cmi8328 *cmi = card->private_data;
56244d086   Takashi Iwai   ALSA: cmi8328: Fi...
424
425
  
  #ifdef SUPPORT_JOYSTICK
f99334874   Ondrej Zary   ALSA: introduce s...
426
427
428
429
430
431
432
433
434
435
436
  	if (cmi->gameport) {
  		struct resource *res = gameport_get_port_data(cmi->gameport);
  		gameport_unregister_port(cmi->gameport);
  		release_and_free_resource(res);
  	}
  #endif
  	/* disable everything */
  	snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE);
  	snd_cmi8328_cfg_write(cmi->port, CFG2, 0);
  	snd_cmi8328_cfg_write(cmi->port, CFG3, 0);
  	snd_card_free(card);
f99334874   Ondrej Zary   ALSA: introduce s...
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
  	return 0;
  }
  
  #ifdef CONFIG_PM
  static int snd_cmi8328_suspend(struct device *pdev, unsigned int n,
  				pm_message_t state)
  {
  	struct snd_card *card = dev_get_drvdata(pdev);
  	struct snd_cmi8328 *cmi;
  
  	if (!card)	/* ignore absent devices */
  		return 0;
  	cmi = card->private_data;
  	snd_cmi8328_cfg_save(cmi->port, cmi->cfg);
  	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
  	snd_pcm_suspend_all(cmi->wss->pcm);
  	cmi->wss->suspend(cmi->wss);
  
  	return 0;
  }
  
  static int snd_cmi8328_resume(struct device *pdev, unsigned int n)
  {
  	struct snd_card *card = dev_get_drvdata(pdev);
  	struct snd_cmi8328 *cmi;
  
  	if (!card)	/* ignore absent devices */
  		return 0;
  	cmi = card->private_data;
  	snd_cmi8328_cfg_restore(cmi->port, cmi->cfg);
  	outb(cmi->wss_cfg, cmi->port);
  	cmi->wss->resume(cmi->wss);
  	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
  
  	return 0;
  }
  #endif
  
  static struct isa_driver snd_cmi8328_driver = {
  	.probe		= snd_cmi8328_probe,
1bff292e9   Bill Pemberton   ALSA: isa: remove...
477
  	.remove		= snd_cmi8328_remove,
f99334874   Ondrej Zary   ALSA: introduce s...
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
  #ifdef CONFIG_PM
  	.suspend	= snd_cmi8328_suspend,
  	.resume		= snd_cmi8328_resume,
  #endif
  	.driver		= {
  		.name	= "cmi8328"
  	},
  };
  
  static int __init alsa_card_cmi8328_init(void)
  {
  	return isa_register_driver(&snd_cmi8328_driver, CMI8328_MAX);
  }
  
  static void __exit alsa_card_cmi8328_exit(void)
  {
  	isa_unregister_driver(&snd_cmi8328_driver);
  }
  
  module_init(alsa_card_cmi8328_init)
  module_exit(alsa_card_cmi8328_exit)