initial import
[dosrtxon] / libs / mikmod / drivers / dos / doswss.c
1 /*      MikMod sound library
2         (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for
3         complete list.
4
5         This library is free software; you can redistribute it and/or modify
6         it under the terms of the GNU Library General Public License as
7         published by the Free Software Foundation; either version 2 of
8         the License, or (at your option) any later version.
9
10         This program 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 Library General Public License for more details.
14
15         You should have received a copy of the GNU Library General Public
16         License along with this library; if not, write to the Free Software
17         Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18         02111-1307, USA.
19 */
20
21 /*==============================================================================
22
23   Windows Sound System I/O routines (CS42XX, ESS18XX, GUS+DaughterBoard etc)
24   Written by Andrew Zabolotny <bit@eltech.ru>
25
26 ==============================================================================*/
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #ifdef DRV_WSS
33
34 #include <stdlib.h>
35 #include <dpmi.h>
36 #include <go32.h>
37 #include <dos.h>
38 #include <sys/nearptr.h>
39 #include <sys/farptr.h>
40 #include <string.h>
41
42 #include "doswss.h"
43
44 /********************************************* Private variables/routines *****/
45
46 __wss_state wss;
47
48 /* WSS frequency rates... lower bit selects one of two frequency generators */
49 static unsigned int wss_rates[14][2] = {
50         {5510, 0x00 | WSSM_XTAL2},
51         {6620, 0x0E | WSSM_XTAL2},
52         {8000, 0x00 | WSSM_XTAL1},
53         {9600, 0x0E | WSSM_XTAL1},
54         {11025, 0x02 | WSSM_XTAL2},
55         {16000, 0x02 | WSSM_XTAL1},
56         {18900, 0x04 | WSSM_XTAL2},
57         {22050, 0x06 | WSSM_XTAL2},
58         {27420, 0x04 | WSSM_XTAL1},
59         {32000, 0x06 | WSSM_XTAL1},
60         {33075, 0x0C | WSSM_XTAL2},
61         {37800, 0x08 | WSSM_XTAL2},
62         {44100, 0x0A | WSSM_XTAL2},
63         {48000, 0x0C | WSSM_XTAL1}
64 };
65
66 static void wss_irq()
67 {
68         /* Make sure its not a spurious IRQ */
69         if (!irq_check(wss.irq_handle))
70                 return;
71
72         wss.irqcount++;
73
74         /* Clear IRQ status */
75         outportb(WSS_STATUS, 0);
76
77         /* Write transfer count again */
78         __wss_outreg(WSSR_COUNT_LOW, wss.samples & 0xff);
79         __wss_outreg(WSSR_COUNT_HIGH, wss.samples >> 8);
80         irq_ack(wss.irq_handle);
81
82         enable();
83         if (wss.timer_callback)
84                 wss.timer_callback();
85 }
86
87 static void wss_irq_end()
88 {
89 }
90
91 /* WSS accepts some conventional values instead of frequency in Hz... */
92 static unsigned char __wss_getrate(unsigned int *freq)
93 {
94         int i, best = -1, delta = 0xffff;
95
96         for (i = 0; i < 14; i++) {
97                 int newdelta = abs(wss_rates[i][0] - *freq);
98                 if (newdelta < delta)
99                         best = i, delta = newdelta;
100         }
101
102         *freq = wss_rates[best][0];
103         return wss_rates[best][1];
104 }
105
106 /* Check if we really have a WSS compatible card on given address */
107 static boolean __wss_ping()
108 {
109         /* Disable CODEC operations first */
110         __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
111         /* Now put some harmless values in registers and check them */
112         __wss_outreg(WSSR_COUNT_LOW, 0xaa);
113         __wss_outreg(WSSR_COUNT_HIGH, 0x55);
114         return (__wss_inreg(WSSR_COUNT_LOW) == 0xaa)
115           && (__wss_inreg(WSSR_COUNT_HIGH) == 0x55);
116 }
117
118 static boolean __wss_reset()
119 {
120         int count;
121
122         /* Disable output */
123         wss_output(FALSE);
124
125         /* Now select the test/initialization register */
126         count = 10000;
127         while (inportb(WSS_ADDR) != WSSR_TEST_INIT) {
128                 outportb(WSS_ADDR, WSSR_TEST_INIT);
129                 if (!--count)
130                         return FALSE;
131         }
132
133         count = 10000;
134         while (inportb(WSS_DATA) & WSSM_CALIB_IN_PROGRESS) {
135                 outportb(WSS_ADDR, WSSR_TEST_INIT);
136                 if (!--count)
137                         return FALSE;
138         }
139
140         /* Enable playback IRQ */
141         __wss_regbit_set(WSSR_PIN_CTRL, WSSM_IRQ_ENABLE);
142         __wss_outreg(WSSR_IRQ_STATUS, WSSM_PLAYBACK_IRQ);
143
144         /* Clear IRQ status */
145         outportb(WSS_STATUS, 0);
146
147         return TRUE;
148 }
149
150 static boolean __wss_setformat(unsigned char format)
151 {
152         int count;
153
154         outportb(WSS_ADDR, WSSM_MCE | WSSR_PLAY_FORMAT);
155         outportb(WSS_DATA, format);
156         inportb(WSS_DATA);                      /* ERRATA SHEETS ... */
157         inportb(WSS_DATA);                      /* ERRATA SHEETS ... */
158
159         /* Wait end of syncronization ... */
160         if (!__wss_wait())
161                 return FALSE;
162
163         /* Turn off the ModeChangeEnable bit: do it until it works */
164         count = 10000;
165         while (inportb(WSS_ADDR) != WSSR_PLAY_FORMAT) {
166                 outportb(WSS_ADDR, WSSR_PLAY_FORMAT);
167                 if (!--count)
168                         return FALSE;
169         }
170
171         return __wss_reset();
172 }
173
174 /**************************************************** WSS detection stuff *****/
175
176 static int __wss_irq_irqdetect(int irqno)
177 {
178         unsigned char status = inportb(WSS_STATUS);
179         /* Clear IRQ status */
180         outportb(WSS_STATUS, 0);
181         /* Reset transfer counter */
182         __wss_outreg(WSSR_COUNT_LOW, 0);
183         __wss_outreg(WSSR_COUNT_HIGH, 0);
184         return (status & WSSM_INT);
185 }
186
187 static boolean __wss_detect()
188 {
189         /* First find the port number */
190         if (!wss.port) {
191                 static unsigned int wss_ports[] =
192                   { 0x32c, 0x530, 0x604, 0xE80, 0xF40 };
193                 int i;
194                 for (i = 0; i < 5; i++) {
195                         wss.port = wss_ports[i];
196                         if (__wss_ping())
197                                 break;
198                 }
199                 if (i < 0) {
200                         wss.port = 0;
201                         return FALSE;
202                 }
203         }
204
205         /* Now disable output */
206         wss_output(FALSE);
207
208         /* Detect the DMA channel */
209         if (!wss.dma) {
210                 static int __dma[] = { 0, 1, 3 };
211                 int i;
212
213                 /* Enable playback IRQ */
214                 __wss_regbit_set(WSSR_PIN_CTRL, WSSM_IRQ_ENABLE);
215                 __wss_outreg(WSSR_IRQ_STATUS, WSSM_PLAYBACK_IRQ);
216
217                 /* Start a short DMA transfer and check if DMA count is zero */
218                 for (i = 0; i < 3; i++) {
219                         unsigned int timer, status, freq = 44100;
220
221                         wss.dma = __dma[i];
222
223                         dma_disable(wss.dma);
224                         dma_set_mode(wss.dma, DMA_MODE_WRITE);
225                         dma_clear_ff(wss.dma);
226                         dma_set_count(wss.dma, 10);
227                         dma_enable(wss.dma);
228
229                         /* Clear IRQ status */
230                         outportb(WSS_STATUS, 0);
231
232                         __wss_setformat(__wss_getrate(&freq));
233                         __wss_outreg(WSSR_COUNT_LOW, 1);
234                         __wss_outreg(WSSR_COUNT_HIGH, 0);
235                         /* Tell codec to start transfer */
236                         __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
237
238                         _farsetsel(_dos_ds);
239                         timer = _farnspeekl(0x46c);
240
241                         while (_farnspeekl(0x46c) - timer <= 2)
242                                 if (dma_get_count(wss.dma) == 0)
243                                         break;
244                         __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
245                         dma_disable(wss.dma);
246
247                         /* Now check if DMA transfer count is zero and an IRQ is pending */
248                         status = inportb(WSS_STATUS);
249                         outportb(WSS_STATUS, 0);
250                         if ((dma_get_count(wss.dma) == 0) && (status & WSSM_INT))
251                                 break;
252
253                         wss.dma = 0;
254                 }
255
256                 if (!wss.dma)
257                         return FALSE;
258         }
259
260         /* Now detect the IRQ number */
261         if (!wss.irq) {
262                 unsigned int i, irqmask, freq = 5510;
263                 unsigned long timer, delta = 0x7fffffff;
264
265                 /* IRQ can be one of 2,3,5,7,10 */
266                 irq_detect_start(0x04ac, __wss_irq_irqdetect);
267
268                 dma_disable(wss.dma);
269                 dma_set_mode(wss.dma, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
270                 dma_clear_ff(wss.dma);
271                 dma_set_count(wss.dma, 1);
272                 dma_enable(wss.dma);
273
274                 __wss_setformat(__wss_getrate(&freq));
275
276                 /* Clear IRQ status */
277                 outportb(WSS_STATUS, 0);
278
279                 __wss_outreg(WSSR_COUNT_LOW, 0);
280                 __wss_outreg(WSSR_COUNT_HIGH, 0);
281
282                 /* Prepare timeout counter */
283                 _farsetsel(_dos_ds);
284                 timer = _farnspeekl(0x46c);
285                 while (timer == _farnspeekl(0x46c));
286                 timer = _farnspeekl(0x46c);
287
288                 /* Reset all IRQ counters */
289                 irq_detect_clear();
290
291                 /* Tell codec to start transfer */
292                 __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
293
294                 /* Now wait 1/18 seconds */
295                 while (timer == _farnspeekl(0x46c));
296                 __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
297                 dma_disable(wss.dma);
298
299                 /* Given frequency 5510Hz, a buffer size of 1 byte and a time interval
300                    of 1/18.2 second, we should have received about 302 interrupts */
301                 for (i = 2; i <= 10; i++) {
302                         int count = abs(302 - irq_detect_get(i, &irqmask));
303                         if (count < delta)
304                                 wss.irq = i, delta = count;
305                 }
306                 if (delta > 150)
307                         wss.irq = 0;
308
309                 irq_detect_end();
310                 if (!wss.irq)
311                         return FALSE;
312         }
313
314         return TRUE;
315 }
316
317 /*************************************************** High-level interface *****/
318
319 /* Detect whenever WSS is present and fill "wss" structure */
320 boolean wss_detect()
321 {
322         char *env;
323
324         /* Try to find the port and DMA from environment */
325         env = getenv("WSS");
326
327         while (env && *env) {
328                 /* Skip whitespace */
329                 while ((*env == ' ') || (*env == '\t'))
330                         env++;
331                 if (!*env)
332                         break;
333
334                 switch (*env++) {
335                   case 'A':
336                   case 'a':
337                         if (!wss.port)
338                                 wss.port = strtol(env, &env, 16);
339                         break;
340                   case 'I':
341                   case 'i':
342                         if (!wss.irq)
343                                 wss.irq = strtol(env, &env, 10);
344                         break;
345                   case 'D':
346                   case 'd':
347                         if (!wss.dma)
348                                 wss.dma = strtol(env, &env, 10);
349                         break;
350                   default:
351                         /* Skip other values */
352                         while (*env && (*env != ' ') && (*env != '\t'))
353                                 env++;
354                         break;
355                 }
356         }
357
358         /* Try to fill the gaps in wss hardware parameters */
359         __wss_detect();
360
361         if (!wss.port || !wss.irq || !wss.dma)
362                 return FALSE;
363
364         if (!__wss_ping())
365                 return FALSE;
366
367         if (!__wss_reset())
368                 return FALSE;
369
370         wss.ok = 1;
371         return TRUE;
372 }
373
374 /* Reset WSS */
375 void wss_reset()
376 {
377         wss_stop_dma();
378         __wss_reset();
379 }
380
381 /* Open WSS for usage */
382 boolean wss_open()
383 {
384         __dpmi_meminfo struct_info;
385
386         if (!wss.ok)
387                 if (!wss_detect())
388                         return FALSE;
389
390         if (wss.open)
391                 return FALSE;
392
393         /* Now lock the wss structure in memory */
394         struct_info.address = __djgpp_base_address + (unsigned long)&wss;
395         struct_info.size = sizeof(wss);
396         if (__dpmi_lock_linear_region(&struct_info))
397                 return FALSE;
398
399         /* Hook the WSS IRQ */
400         wss.irq_handle =
401           irq_hook(wss.irq, wss_irq, (long)wss_irq_end - (long)wss_irq);
402         if (!wss.irq_handle) {
403                 __dpmi_unlock_linear_region(&struct_info);
404                 return FALSE;
405         }
406
407         /* Enable the interrupt */
408         irq_enable(wss.irq_handle);
409         if (wss.irq > 7)
410                 _irq_enable(2);
411
412         wss.open++;
413
414         return TRUE;
415 }
416
417 /* Finish working with WSS */
418 boolean wss_close()
419 {
420         __dpmi_meminfo struct_info;
421         if (!wss.open)
422                 return FALSE;
423
424         wss.open--;
425
426         /* Stop/free DMA buffer */
427         wss_stop_dma();
428
429         /* Unhook IRQ */
430         irq_unhook(wss.irq_handle);
431         wss.irq_handle = NULL;
432
433         /* Unlock the wss structure */
434         struct_info.address = __djgpp_base_address + (unsigned long)&wss;
435         struct_info.size = sizeof(wss);
436         __dpmi_unlock_linear_region(&struct_info);
437
438         return TRUE;
439 }
440
441 /* Adjust frequency rate to nearest WSS available */
442 unsigned int wss_adjust_freq(unsigned int freq)
443 {
444         __wss_getrate(&freq);
445         return freq;
446 }
447
448 /* Enable/disable speaker output */
449 /* Start playing from DMA buffer in either 8/16 bit mono/stereo */
450 boolean wss_start_dma(unsigned char mode, unsigned int freq)
451 {
452         int dmabuffsize;
453         unsigned char format;
454
455         /* Stop DMA transfer if it is enabled */
456         wss_stop_dma();
457
458         /* Sanity check: we support only 8-bit unsigned and 16-bit signed formats */
459         if (((mode & WSSMODE_16BITS) && !(mode & WSSMODE_SIGNED))
460                 || (!(mode & WSSMODE_16BITS) && (mode & WSSMODE_SIGNED)))
461                 return FALSE;
462
463         /* Find the nearest frequency divisor (rate) */
464         format = __wss_getrate(&freq);
465         wss.mode = mode;
466
467         /* Get a DMA buffer enough for a 1sec interval... 4K <= dmasize <= 32K */
468         dmabuffsize = freq;
469         if (mode & WSSMODE_STEREO)
470                 dmabuffsize *= 2;
471         if (mode & WSSMODE_16BITS)
472                 dmabuffsize *= 2;
473         dmabuffsize >>= 2;
474         if (dmabuffsize < 4096)
475                 dmabuffsize = 4096;
476         if (dmabuffsize > 32768)
477                 dmabuffsize = 32768;
478         dmabuffsize = (dmabuffsize + 255) & 0xffffff00;
479
480         wss.dma_buff = dma_allocate(wss.dma, dmabuffsize);
481         if (!wss.dma_buff)
482                 return FALSE;
483
484         /* Fill DMA buffer with silence */
485         dmabuffsize = wss.dma_buff->size;
486         if (mode & WSSMODE_SIGNED)
487                 memset(wss.dma_buff->linear, 0, dmabuffsize);
488         else
489                 memset(wss.dma_buff->linear, 0x80, dmabuffsize);
490
491         /* Check data size and build a WSSR_PLAY_FORMAT value accordingly */
492         wss.samples = dmabuffsize;
493         if (mode & WSSMODE_16BITS) {
494                 wss.samples >>= 1;
495                 format |= WSSM_16BITS;
496         }
497
498         if (mode & WSSMODE_STEREO) {
499                 wss.samples >>= 1;
500                 format |= WSSM_STEREO;
501         }
502
503         if (!__wss_setformat(format)) {
504                 wss_stop_dma();
505                 return FALSE;
506         }
507
508         /* Prime DMA for transfer */
509         dma_start(wss.dma_buff, dmabuffsize, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
510
511         /* Tell codec how many samples to transfer */
512         wss.samples = (wss.samples >> 1) - 1;
513         __wss_outreg(WSSR_COUNT_LOW, wss.samples & 0xff);
514         __wss_outreg(WSSR_COUNT_HIGH, wss.samples >> 8);
515
516         /* Tell codec to start transfer */
517         __wss_regbit_set(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
518
519         return TRUE;
520 }
521
522 /* Stop playing from DMA buffer */
523 void wss_stop_dma()
524 {
525         if (!wss.dma_buff)
526                 return;
527
528         __wss_regbit_reset(WSSR_IFACE_CTRL, WSSM_PLAYBACK_ENABLE);
529         dma_disable(wss.dma);
530         dma_free(wss.dma_buff);
531         wss.dma_buff = NULL;
532 }
533
534 /* Query current position/total size of the DMA buffer */
535 void wss_query_dma(unsigned int *dma_size, unsigned int *dma_pos)
536 {
537         unsigned int dma_left;
538         *dma_size = wss.dma_buff->size;
539         /* It can happen we try to read DMA count when HI/LO bytes will be
540            inconsistent */
541         for (;;) {
542                 unsigned int dma_left_test;
543                 dma_clear_ff(wss.dma);
544                 dma_left_test = dma_get_count(wss.dma);
545                 dma_left = dma_get_count(wss.dma);
546                 if ((dma_left >= dma_left_test) && (dma_left - dma_left_test < 10))
547                         break;
548         }
549         *dma_pos = *dma_size - dma_left;
550 }
551
552 void wss_output(boolean enable)
553 {
554         if (enable)
555                 wss.curlevel = wss.level;
556         else
557                 wss.curlevel = 0x3f;
558
559         __wss_outreg(WSSR_MASTER_L, wss.curlevel);
560         __wss_outreg(WSSR_MASTER_R, wss.curlevel);
561 }
562
563 void wss_level(int level)
564 {
565         if (level < 0)
566                 level = 0;
567         if (level > 63)
568                 level = 63;
569         wss.curlevel = wss.level = level ^ 63;
570
571         __wss_outreg(WSSR_MASTER_L, wss.curlevel);
572         __wss_outreg(WSSR_MASTER_R, wss.curlevel);
573 }
574
575 #endif /* DRV_WSS */
576
577 /* ex:set ts=4: */