1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20
21#include "quakedef.h"
22#include "dosisms.h"
23
24int BLASTER_GetDMAPos(void);
25
26/*
27===============================================================================
28GUS SUPPORT
29
30===============================================================================
31*/
32
33qboolean GUS_Init (void);
34int GUS_GetDMAPos (void);
35void GUS_Shutdown (void);
36
37
38/*
39===============================================================================
40
41BLASTER SUPPORT
42
43===============================================================================
44*/
45
46short *dma_buffer=0;
47static int dma_size;
48static	int dma;
49
50static	int dsp_port;
51static	int irq;
52static	int low_dma;
53static	int high_dma;
54static	int mixer_port;
55static	int mpu401_port;
56
57int dsp_version;
58int dsp_minor_version;
59
60int timeconstant=-1;
61
62
63void PrintBits (byte b)
64{
65	int	i;
66	char	str[9];
67
68	for (i=0 ; i<8 ; i++)
69		str[i] = '0' + ((b & (1<<(7-i))) > 0);
70
71	str[8] = 0;
72	Con_Printf ("%s (%i)", str, b);
73}
74
75void SB_Info_f(void)
76{
77	Con_Printf ("BLASTER=%s\n", getenv("BLASTER"));
78	Con_Printf("dsp version=%d.%d\n", dsp_version, dsp_minor_version);
79	Con_Printf("dma=%d\n", dma);
80	if (timeconstant != -1)
81		Con_Printf("timeconstant=%d\n", timeconstant);
82	Con_Printf("dma position:%i\n", BLASTER_GetDMAPos ());
83}
84
85// =======================================================================
86// Interprets BLASTER variable
87// =======================================================================
88
89int GetBLASTER(void)
90{
91	char *BLASTER;
92	char *param;
93
94	BLASTER = getenv("BLASTER");
95	if (!BLASTER)
96		return 0;
97
98	param = strchr(BLASTER, 'A');
99	if (!param)
100		param = strchr(BLASTER, 'a');
101	if (!param)
102		return 0;
103	sscanf(param+1, "%x", &dsp_port);
104
105	param = strchr(BLASTER, 'I');
106	if (!param)
107		param = strchr(BLASTER, 'i');
108	if (!param)
109		return 0;
110	sscanf(param+1, "%d", &irq);
111
112	param = strchr(BLASTER, 'D');
113	if (!param)
114		param = strchr(BLASTER, 'd');
115	if (!param)
116		return 0;
117	sscanf(param+1, "%d", &low_dma);
118
119	param = strchr(BLASTER, 'H');
120	if (!param)
121		param = strchr(BLASTER, 'h');
122	if (param)
123		sscanf(param+1, "%d", &high_dma);
124
125	param = strchr(BLASTER, 'M');
126	if (!param)
127		param = strchr(BLASTER, 'm');
128	if (param)
129		sscanf(param+1, "%x", &mixer_port);
130	else
131		mixer_port = dsp_port;
132
133	param = strchr(BLASTER, 'P');
134	if (!param)
135		param = strchr(BLASTER, 'p');
136	if (param)
137		sscanf(param+1, "%x", &mpu401_port);
138
139	return 1;
140
141}
142
143// ==================================================================
144// Resets DSP.  Returns 0 on success.
145// ==================================================================
146
147int ResetDSP(void)
148{
149	volatile int i;
150
151	dos_outportb(dsp_port + 6, 1);
152	for (i=65536 ; i ; i--) ;
153	dos_outportb(dsp_port + 6, 0);
154	for (i=65536 ; i ; i--)
155	{
156		if (!(dos_inportb(dsp_port + 0xe) & 0x80)) continue;
157		if (dos_inportb(dsp_port + 0xa) == 0xaa) break;
158	}
159	if (i) return 0;
160	else return 1;
161
162}
163
164int ReadDSP(void)
165{
166	while (!(dos_inportb(dsp_port+0xe)&0x80)) ;
167	return dos_inportb(dsp_port+0xa);
168}
169
170void WriteDSP(int val)
171{
172	while ((dos_inportb(dsp_port+0xc)&0x80)) ;
173	dos_outportb(dsp_port+0xc, val);
174}
175
176int ReadMixer(int addr)
177{
178	dos_outportb(mixer_port+4, addr);
179	return dos_inportb(mixer_port+5);
180}
181
182void WriteMixer(int addr, int val)
183{
184	dos_outportb(mixer_port+4, addr);
185	dos_outportb(mixer_port+5, val);
186}
187
188int		oldmixervalue;
189
190/*
191================
192StartSB
193
194================
195*/
196void StartSB(void)
197{
198	int		i;
199
200// version 4.xx startup code
201	if (dsp_version >= 4)
202	{
203		Con_Printf("Version 4 SB startup\n");
204		WriteDSP(0xd1); // turn on speaker
205
206		WriteDSP(0x41);
207
208		WriteDSP(shm->speed>>8);
209		WriteDSP(shm->speed&0xff);
210
211		WriteDSP(0xb6);	// 16-bit output
212		WriteDSP(0x30);	// stereo
213		WriteDSP((shm->samples-1) & 0xff);	// # of samples - 1
214		WriteDSP((shm->samples-1) >> 8);
215	}
216// version 3.xx startup code
217	else if (dsp_version == 3)
218	{
219		Con_Printf("Version 3 SB startup\n");
220		WriteDSP(0xd1); // turn on speaker
221
222		oldmixervalue = ReadMixer (0xe);
223		WriteMixer (0xe, oldmixervalue | 0x2);// turn on stereo
224
225		WriteDSP(0x14);			// send one byte
226		WriteDSP(0x0);
227		WriteDSP(0x0);
228
229		for (i=0 ; i<0x10000 ; i++)
230			dos_inportb(dsp_port+0xe);		// ack the dsp
231
232		timeconstant = 65536-(256000000/(shm->channels*shm->speed));
233		WriteDSP(0x40);
234		WriteDSP(timeconstant>>8);
235
236		WriteMixer (0xe, ReadMixer(0xe) | 0x20);// turn off filter
237
238		WriteDSP(0x48);
239		WriteDSP((shm->samples-1) & 0xff);	// # of samples - 1
240		WriteDSP((shm->samples-1) >> 8);
241
242		WriteDSP(0x90); // high speed 8 bit stereo
243	}
244// normal speed mono
245	else
246	{
247		Con_Printf("Version 2 SB startup\n");
248		WriteDSP(0xd1); // turn on speaker
249
250		timeconstant = 65536-(256000000/(shm->channels*shm->speed));
251		WriteDSP(0x40);
252		WriteDSP(timeconstant>>8);
253
254		WriteDSP(0x48);
255		WriteDSP((shm->samples-1) & 0xff);	// # of samples - 1
256		WriteDSP((shm->samples-1) >> 8);
257
258		WriteDSP(0x1c); // normal speed 8 bit mono
259	}
260}
261
262static int page_reg[] = { 0x87, 0x83, 0x81, 0x82, 0x8f, 0x8b, 0x89, 0x8a };
263static int addr_reg[] = { 0, 2, 4, 6, 0xc0, 0xc4, 0xc8, 0xcc };
264static int count_reg[] = { 1, 3, 5, 7, 0xc2, 0xc6, 0xca, 0xce };
265
266static int mode_reg;
267static int flipflop_reg;
268static int disable_reg;
269static int clear_reg;
270
271/*
272================
273StartDMA
274
275================
276*/
277void StartDMA(void)
278{
279	int mode;
280	int realaddr;
281
282	realaddr = ptr2real(dma_buffer);
283
284// use a high dma channel if specified
285	if (high_dma && dsp_version >= 4)	// 8 bit snd can never use 16 bit dma
286		dma = high_dma;
287	else
288		dma = low_dma;
289
290	Con_Printf ("Using DMA channel %i\n", dma);
291
292	if (dma > 3)
293	{
294		mode_reg = 0xd6;
295		flipflop_reg = 0xd8;
296		disable_reg = 0xd4;
297		clear_reg = 0xdc;
298	}
299	else
300	{
301		mode_reg = 0xb;
302		flipflop_reg = 0xc;
303		disable_reg = 0xa;
304		clear_reg = 0xe;
305	}
306
307	dos_outportb(disable_reg, dma|4);	// disable channel
308	// set mode- see "undocumented pc", p.876
309	mode =	(1<<6)	// single-cycle
310		+(0<<5)		// address increment
311		+(1<<4)		// auto-init dma
312		+(2<<2)		// read
313		+(dma&3);	// channel #
314	dos_outportb(mode_reg, mode);
315
316// set address
317	// set page
318	dos_outportb(page_reg[dma], realaddr >> 16);
319
320	if (dma > 3)
321	{	// address is in words
322		dos_outportb(flipflop_reg, 0);		// prepare to send 16-bit value
323		dos_outportb(addr_reg[dma], (realaddr>>1) & 0xff);
324		dos_outportb(addr_reg[dma], (realaddr>>9) & 0xff);
325
326		dos_outportb(flipflop_reg, 0);		// prepare to send 16-bit value
327		dos_outportb(count_reg[dma], ((dma_size>>1)-1) & 0xff);
328		dos_outportb(count_reg[dma], ((dma_size>>1)-1) >> 8);
329	}
330	else
331	{	// address is in bytes
332		dos_outportb(flipflop_reg, 0);		// prepare to send 16-bit value
333		dos_outportb(addr_reg[dma], realaddr & 0xff);
334		dos_outportb(addr_reg[dma], (realaddr>>8) & 0xff);
335
336		dos_outportb(flipflop_reg, 0);		// prepare to send 16-bit value
337		dos_outportb(count_reg[dma], (dma_size-1) & 0xff);
338		dos_outportb(count_reg[dma], (dma_size-1) >> 8);
339	}
340
341	dos_outportb(clear_reg, 0);		// clear write mask
342	dos_outportb(disable_reg, dma&~4);
343}
344
345
346/*
347==================
348BLASTER_Init
349
350Returns false if nothing is found.
351==================
352*/
353qboolean BLASTER_Init(void)
354{
355	int 	size;
356	int 	realaddr;
357	int 	rc;
358	int		p;
359
360	shm = 0;
361	rc = 0;
362
363//
364// must have a blaster variable set
365//
366	if (!GetBLASTER())
367	{
368		Con_NotifyBox (
369		"The BLASTER environment variable\n"
370		"is not set, sound effects are\n"
371		"disabled.  See README.TXT for help.\n"
372		);
373		return 0;
374	}
375
376	if (ResetDSP())
377	{
378		Con_Printf("Could not reset SB");
379		return 0;
380	}
381
382//
383// get dsp version
384//
385	WriteDSP(0xe1);
386	dsp_version = ReadDSP();
387	dsp_minor_version = ReadDSP();
388
389// we need at least v2 for auto-init dma
390	if (dsp_version < 2)
391	{
392		Con_Printf ("Sound blaster must be at least v2.0\n");
393		return 0;
394	}
395
396// allow command line parm to set quality down
397	p = COM_CheckParm ("-dsp");
398	if (p && p < com_argc - 1)
399	{
400		p = Q_atoi (com_argv[p+1]);
401		if (p < 2 || p > 4)
402			Con_Printf ("-dsp parameter can only be 2, 3, or 4\n");
403		else if (p > dsp_version)
404			Con_Printf ("Can't -dsp %i on v%i hardware\n", p, dsp_version);
405		else
406			dsp_version = p;
407	}
408
409
410// everyone does 11khz sampling rate unless told otherwise
411	shm = &sn;
412	shm->speed = 11025;
413	rc = COM_CheckParm("-sspeed");
414	if (rc)
415		shm->speed = Q_atoi(com_argv[rc+1]);
416
417// version 4 cards (sb 16) do 16 bit stereo
418	if (dsp_version >= 4)
419	{
420		shm->channels = 2;
421		shm->samplebits = 16;
422	}
423// version 3 cards (sb pro) do 8 bit stereo
424	else if (dsp_version == 3)
425	{
426		shm->channels = 2;
427		shm->samplebits = 8;
428	}
429// v2 cards do 8 bit mono
430	else
431	{
432		shm->channels = 1;
433		shm->samplebits = 8;
434	}
435
436
437	Cmd_AddCommand("sbinfo", SB_Info_f);
438	size = 4096;
439
440// allocate 8k and get a 4k-aligned buffer from it
441	dma_buffer = dos_getmemory(size*2);
442	if (!dma_buffer)
443	{
444		Con_Printf("Couldn't allocate sound dma buffer");
445		return false;
446	}
447
448	realaddr = ptr2real(dma_buffer);
449	realaddr = (realaddr + size) & ~(size-1);
450	dma_buffer = (short *) real2ptr(realaddr);
451	dma_size = size;
452
453	memset(dma_buffer, 0, dma_size);
454
455	shm->soundalive = true;
456	shm->splitbuffer = false;
457
458	shm->samples = size/(shm->samplebits/8);
459	shm->samplepos = 0;
460	shm->submission_chunk = 1;
461	shm->buffer = (unsigned char *) dma_buffer;
462	shm->samples = size/(shm->samplebits/8);
463
464	StartDMA();
465	StartSB();
466
467	return true;
468}
469
470
471/*
472==============
473BLASTER_GetDMAPos
474
475return the current sample position (in mono samples read)
476inside the recirculating dma buffer, so the mixing code will know
477how many sample are required to fill it up.
478===============
479*/
480int BLASTER_GetDMAPos(void)
481{
482	int count;
483
484// this function is called often.  acknowledge the transfer completions
485// all the time so that it loops
486	if (dsp_version >= 4)
487		dos_inportb(dsp_port+0xf);	// 16 bit audio
488	else
489		dos_inportb(dsp_port+0xe);	// 8 bit audio
490
491// clear 16-bit reg flip-flop
492// load the current dma count register
493	if (dma < 4)
494	{
495		dos_outportb(0xc, 0);
496		count = dos_inportb(dma*2+1);
497		count += dos_inportb(dma*2+1) << 8;
498		if (shm->samplebits == 16)
499			count /= 2;
500		count = shm->samples - (count+1);
501	}
502	else
503	{
504		dos_outportb(0xd8, 0);
505		count = dos_inportb(0xc0+(dma-4)*4+2);
506		count += dos_inportb(0xc0+(dma-4)*4+2) << 8;
507		if (shm->samplebits == 8)
508			count *= 2;
509		count = shm->samples - (count+1);
510	}
511
512//	Con_Printf("DMA pos = 0x%x\n", count);
513
514	shm->samplepos = count & (shm->samples-1);
515	return shm->samplepos;
516
517}
518
519/*
520==============
521BLASTER_Shutdown
522
523Reset the sound device for exiting
524===============
525*/
526void BLASTER_Shutdown(void)
527{
528	if (dsp_version >= 4)
529	{
530	}
531	else if (dsp_version == 3)
532	{
533		ResetDSP ();			// stop high speed mode
534		WriteMixer (0xe, oldmixervalue); // turn stereo off and filter on
535	}
536	else
537	{
538
539	}
540
541	WriteDSP(0xd3); // turn off speaker
542	ResetDSP ();
543
544	dos_outportb(disable_reg, dma|4);	// disable dma channel
545}
546
547
548
549/*
550===============================================================================
551
552INTERFACE
553
554===============================================================================
555*/
556
557typedef enum
558{
559	dma_none,
560	dma_blaster,
561	dma_gus
562} dmacard_t;
563
564dmacard_t	dmacard;
565
566/*
567==================
568SNDDM_Init
569
570Try to find a sound device to mix for.
571Returns false if nothing is found.
572Returns true and fills in the "shm" structure with information for the mixer.
573==================
574*/
575qboolean SNDDMA_Init(void)
576{
577	if (GUS_Init ())
578	{
579		dmacard = dma_gus;
580		return true;
581	}
582	if (BLASTER_Init ())
583	{
584		dmacard = dma_blaster;
585		return true;
586	}
587
588	dmacard = dma_none;
589
590	return false;
591}
592
593
594/*
595==============
596SNDDMA_GetDMAPos
597
598return the current sample position (in mono samples, not stereo)
599inside the recirculating dma buffer, so the mixing code will know
600how many sample are required to fill it up.
601===============
602*/
603int SNDDMA_GetDMAPos(void)
604{
605	switch (dmacard)
606	{
607	case dma_blaster:
608		return BLASTER_GetDMAPos ();
609	case dma_gus:
610		return GUS_GetDMAPos ();
611	case dma_none:
612		break;
613	}
614
615	return 0;
616}
617
618/*
619==============
620SNDDMA_Shutdown
621
622Reset the sound device for exiting
623===============
624*/
625void SNDDMA_Shutdown(void)
626{
627	switch (dmacard)
628	{
629	case dma_blaster:
630		BLASTER_Shutdown ();
631		break;
632	case dma_gus:
633		GUS_Shutdown ();
634		break;
635	case dma_none:
636		break;
637	}
638
639	dmacard = dma_none;
640	return;
641}
642
643/*
644==============
645SNDDMA_Submit
646
647Send sound to device if buffer isn't really the dma buffer
648===============
649*/
650void SNDDMA_Submit(void)
651{
652}
653
654