33c9460722935ed563203ef64a9fc8ea9c6d21e8
[qemu] / hw / cs4231a.c
1 /*
2  * QEMU Crystal CS4231 audio chip emulation
3  *
4  * Copyright (c) 2006 Fabrice Bellard
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include "hw.h"
25 #include "audiodev.h"
26 #include "audio/audio.h"
27 #include "isa.h"
28 #include "qemu-timer.h"
29
30 /*
31   Missing features:
32   ADC
33   Loopback
34   Timer
35   ADPCM
36   More...
37 */
38
39 /* #define DEBUG */
40 /* #define DEBUG_XLAW */
41
42 static struct {
43     int irq;
44     int dma;
45     int port;
46     int aci_counter;
47 } conf = {9, 3, 0x534, 1};
48
49 #ifdef DEBUG
50 #define dolog(...) AUD_log ("cs4231a", __VA_ARGS__)
51 #else
52 #define dolog(...)
53 #endif
54
55 #define lwarn(...) AUD_log ("cs4231a", "warning: " __VA_ARGS__)
56 #define lerr(...) AUD_log ("cs4231a", "error: " __VA_ARGS__)
57
58 #define CS_REGS 16
59 #define CS_DREGS 32
60
61 typedef struct CSState {
62     QEMUSoundCard card;
63     qemu_irq *pic;
64     uint32_t regs[CS_REGS];
65     uint8_t dregs[CS_DREGS];
66     int irq;
67     int dma;
68     int port;
69     int shift;
70     int dma_running;
71     int audio_free;
72     int transferred;
73     int aci_counter;
74     SWVoiceOut *voice;
75     int16_t *tab;
76 } CSState;
77
78 #define IO_READ_PROTO(name)                             \
79     static uint32_t name (void *opaque, uint32_t addr)
80
81 #define IO_WRITE_PROTO(name)                                            \
82     static void name (void *opaque, uint32_t addr, uint32_t val)
83
84 #define GET_SADDR(addr) (addr & 3)
85
86 #define MODE2 (1 << 6)
87 #define MCE (1 << 6)
88 #define PMCE (1 << 4)
89 #define CMCE (1 << 5)
90 #define TE (1 << 6)
91 #define PEN (1 << 0)
92 #define INT (1 << 0)
93 #define IEN (1 << 1)
94 #define PPIO (1 << 6)
95 #define PI (1 << 4)
96 #define CI (1 << 5)
97 #define TI (1 << 6)
98
99 enum {
100     Index_Address,
101     Index_Data,
102     Status,
103     PIO_Data
104 };
105
106 enum {
107     Left_ADC_Input_Control,
108     Right_ADC_Input_Control,
109     Left_AUX1_Input_Control,
110     Right_AUX1_Input_Control,
111     Left_AUX2_Input_Control,
112     Right_AUX2_Input_Control,
113     Left_DAC_Output_Control,
114     Right_DAC_Output_Control,
115     FS_And_Playback_Data_Format,
116     Interface_Configuration,
117     Pin_Control,
118     Error_Status_And_Initialization,
119     MODE_And_ID,
120     Loopback_Control,
121     Playback_Upper_Base_Count,
122     Playback_Lower_Base_Count,
123     Alternate_Feature_Enable_I,
124     Alternate_Feature_Enable_II,
125     Left_Line_Input_Control,
126     Right_Line_Input_Control,
127     Timer_Low_Base,
128     Timer_High_Base,
129     RESERVED,
130     Alternate_Feature_Enable_III,
131     Alternate_Feature_Status,
132     Version_Chip_ID,
133     Mono_Input_And_Output_Control,
134     RESERVED_2,
135     Capture_Data_Format,
136     RESERVED_3,
137     Capture_Upper_Base_Count,
138     Capture_Lower_Base_Count
139 };
140
141 static int freqs[2][8] = {
142     { 8000, 16000, 27420, 32000,    -1,    -1, 48000, 9000 },
143     { 5510, 11025, 18900, 22050, 37800, 44100, 33075, 6620 }
144 };
145
146 /* Tables courtesy http://hazelware.luggle.com/tutorials/mulawcompression.html */
147 static int16_t MuLawDecompressTable[256] =
148 {
149      -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
150      -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
151      -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
152      -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
153       -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
154       -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
155       -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
156       -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
157       -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
158       -1372, -1308, -1244, -1180, -1116, -1052,  -988,  -924,
159        -876,  -844,  -812,  -780,  -748,  -716,  -684,  -652,
160        -620,  -588,  -556,  -524,  -492,  -460,  -428,  -396,
161        -372,  -356,  -340,  -324,  -308,  -292,  -276,  -260,
162        -244,  -228,  -212,  -196,  -180,  -164,  -148,  -132,
163        -120,  -112,  -104,   -96,   -88,   -80,   -72,   -64,
164         -56,   -48,   -40,   -32,   -24,   -16,    -8,     0,
165       32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
166       23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
167       15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
168       11900, 11388, 10876, 10364,  9852,  9340,  8828,  8316,
169        7932,  7676,  7420,  7164,  6908,  6652,  6396,  6140,
170        5884,  5628,  5372,  5116,  4860,  4604,  4348,  4092,
171        3900,  3772,  3644,  3516,  3388,  3260,  3132,  3004,
172        2876,  2748,  2620,  2492,  2364,  2236,  2108,  1980,
173        1884,  1820,  1756,  1692,  1628,  1564,  1500,  1436,
174        1372,  1308,  1244,  1180,  1116,  1052,   988,   924,
175         876,   844,   812,   780,   748,   716,   684,   652,
176         620,   588,   556,   524,   492,   460,   428,   396,
177         372,   356,   340,   324,   308,   292,   276,   260,
178         244,   228,   212,   196,   180,   164,   148,   132,
179         120,   112,   104,    96,    88,    80,    72,    64,
180          56,    48,    40,    32,    24,    16,     8,     0
181 };
182
183 static int16_t ALawDecompressTable[256] =
184 {
185      -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
186      -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
187      -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
188      -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
189      -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
190      -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
191      -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472,
192      -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
193      -344,  -328,  -376,  -360,  -280,  -264,  -312,  -296,
194      -472,  -456,  -504,  -488,  -408,  -392,  -440,  -424,
195      -88,   -72,   -120,  -104,  -24,   -8,    -56,   -40,
196      -216,  -200,  -248,  -232,  -152,  -136,  -184,  -168,
197      -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
198      -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
199      -688,  -656,  -752,  -720,  -560,  -528,  -624,  -592,
200      -944,  -912,  -1008, -976,  -816,  -784,  -880,  -848,
201       5504,  5248,  6016,  5760,  4480,  4224,  4992,  4736,
202       7552,  7296,  8064,  7808,  6528,  6272,  7040,  6784,
203       2752,  2624,  3008,  2880,  2240,  2112,  2496,  2368,
204       3776,  3648,  4032,  3904,  3264,  3136,  3520,  3392,
205       22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
206       30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
207       11008, 10496, 12032, 11520, 8960,  8448,  9984,  9472,
208       15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
209       344,   328,   376,   360,   280,   264,   312,   296,
210       472,   456,   504,   488,   408,   392,   440,   424,
211       88,    72,   120,   104,    24,     8,    56,    40,
212       216,   200,   248,   232,   152,   136,   184,   168,
213       1376,  1312,  1504,  1440,  1120,  1056,  1248,  1184,
214       1888,  1824,  2016,  1952,  1632,  1568,  1760,  1696,
215       688,   656,   752,   720,   560,   528,   624,   592,
216       944,   912,  1008,   976,   816,   784,   880,   848
217 };
218
219 static void cs_reset(void *opaque)
220 {
221     CSState *s = opaque;
222
223     s->regs[Index_Address] = 0x40;
224     s->regs[Index_Data]    = 0x00;
225     s->regs[Status]        = 0x00;
226     s->regs[PIO_Data]      = 0x00;
227
228     s->dregs[Left_ADC_Input_Control]          = 0x00;
229     s->dregs[Right_ADC_Input_Control]         = 0x00;
230     s->dregs[Left_AUX1_Input_Control]         = 0x88;
231     s->dregs[Right_AUX1_Input_Control]        = 0x88;
232     s->dregs[Left_AUX2_Input_Control]         = 0x88;
233     s->dregs[Right_AUX2_Input_Control]        = 0x88;
234     s->dregs[Left_DAC_Output_Control]         = 0x80;
235     s->dregs[Right_DAC_Output_Control]        = 0x80;
236     s->dregs[FS_And_Playback_Data_Format]     = 0x00;
237     s->dregs[Interface_Configuration]         = 0x08;
238     s->dregs[Pin_Control]                     = 0x00;
239     s->dregs[Error_Status_And_Initialization] = 0x00;
240     s->dregs[MODE_And_ID]                     = 0x8a;
241     s->dregs[Loopback_Control]                = 0x00;
242     s->dregs[Playback_Upper_Base_Count]       = 0x00;
243     s->dregs[Playback_Lower_Base_Count]       = 0x00;
244     s->dregs[Alternate_Feature_Enable_I]      = 0x00;
245     s->dregs[Alternate_Feature_Enable_II]     = 0x00;
246     s->dregs[Left_Line_Input_Control]         = 0x88;
247     s->dregs[Right_Line_Input_Control]        = 0x88;
248     s->dregs[Timer_Low_Base]                  = 0x00;
249     s->dregs[Timer_High_Base]                 = 0x00;
250     s->dregs[RESERVED]                        = 0x00;
251     s->dregs[Alternate_Feature_Enable_III]    = 0x00;
252     s->dregs[Alternate_Feature_Status]        = 0x00;
253     s->dregs[Version_Chip_ID]                 = 0xa0;
254     s->dregs[Mono_Input_And_Output_Control]   = 0xa0;
255     s->dregs[RESERVED_2]                      = 0x00;
256     s->dregs[Capture_Data_Format]             = 0x00;
257     s->dregs[RESERVED_3]                      = 0x00;
258     s->dregs[Capture_Upper_Base_Count]        = 0x00;
259     s->dregs[Capture_Lower_Base_Count]        = 0x00;
260 }
261
262 static void cs_audio_callback (void *opaque, int free)
263 {
264     CSState *s = opaque;
265     s->audio_free = free;
266 }
267
268 static void cs_reset_voices (CSState *s, uint32_t val)
269 {
270     int xtal;
271     struct audsettings as;
272
273 #ifdef DEBUG_XLAW
274     if (val == 0 || val == 32)
275         val = (1 << 4) | (1 << 5);
276 #endif
277
278     xtal = val & 1;
279     as.freq = freqs[xtal][(val >> 1) & 7];
280
281     if (as.freq == -1) {
282         lerr ("unsupported frequency (val=%#x)\n", val);
283         goto error;
284     }
285
286     as.nchannels = (val & (1 << 4)) ? 2 : 1;
287     as.endianness = 0;
288     s->tab = NULL;
289
290     switch ((val >> 5) & ((s->dregs[MODE_And_ID] & MODE2) ? 7 : 3)) {
291     case 0:
292         as.fmt = AUD_FMT_U8;
293         s->shift = as.nchannels == 2;
294         break;
295
296     case 1:
297         s->tab = MuLawDecompressTable;
298         goto x_law;
299     case 3:
300         s->tab = ALawDecompressTable;
301     x_law:
302         as.fmt = AUD_FMT_S16;
303         as.endianness = AUDIO_HOST_ENDIANNESS;
304         s->shift = as.nchannels == 2;
305         break;
306
307     case 6:
308         as.endianness = 1;
309     case 2:
310         as.fmt = AUD_FMT_S16;
311         s->shift = as.nchannels;
312         break;
313
314     case 7:
315     case 4:
316         lerr ("attempt to use reserved format value (%#x)\n", val);
317         goto error;
318
319     case 5:
320         lerr ("ADPCM 4 bit IMA compatible format is not supported\n");
321         goto error;
322     }
323
324     s->voice = AUD_open_out (
325         &s->card,
326         s->voice,
327         "cs4231a",
328         s,
329         cs_audio_callback,
330         &as
331         );
332
333     if (s->dregs[Interface_Configuration] & PEN) {
334         if (!s->dma_running) {
335             DMA_hold_DREQ (s->dma);
336             AUD_set_active_out (s->voice, 1);
337             s->transferred = 0;
338         }
339         s->dma_running = 1;
340     }
341     else {
342         if (s->dma_running) {
343             DMA_release_DREQ (s->dma);
344             AUD_set_active_out (s->voice, 0);
345         }
346         s->dma_running = 0;
347     }
348     return;
349
350  error:
351     if (s->dma_running) {
352         DMA_release_DREQ (s->dma);
353         AUD_set_active_out (s->voice, 0);
354     }
355 }
356
357 IO_READ_PROTO (cs_read)
358 {
359     CSState *s = opaque;
360     uint32_t saddr, iaddr, ret;
361
362     saddr = GET_SADDR (addr);
363     iaddr = ~0U;
364
365     switch (saddr) {
366     case Index_Address:
367         ret = s->regs[saddr] & ~0x80;
368         break;
369
370     case Index_Data:
371         if (!(s->dregs[MODE_And_ID] & MODE2))
372             iaddr = s->regs[Index_Address] & 0x0f;
373         else
374             iaddr = s->regs[Index_Address] & 0x1f;
375
376         ret = s->dregs[iaddr];
377         if (iaddr == Error_Status_And_Initialization) {
378             /* keep SEAL happy */
379             if (s->aci_counter) {
380                 ret |= 1 << 5;
381                 s->aci_counter -= 1;
382             }
383         }
384         break;
385
386     default:
387         ret = s->regs[saddr];
388         break;
389     }
390     dolog ("read %d:%d -> %d\n", saddr, iaddr, ret);
391     return ret;
392 }
393
394 IO_WRITE_PROTO (cs_write)
395 {
396     CSState *s = opaque;
397     uint32_t saddr, iaddr;
398
399     saddr = GET_SADDR (addr);
400
401     switch (saddr) {
402     case Index_Address:
403         if (!(s->regs[Index_Address] & MCE) && (val & MCE)
404             && (s->dregs[Interface_Configuration] & (3 << 3)))
405             s->aci_counter = conf.aci_counter;
406
407         s->regs[Index_Address] = val & ~(1 << 7);
408         break;
409
410     case Index_Data:
411         if (!(s->dregs[MODE_And_ID] & MODE2))
412             iaddr = s->regs[Index_Address] & 0x0f;
413         else
414             iaddr = s->regs[Index_Address] & 0x1f;
415
416         switch (iaddr) {
417         case RESERVED:
418         case RESERVED_2:
419         case RESERVED_3:
420             lwarn ("attempt to write %#x to reserved indirect register %d\n",
421                    val, iaddr);
422             break;
423
424         case FS_And_Playback_Data_Format:
425             if (s->regs[Index_Address] & MCE) {
426                 cs_reset_voices (s, val);
427             }
428             else {
429                 if (s->dregs[Alternate_Feature_Status] & PMCE) {
430                     val = (val & ~0x0f) | (s->dregs[iaddr] & 0x0f);
431                     cs_reset_voices (s, val);
432                 }
433                 else {
434                     lwarn ("[P]MCE(%#x, %#x) is not set, val=%#x\n",
435                            s->regs[Index_Address],
436                            s->dregs[Alternate_Feature_Status],
437                            val);
438                     break;
439                 }
440             }
441             s->dregs[iaddr] = val;
442             break;
443
444         case Interface_Configuration:
445             val &= ~(1 << 5);   /* D5 is reserved */
446             s->dregs[iaddr] = val;
447             if (val & PPIO) {
448                 lwarn ("PIO is not supported (%#x)\n", val);
449                 break;
450             }
451             if (val & PEN) {
452                 if (!s->dma_running) {
453                     cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]);
454                 }
455             }
456             else {
457                 if (s->dma_running) {
458                     DMA_release_DREQ (s->dma);
459                     AUD_set_active_out (s->voice, 0);
460                     s->dma_running = 0;
461                 }
462             }
463             break;
464
465         case Error_Status_And_Initialization:
466             lwarn ("attempt to write to read only register %d\n", iaddr);
467             break;
468
469         case MODE_And_ID:
470             dolog ("val=%#x\n", val);
471             if (val & MODE2)
472                 s->dregs[iaddr] |= MODE2;
473             else
474                 s->dregs[iaddr] &= ~MODE2;
475             break;
476
477         case Alternate_Feature_Enable_I:
478             if (val & TE)
479                 lerr ("timer is not yet supported\n");
480             s->dregs[iaddr] = val;
481             break;
482
483         case Alternate_Feature_Status:
484             if ((s->dregs[iaddr] & PI) && !(val & PI)) {
485                 /* XXX: TI CI */
486                 qemu_irq_lower (s->pic[s->irq]);
487                 s->regs[Status] &= ~INT;
488             }
489             s->dregs[iaddr] = val;
490             break;
491
492         case Version_Chip_ID:
493             lwarn ("write to Version_Chip_ID register %#x\n", val);
494             s->dregs[iaddr] = val;
495             break;
496
497         default:
498             s->dregs[iaddr] = val;
499             break;
500         }
501         dolog ("written value %#x to indirect register %d\n", val, iaddr);
502         break;
503
504     case Status:
505         if (s->regs[Status] & INT) {
506             qemu_irq_lower (s->pic[s->irq]);
507         }
508         s->regs[Status] &= ~INT;
509         s->dregs[Alternate_Feature_Status] &= ~(PI | CI | TI);
510         break;
511
512     case PIO_Data:
513         lwarn ("attempt to write value %#x to PIO register\n", val);
514         break;
515     }
516 }
517
518 static int cs_write_audio (CSState *s, int nchan, int dma_pos,
519                            int dma_len, int len)
520 {
521     int temp, net;
522     uint8_t tmpbuf[4096];
523
524     temp = len;
525     net = 0;
526
527     while (temp) {
528         int left = dma_len - dma_pos;
529         int copied;
530         size_t to_copy;
531
532         to_copy = audio_MIN (temp, left);
533         if (to_copy > sizeof (tmpbuf)) {
534             to_copy = sizeof (tmpbuf);
535         }
536
537         copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy);
538         if (s->tab) {
539             int i;
540             int16_t linbuf[4096];
541
542             for (i = 0; i < copied; ++i)
543                 linbuf[i] = s->tab[tmpbuf[i]];
544             copied = AUD_write (s->voice, linbuf, copied << 1);
545             copied >>= 1;
546         }
547         else {
548             copied = AUD_write (s->voice, tmpbuf, copied);
549         }
550
551         temp -= copied;
552         dma_pos = (dma_pos + copied) % dma_len;
553         net += copied;
554
555         if (!copied) {
556             break;
557         }
558     }
559
560     return net;
561 }
562
563 static int cs_dma_read (void *opaque, int nchan, int dma_pos, int dma_len)
564 {
565     CSState *s = opaque;
566     int copy, written;
567     int till = -1;
568
569     copy = s->voice ? (s->audio_free >> (s->tab != NULL)) : dma_len;
570
571     if (s->dregs[Pin_Control] & IEN) {
572         till = (s->dregs[Playback_Lower_Base_Count]
573             | (s->dregs[Playback_Upper_Base_Count] << 8)) << s->shift;
574         till -= s->transferred;
575         copy = audio_MIN (till, copy);
576     }
577
578     if ((copy <= 0) || (dma_len <= 0)) {
579         return dma_pos;
580     }
581
582     written = cs_write_audio (s, nchan, dma_pos, dma_len, copy);
583
584     dma_pos = (dma_pos + written) % dma_len;
585     s->audio_free -= (written << (s->tab != NULL));
586
587     if (written == till) {
588         s->regs[Status] |= INT;
589         s->dregs[Alternate_Feature_Status] |= PI;
590         s->transferred = 0;
591         qemu_irq_raise (s->pic[s->irq]);
592     }
593     else {
594         s->transferred += written;
595     }
596
597     return dma_pos;
598 }
599
600 static void cs_save(QEMUFile *f, void *opaque)
601 {
602     CSState *s = opaque;
603     unsigned int i;
604     uint32_t val;
605
606     for (i = 0; i < CS_REGS; i++)
607         qemu_put_be32s(f, &s->regs[i]);
608
609     qemu_put_buffer(f, s->dregs, CS_DREGS);
610     val = s->dma_running; qemu_put_be32s(f, &val);
611     val = s->audio_free;  qemu_put_be32s(f, &val);
612     val = s->transferred; qemu_put_be32s(f, &val);
613     val = s->aci_counter; qemu_put_be32s(f, &val);
614 }
615
616 static int cs_load(QEMUFile *f, void *opaque, int version_id)
617 {
618     CSState *s = opaque;
619     unsigned int i;
620     uint32_t val, dma_running;
621
622     if (version_id > 1)
623         return -EINVAL;
624
625     for (i = 0; i < CS_REGS; i++)
626         qemu_get_be32s(f, &s->regs[i]);
627
628     qemu_get_buffer(f, s->dregs, CS_DREGS);
629
630     qemu_get_be32s(f, &dma_running);
631     qemu_get_be32s(f, &val); s->audio_free  = val;
632     qemu_get_be32s(f, &val); s->transferred = val;
633     qemu_get_be32s(f, &val); s->aci_counter = val;
634     if (dma_running && (s->dregs[Interface_Configuration] & PEN))
635         cs_reset_voices (s, s->dregs[FS_And_Playback_Data_Format]);
636     return 0;
637 }
638
639 int cs4231a_init (qemu_irq *pic)
640 {
641     int i;
642     CSState *s;
643
644     s = qemu_mallocz (sizeof (*s));
645
646     s->pic = pic;
647     s->irq = conf.irq;
648     s->dma = conf.dma;
649     s->port = conf.port;
650
651     for (i = 0; i < 4; i++) {
652         register_ioport_write (s->port + i, 1, 1, cs_write, s);
653         register_ioport_read (s->port + i, 1, 1, cs_read, s);
654     }
655
656     DMA_register_channel (s->dma, cs_dma_read, s);
657
658     register_savevm ("cs4231a", 0, 1, cs_save, cs_load, s);
659     qemu_register_reset (cs_reset, s);
660     cs_reset (s);
661
662     AUD_register_card ("cs4231a", &s->card);
663     return 0;
664 }