aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
authorOndrej Zary <linux@rainbow-software.org>2012-08-20 16:39:51 -0400
committerTakashi Iwai <tiwai@suse.de>2012-08-21 01:30:46 -0400
commitf9933487468c760b8cd9b4e9f7ec4e494f711a0a (patch)
tree98a324f6baae28c43c4a90ec10b6d792c81fb82d /sound
parentddf83485d7da468251716b8040bac1280622181e (diff)
ALSA: introduce snd-cmi8328: C-Media CMI8328 driver
Introduce snd-cmi8328 driver for C-Media CMI8328-based sound cards, such as AudioExcel AV500. It supports PCM playback and capture (full-duplex) through wss_lib, gameport, OPL3 and MPU401. The AV500 card has onboard Dream wavetable synth connected to the MPU401 port and Aux 1 input internally which works too. The CDROM interface is not supported (as the drivers for these CDROMs were removed from the kernel some time ago). A separate driver is needed because CMI8328 is completely different chip to CMI8329/CMI8330. It's configured by magic registers (there's no PnP). Sound is provided by a real WSS codec (CS4231A) and the SB part is just a SB Pro emulation (for DOS games, useless for Linux). When SB is enabled, the CMI8328 chip disables access to the WSS codec, emulates SoundBlaster on one side and outputs sound data to the codec - so SB and WSS can't work together with this card. The WSS codec can do full duplex by itself so there's no need for crazy things like snd-cmi8330 does (combining SB and WSS parts into one driver). Signed-off-by: Ondrej Zary <linux@rainbow-software.org> Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound')
-rw-r--r--sound/isa/Kconfig12
-rw-r--r--sound/isa/Makefile2
-rw-r--r--sound/isa/cmi8328.c482
3 files changed, 496 insertions, 0 deletions
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig
index 52064cfa91f3..a38d9643e9d8 100644
--- a/sound/isa/Kconfig
+++ b/sound/isa/Kconfig
@@ -117,6 +117,18 @@ config SND_AZT2320
117 To compile this driver as a module, choose M here: the module 117 To compile this driver as a module, choose M here: the module
118 will be called snd-azt2320. 118 will be called snd-azt2320.
119 119
120config SND_CMI8328
121 tristate "C-Media CMI8328"
122 select SND_WSS_LIB
123 select SND_OPL3_LIB
124 select SND_MPU401_UART
125 help
126 Say Y here to include support for soundcards based on the
127 C-Media CMI8328 chip.
128
129 To compile this driver as a module, choose M here: the module
130 will be called snd-cmi8328.
131
120config SND_CMI8330 132config SND_CMI8330
121 tristate "C-Media CMI8330" 133 tristate "C-Media CMI8330"
122 select SND_WSS_LIB 134 select SND_WSS_LIB
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
index 8d781e419e2e..9a15f1497b10 100644
--- a/sound/isa/Makefile
+++ b/sound/isa/Makefile
@@ -6,6 +6,7 @@
6snd-adlib-objs := adlib.o 6snd-adlib-objs := adlib.o
7snd-als100-objs := als100.o 7snd-als100-objs := als100.o
8snd-azt2320-objs := azt2320.o 8snd-azt2320-objs := azt2320.o
9snd-cmi8328-objs := cmi8328.o
9snd-cmi8330-objs := cmi8330.o 10snd-cmi8330-objs := cmi8330.o
10snd-es18xx-objs := es18xx.o 11snd-es18xx-objs := es18xx.o
11snd-opl3sa2-objs := opl3sa2.o 12snd-opl3sa2-objs := opl3sa2.o
@@ -16,6 +17,7 @@ snd-sscape-objs := sscape.o
16obj-$(CONFIG_SND_ADLIB) += snd-adlib.o 17obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
17obj-$(CONFIG_SND_ALS100) += snd-als100.o 18obj-$(CONFIG_SND_ALS100) += snd-als100.o
18obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o 19obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o
20obj-$(CONFIG_SND_CMI8328) += snd-cmi8328.o
19obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o 21obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o
20obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o 22obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
21obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o 23obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
diff --git a/sound/isa/cmi8328.c b/sound/isa/cmi8328.c
new file mode 100644
index 000000000000..aefafff50756
--- /dev/null
+++ b/sound/isa/cmi8328.c
@@ -0,0 +1,482 @@
1/*
2 * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500
3 * Copyright (c) 2012 Ondrej Zary
4 *
5 * AudioExcel AV500 card consists of:
6 * - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM)
7 * - CS4231A - WSS codec
8 * - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401
9 */
10
11#include <linux/init.h>
12#include <linux/isa.h>
13#include <linux/module.h>
14#include <linux/gameport.h>
15#include <asm/dma.h>
16#include <sound/core.h>
17#include <sound/wss.h>
18#include <sound/opl3.h>
19#include <sound/mpu401.h>
20#define SNDRV_LEGACY_FIND_FREE_IOPORT
21#define SNDRV_LEGACY_FIND_FREE_IRQ
22#define SNDRV_LEGACY_FIND_FREE_DMA
23#include <sound/initval.h>
24
25MODULE_AUTHOR("Ondrej Zary <linux@rainbow-software.org>");
26MODULE_DESCRIPTION("C-Media CMI8328");
27MODULE_LICENSE("GPL");
28
29#if defined(CONFIG_GAMEPORT) || defined(CONFIG_GAMEPORT_MODULE)
30#define SUPPORT_JOYSTICK 1
31#endif
32
33/* I/O port is configured by jumpers on the card to one of these */
34static int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 };
35#define CMI8328_MAX ARRAY_SIZE(cmi8328_ports)
36
37static int index[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = -1};
38static char *id[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = NULL};
39static long port[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
40static int irq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
41static int dma1[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
42static int dma2[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA};
43static long mpuport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT};
44static int mpuirq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ};
45#ifdef SUPPORT_JOYSTICK
46static bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true};
47#endif
48
49module_param_array(index, int, NULL, 0444);
50MODULE_PARM_DESC(index, "Index value for CMI8328 soundcard.");
51module_param_array(id, charp, NULL, 0444);
52MODULE_PARM_DESC(id, "ID string for CMI8328 soundcard.");
53
54module_param_array(port, long, NULL, 0444);
55MODULE_PARM_DESC(port, "Port # for CMI8328 driver.");
56module_param_array(irq, int, NULL, 0444);
57MODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver.");
58module_param_array(dma1, int, NULL, 0444);
59MODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver.");
60module_param_array(dma2, int, NULL, 0444);
61MODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver.");
62
63module_param_array(mpuport, long, NULL, 0444);
64MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver.");
65module_param_array(mpuirq, int, NULL, 0444);
66MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port.");
67#ifdef SUPPORT_JOYSTICK
68module_param_array(gameport, bool, NULL, 0444);
69MODULE_PARM_DESC(gameport, "Enable gameport.");
70#endif
71
72struct snd_cmi8328 {
73 u16 port;
74 u8 cfg[3];
75 u8 wss_cfg;
76 struct snd_card *card;
77 struct snd_wss *wss;
78#ifdef SUPPORT_JOYSTICK
79 struct gameport *gameport;
80#endif
81};
82
83/* CMI8328 configuration registers */
84#define CFG1 0x61
85#define CFG1_SB_DISABLE (1 << 0)
86#define CFG1_GAMEPORT (1 << 1)
87/*
88 * bit 0: SB: 0=enabled, 1=disabled
89 * bit 1: gameport: 0=disabled, 1=enabled
90 * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11
91 * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3
92 * bit 7: SB port: 0=0x220, 1=0x240
93 */
94#define CFG2 0x62
95#define CFG2_MPU_ENABLE (1 << 2)
96/*
97 * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes,
98 11=IDE
99 * bit 2: MPU401: 0=disabled, 1=enabled
100 * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9,
101 * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332,
102 101=0x334, 110=0x336
103 */
104#define CFG3 0x63
105/*
106 * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10,
107 110=11
108 * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3
109 * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340,
110 101=0x350, 110=0x360, 111=0x370
111 */
112
113static u8 snd_cmi8328_cfg_read(u16 port, u8 reg)
114{
115 outb(0x43, port + 3);
116 outb(0x21, port + 3);
117 outb(reg, port + 3);
118 return inb(port);
119}
120
121static void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val)
122{
123 outb(0x43, port + 3);
124 outb(0x21, port + 3);
125 outb(reg, port + 3);
126 outb(val, port + 3); /* yes, value goes to the same port as index */
127}
128
129static void snd_cmi8328_cfg_save(u16 port, u8 cfg[])
130{
131 cfg[0] = snd_cmi8328_cfg_read(port, CFG1);
132 cfg[1] = snd_cmi8328_cfg_read(port, CFG2);
133 cfg[2] = snd_cmi8328_cfg_read(port, CFG3);
134}
135
136static void snd_cmi8328_cfg_restore(u16 port, u8 cfg[])
137{
138 snd_cmi8328_cfg_write(port, CFG1, cfg[0]);
139 snd_cmi8328_cfg_write(port, CFG2, cfg[1]);
140 snd_cmi8328_cfg_write(port, CFG3, cfg[2]);
141}
142
143static int __devinit snd_cmi8328_mixer(struct snd_wss *chip)
144{
145 struct snd_card *card;
146 struct snd_ctl_elem_id id1, id2;
147 int err;
148
149 card = chip->card;
150
151 memset(&id1, 0, sizeof(id1));
152 memset(&id2, 0, sizeof(id2));
153 id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
154 /* rename AUX0 switch to CD */
155 strcpy(id1.name, "Aux Playback Switch");
156 strcpy(id2.name, "CD Playback Switch");
157 err = snd_ctl_rename_id(card, &id1, &id2);
158 if (err < 0) {
159 snd_printk(KERN_ERR "error renaming control\n");
160 return err;
161 }
162 /* rename AUX0 volume to CD */
163 strcpy(id1.name, "Aux Playback Volume");
164 strcpy(id2.name, "CD Playback Volume");
165 err = snd_ctl_rename_id(card, &id1, &id2);
166 if (err < 0) {
167 snd_printk(KERN_ERR "error renaming control\n");
168 return err;
169 }
170 /* rename AUX1 switch to Synth */
171 strcpy(id1.name, "Aux Playback Switch");
172 id1.index = 1;
173 strcpy(id2.name, "Synth Playback Switch");
174 err = snd_ctl_rename_id(card, &id1, &id2);
175 if (err < 0) {
176 snd_printk(KERN_ERR "error renaming control\n");
177 return err;
178 }
179 /* rename AUX1 volume to Synth */
180 strcpy(id1.name, "Aux Playback Volume");
181 id1.index = 1;
182 strcpy(id2.name, "Synth Playback Volume");
183 err = snd_ctl_rename_id(card, &id1, &id2);
184 if (err < 0) {
185 snd_printk(KERN_ERR "error renaming control\n");
186 return err;
187 }
188
189 return 0;
190}
191
192/* find index of an item in "-1"-ended array */
193int array_find(int array[], int item)
194{
195 int i;
196
197 for (i = 0; array[i] != -1; i++)
198 if (array[i] == item)
199 return i;
200
201 return -1;
202}
203/* the same for long */
204int array_find_l(long array[], long item)
205{
206 int i;
207
208 for (i = 0; array[i] != -1; i++)
209 if (array[i] == item)
210 return i;
211
212 return -1;
213}
214
215static int __devinit snd_cmi8328_probe(struct device *pdev, unsigned int ndev)
216{
217 struct snd_card *card;
218 struct snd_opl3 *opl3;
219 struct snd_cmi8328 *cmi;
220#ifdef SUPPORT_JOYSTICK
221 struct resource *res;
222#endif
223 int err, pos;
224 static long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334,
225 0x336, -1 };
226 static u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 };
227 static int mpu_irqs[] = { 9, 7, 5, 3, -1 };
228 static u8 mpu_irq_bits[] = { 3, 2, 1, 0 };
229 static int irqs[] = { 9, 10, 11, 7, -1 };
230 static u8 irq_bits[] = { 2, 3, 4, 1 };
231 static int dma1s[] = { 3, 1, 0, -1 };
232 static u8 dma_bits[] = { 3, 2, 1 };
233 static int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} };
234 u16 port = cmi8328_ports[ndev];
235 u8 val;
236
237 /* 0xff is invalid configuration (but settable - hope it isn't set) */
238 if (snd_cmi8328_cfg_read(port, CFG1) == 0xff)
239 return -ENODEV;
240 /* the SB disable bit must NEVER EVER be cleared or the WSS dies */
241 snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE);
242 if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE)
243 return -ENODEV;
244 /* disable everything first */
245 snd_cmi8328_cfg_write(port, CFG2, 0); /* disable CDROM and MPU401 */
246 snd_cmi8328_cfg_write(port, CFG3, 0); /* disable CDROM IRQ and DMA */
247
248 if (irq[ndev] == SNDRV_AUTO_IRQ) {
249 irq[ndev] = snd_legacy_find_free_irq(irqs);
250 if (irq[ndev] < 0) {
251 snd_printk(KERN_ERR "unable to find a free IRQ\n");
252 return -EBUSY;
253 }
254 }
255 if (dma1[ndev] == SNDRV_AUTO_DMA) {
256 dma1[ndev] = snd_legacy_find_free_dma(dma1s);
257 if (dma1[ndev] < 0) {
258 snd_printk(KERN_ERR "unable to find a free DMA1\n");
259 return -EBUSY;
260 }
261 }
262 if (dma2[ndev] == SNDRV_AUTO_DMA) {
263 dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]);
264 if (dma2[ndev] < 0) {
265 snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n");
266 dma2[ndev] = -1;
267 }
268 }
269 /* configure WSS IRQ... */
270 pos = array_find(irqs, irq[ndev]);
271 if (pos < 0) {
272 snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]);
273 return -EINVAL;
274 }
275 val = irq_bits[pos] << 3;
276 /* ...and DMA... */
277 pos = array_find(dma1s, dma1[ndev]);
278 if (pos < 0) {
279 snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]);
280 return -EINVAL;
281 }
282 val |= dma_bits[pos];
283 /* ...and DMA2 */
284 if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) {
285 pos = array_find(dma2s[dma1[ndev]], dma2[ndev]);
286 if (pos < 0) {
287 snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]);
288 return -EINVAL;
289 }
290 val |= 0x04; /* enable separate capture DMA */
291 }
292 outb(val, port);
293
294 err = snd_card_create(index[ndev], id[ndev], THIS_MODULE,
295 sizeof(struct snd_cmi8328), &card);
296 if (err < 0)
297 return err;
298 cmi = card->private_data;
299 cmi->card = card;
300 cmi->port = port;
301 cmi->wss_cfg = val;
302 snd_card_set_dev(card, pdev);
303
304 err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev],
305 dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss);
306 if (err < 0)
307 goto error;
308
309 err = snd_wss_pcm(cmi->wss, 0, NULL);
310 if (err < 0)
311 goto error;
312
313 err = snd_wss_mixer(cmi->wss);
314 if (err < 0)
315 goto error;
316 err = snd_cmi8328_mixer(cmi->wss);
317 if (err < 0)
318 goto error;
319
320 if (snd_wss_timer(cmi->wss, 0, NULL) < 0)
321 snd_printk(KERN_WARNING "error initializing WSS timer\n");
322
323 if (mpuport[ndev] == SNDRV_AUTO_PORT) {
324 mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2);
325 if (mpuport[ndev] < 0)
326 snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
327 }
328 if (mpuirq[ndev] == SNDRV_AUTO_IRQ) {
329 mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs);
330 if (mpuirq[ndev] < 0)
331 snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
332 }
333 /* enable and configure MPU401 */
334 if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) {
335 val = CFG2_MPU_ENABLE;
336 pos = array_find_l(mpu_ports, mpuport[ndev]);
337 if (pos < 0)
338 snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n",
339 mpuport[ndev]);
340 else {
341 val |= mpu_port_bits[pos] << 5;
342 pos = array_find(mpu_irqs, mpuirq[ndev]);
343 if (pos < 0)
344 snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n",
345 mpuirq[ndev]);
346 else {
347 val |= mpu_irq_bits[pos] << 3;
348 snd_cmi8328_cfg_write(port, CFG2, val);
349 if (snd_mpu401_uart_new(card, 0,
350 MPU401_HW_MPU401, mpuport[ndev],
351 0, mpuirq[ndev], NULL) < 0)
352 snd_printk(KERN_ERR "error initializing MPU401\n");
353 }
354 }
355 }
356 /* OPL3 is hardwired to 0x388 and cannot be disabled */
357 if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0)
358 snd_printk(KERN_ERR "error initializing OPL3\n");
359 else
360 if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0)
361 snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n");
362
363 strcpy(card->driver, "CMI8328");
364 strcpy(card->shortname, "C-Media CMI8328");
365 sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d",
366 card->shortname, cmi->wss->port, irq[ndev], dma1[ndev],
367 (dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]);
368
369 dev_set_drvdata(pdev, card);
370 err = snd_card_register(card);
371 if (err < 0)
372 goto error;
373#ifdef SUPPORT_JOYSTICK
374 if (!gameport[ndev])
375 return 0;
376 /* gameport is hardwired to 0x200 */
377 res = request_region(0x200, 8, "CMI8328 gameport");
378 if (!res)
379 snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n");
380 else {
381 struct gameport *gp = cmi->gameport = gameport_allocate_port();
382 if (!cmi->gameport)
383 release_and_free_resource(res);
384 else {
385 gameport_set_name(gp, "CMI8328 Gameport");
386 gameport_set_phys(gp, "%s/gameport0", dev_name(pdev));
387 gameport_set_dev_parent(gp, pdev);
388 gp->io = 0x200;
389 gameport_set_port_data(gp, res);
390 /* Enable gameport */
391 snd_cmi8328_cfg_write(port, CFG1,
392 CFG1_SB_DISABLE | CFG1_GAMEPORT);
393 gameport_register_port(gp);
394 }
395 }
396#endif
397 return 0;
398error:
399 snd_card_free(card);
400
401 return err;
402}
403
404static int __devexit snd_cmi8328_remove(struct device *pdev, unsigned int dev)
405{
406 struct snd_card *card = dev_get_drvdata(pdev);
407#ifdef SUPPORT_JOYSTICK
408 struct snd_cmi8328 *cmi = card->private_data;
409 if (cmi->gameport) {
410 struct resource *res = gameport_get_port_data(cmi->gameport);
411 gameport_unregister_port(cmi->gameport);
412 release_and_free_resource(res);
413 }
414#endif
415 /* disable everything */
416 snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE);
417 snd_cmi8328_cfg_write(cmi->port, CFG2, 0);
418 snd_cmi8328_cfg_write(cmi->port, CFG3, 0);
419 snd_card_free(card);
420 dev_set_drvdata(pdev, NULL);
421 return 0;
422}
423
424#ifdef CONFIG_PM
425static int snd_cmi8328_suspend(struct device *pdev, unsigned int n,
426 pm_message_t state)
427{
428 struct snd_card *card = dev_get_drvdata(pdev);
429 struct snd_cmi8328 *cmi;
430
431 if (!card) /* ignore absent devices */
432 return 0;
433 cmi = card->private_data;
434 snd_cmi8328_cfg_save(cmi->port, cmi->cfg);
435 snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
436 snd_pcm_suspend_all(cmi->wss->pcm);
437 cmi->wss->suspend(cmi->wss);
438
439 return 0;
440}
441
442static int snd_cmi8328_resume(struct device *pdev, unsigned int n)
443{
444 struct snd_card *card = dev_get_drvdata(pdev);
445 struct snd_cmi8328 *cmi;
446
447 if (!card) /* ignore absent devices */
448 return 0;
449 cmi = card->private_data;
450 snd_cmi8328_cfg_restore(cmi->port, cmi->cfg);
451 outb(cmi->wss_cfg, cmi->port);
452 cmi->wss->resume(cmi->wss);
453 snd_power_change_state(card, SNDRV_CTL_POWER_D0);
454
455 return 0;
456}
457#endif
458
459static struct isa_driver snd_cmi8328_driver = {
460 .probe = snd_cmi8328_probe,
461 .remove = __devexit_p(snd_cmi8328_remove),
462#ifdef CONFIG_PM
463 .suspend = snd_cmi8328_suspend,
464 .resume = snd_cmi8328_resume,
465#endif
466 .driver = {
467 .name = "cmi8328"
468 },
469};
470
471static int __init alsa_card_cmi8328_init(void)
472{
473 return isa_register_driver(&snd_cmi8328_driver, CMI8328_MAX);
474}
475
476static void __exit alsa_card_cmi8328_exit(void)
477{
478 isa_unregister_driver(&snd_cmi8328_driver);
479}
480
481module_init(alsa_card_cmi8328_init)
482module_exit(alsa_card_cmi8328_exit)