xref: /netbsd/sys/arch/amiga/amiga/cc.c (revision 5134f28f)
1 /*	$NetBSD: cc.c,v 1.28 2021/08/12 20:13:54 andvar Exp $	*/
2 
3 /*
4  * Copyright (c) 1994 Christian E. Hopps
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed by Christian E. Hopps.
18  * 4. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: cc.c,v 1.28 2021/08/12 20:13:54 andvar Exp $");
35 
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/queue.h>
39 
40 #include <amiga/amiga/custom.h>
41 #include <amiga/amiga/cc.h>
42 #include "audio.h"
43 
44 vaddr_t CUSTOMADDR, CUSTOMbase;
45 
46 #if defined (__GNUC__)
47 #define INLINE inline
48 #else
49 #define INLINE
50 #endif
51 
52 /* init all the "custom chips" */
53 void
custom_chips_init(void)54 custom_chips_init(void)
55 {
56 	cc_init_chipmem();
57 	cc_init_vbl();
58 	cc_init_audio();
59 	cc_init_blitter();
60 	cc_init_copper();
61 }
62 
63 /*
64  * Vertical blank interrupt server chains.
65  */
66 LIST_HEAD(vbllist, vbl_node) vbl_list;
67 
68 void
turn_vbl_function_off(struct vbl_node * n)69 turn_vbl_function_off(struct vbl_node *n)
70 {
71 	if (n->flags & VBLNF_OFF)
72 		return;
73 
74 	n->flags |= VBLNF_TURNOFF;
75 	while ((n->flags & VBLNF_OFF) == 0)
76 		;
77 }
78 
79 /* allow function to be called on next vbl interrupt. */
80 void
turn_vbl_function_on(struct vbl_node * n)81 turn_vbl_function_on(struct vbl_node *n)
82 {
83 	n->flags &= (short) ~(VBLNF_OFF);
84 }
85 
86 void
add_vbl_function(struct vbl_node * add,short priority,void * data)87 add_vbl_function(struct vbl_node *add, short priority, void *data)
88 {
89 	int s;
90 	struct vbl_node *n, *prev;
91 
92 	s = spl3();
93 	prev = NULL;
94 	LIST_FOREACH(n, &vbl_list, link) {
95 		if (add->priority > n->priority) {
96 			/* insert add_node before. */
97 			if (prev == NULL) {
98 				LIST_INSERT_HEAD(&vbl_list, add, link);
99 			} else {
100 				LIST_INSERT_AFTER(prev, add, link);
101 			}
102 			add = NULL;
103 			break;
104 		}
105 		prev = n;
106 	}
107 	if (add != NULL) {
108 		if (prev == NULL) {
109 			LIST_INSERT_HEAD(&vbl_list, add, link);
110 		} else {
111 			LIST_INSERT_AFTER(prev, add, link);
112 		}
113 	}
114 	splx(s);
115 }
116 
117 void
remove_vbl_function(struct vbl_node * n)118 remove_vbl_function(struct vbl_node *n)
119 {
120 	int s;
121 
122 	s = spl3();
123 	LIST_REMOVE(n, link);
124 	splx(s);
125 }
126 
127 /* Level 3 hardware interrupt */
128 void
vbl_handler(void)129 vbl_handler(void)
130 {
131 	struct vbl_node *n;
132 
133 	/* handle all vbl functions */
134 	LIST_FOREACH(n, &vbl_list, link) {
135 		if (n->flags & VBLNF_TURNOFF) {
136 			n->flags |= VBLNF_OFF;
137 			n->flags &= ~(VBLNF_TURNOFF);
138 		} else {
139 			if (n != NULL)
140 				n->function(n->data);
141 		}
142 	}
143 	custom.intreq = INTF_VERTB;
144 }
145 
146 void
cc_init_vbl(void)147 cc_init_vbl(void)
148 {
149 	LIST_INIT(&vbl_list);
150 	/*
151 	 * enable vertical blank interrupts
152 	 */
153 	custom.intena = INTF_SETCLR | INTF_VERTB;
154 }
155 
156 
157 /*
158  * Blitter stuff.
159  */
160 
161 void
cc_init_blitter(void)162 cc_init_blitter(void)
163 {
164 }
165 
166 /* test twice to cover blitter bugs if BLTDONE (BUSY) is set it is not done. */
167 int
is_blitter_busy(void)168 is_blitter_busy(void)
169 {
170 	u_short bb;
171 
172 	bb = (custom.dmaconr & DMAF_BLTDONE);
173 	if ((custom.dmaconr & DMAF_BLTDONE) || bb)
174 		return (1);
175 	return (0);
176 }
177 
178 void
wait_blit(void)179 wait_blit(void)
180 {
181 	/*
182 	 * V40 state this covers all blitter bugs.
183 	 */
184 	while (is_blitter_busy())
185 		;
186 }
187 
188 void
blitter_handler(void)189 blitter_handler(void)
190 {
191 	custom.intreq = INTF_BLIT;
192 }
193 
194 
195 void
do_blit(u_short size)196 do_blit(u_short size)
197 {
198 	custom.bltsize = size;
199 }
200 
201 void
set_blitter_control(u_short con0,u_short con1)202 set_blitter_control(u_short con0, u_short con1)
203 {
204 	custom.bltcon0 = con0;
205 	custom.bltcon1 = con1;
206 }
207 
208 void
set_blitter_mods(u_short a,u_short b,u_short c,u_short d)209 set_blitter_mods(u_short a, u_short b, u_short c, u_short d)
210 {
211 	custom.bltamod = a;
212 	custom.bltbmod = b;
213 	custom.bltcmod = c;
214 	custom.bltdmod = d;
215 }
216 
217 void
set_blitter_masks(u_short fm,u_short lm)218 set_blitter_masks(u_short fm, u_short lm)
219 {
220 	custom.bltafwm = fm;
221 	custom.bltalwm = lm;
222 }
223 
224 void
set_blitter_data(u_short da,u_short db,u_short dc)225 set_blitter_data(u_short da, u_short db, u_short dc)
226 {
227 	custom.bltadat = da;
228 	custom.bltbdat = db;
229 	custom.bltcdat = dc;
230 }
231 
232 void
set_blitter_pointers(void * a,void * b,void * c,void * d)233 set_blitter_pointers(void *a, void *b, void *c, void *d)
234 {
235 	custom.bltapt = a;
236 	custom.bltbpt = b;
237 	custom.bltcpt = c;
238 	custom.bltdpt = d;
239 }
240 
241 /*
242  * Copper Stuff.
243  */
244 
245 
246 /*
247  * Wait till end of frame. We should probably better use the
248  * sleep/wakeup system newly introduced in the vbl manager
249  */
250 void
wait_tof(void)251 wait_tof(void)
252 {
253 	/*
254 	 * wait until bottom of frame.
255 	 */
256 	while ((custom.vposr & 0x0007) == 0)
257 		;
258 
259 	/*
260 	 * wait until until top of frame.
261 	 */
262 	while (custom.vposr & 0x0007)
263 		;
264 
265 	if (custom.vposr & 0x8000)
266 		return;
267 	/*
268 	 * we are on short frame.
269 	 * wait for long frame bit set
270 	 */
271 	while ((custom.vposr & 0x8000) == 0)
272 		;
273 }
274 
275 cop_t *
find_copper_inst(cop_t * l,u_short inst)276 find_copper_inst(cop_t *l, u_short inst)
277 {
278 	cop_t *r = NULL;
279 	while ((l->cp.data & 0xff01ff01) != 0xff01ff00) {
280 		if (l->cp.inst.opcode == inst) {
281 			r = l;
282 			break;
283 		}
284 		l++;
285 	}
286 	return (r);
287 }
288 
289 void
install_copper_list(cop_t * l)290 install_copper_list(cop_t *l)
291 {
292 	wait_tof();
293 	wait_tof();
294 	custom.cop1lc = l;
295 }
296 
297 
298 void
cc_init_copper(void)299 cc_init_copper(void)
300 {
301 }
302 
303 /*
304  * level 3 interrupt
305  */
306 void
copper_handler(void)307 copper_handler(void)
308 {
309 	custom.intreq = INTF_COPER;
310 }
311 
312 /*
313  * Audio stuff.
314  */
315 
316 
317 /* - channel[4] */
318 /* the data for each audio channel and what to do with it. */
319 struct audio_channel channel[4];
320 
321 /* audio vbl node for vbl function  */
322 struct vbl_node audio_vbl_node;
323 
324 void
cc_init_audio(void)325 cc_init_audio(void)
326 {
327 	int i;
328 
329 	/*
330 	 * disable all audio interrupts
331 	 */
332 	custom.intena = INTF_AUD0|INTF_AUD1|INTF_AUD2|INTF_AUD3;
333 
334 	/*
335 	 * initialize audio channels to off.
336 	 */
337 	for (i = 0; i < 4; i++) {
338 		channel[i].play_count = 0;
339 		channel[i].isaudio = 0;
340 		channel[i].handler = NULL;
341 	}
342 }
343 
344 
345 /*
346  * Audio Interrupt Handler
347  */
348 void
audio_handler(void)349 audio_handler(void)
350 {
351 	u_short audio_dma, flag, ir;
352 	int i;
353 
354 	audio_dma = custom.dmaconr;
355 
356 	/*
357 	 * only check channels who have DMA enabled.
358 	 */
359 	audio_dma &= (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3);
360 
361 	/*
362 	 * disable all audio interrupts with DMA set
363 	 */
364 	custom.intena = (audio_dma << INTB_AUD0) & AUCC_ALLINTF;
365 
366 	/*
367 	 * if no audio DMA enabled then exit quick.
368 	 */
369 	if (!audio_dma) {
370 		/*
371 		 * clear all interrupts.
372 		 */
373 		custom.intreq = AUCC_ALLINTF;
374 		goto out;
375 	}
376 	for (i = 0; i < AUCC_MAXINT; i++) {
377 		flag = (1 << i);
378 		ir = custom.intreqr;
379 		/*
380 		 * is this channel's interrupt is set?
381 		 */
382 		if ((ir & (flag << INTB_AUD0)) == 0)
383 			continue;
384 #if NAUDIO>0
385 		custom.intreq=(flag<<INTB_AUD0);
386 		/* call audio handler with channel number */
387 		if (channel[i].isaudio==1)
388 			if (channel[i].handler)
389 				(*channel[i].handler)(i);
390 #endif
391 
392 		if (channel[i].play_count)
393 			channel[i].play_count--;
394 		else {
395 			/*
396 			 * disable DMA to this channel and
397 			 * disable interrupts to this channel
398 			 */
399 			custom.dmacon = flag;
400 			custom.intena = (flag << INTB_AUD0);
401 			if (channel[i].isaudio==-1)
402 				channel[i].isaudio=0;
403 		}
404 		/*
405 		 * clear this channels interrupt.
406 		 */
407 		custom.intreq = (flag << INTB_AUD0);
408 	}
409 
410 out:
411 	/*
412 	 * enable audio interrupts with DMA still set.
413 	 */
414 	audio_dma = custom.dmaconr;
415 	audio_dma &= (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3);
416 	custom.intena = INTF_SETCLR | (audio_dma << INTB_AUD0);
417 }
418 
419 void
play_sample(u_short len,u_short * data,u_short period,u_short volume,u_short channels,u_long count)420 play_sample(u_short len, u_short *data, u_short period, u_short volume, u_short channels, u_long count)
421 {
422 	u_short dmabits, ch;
423 	register int i;
424 
425 	dmabits = channels & 0xf;
426 
427 	/* check to see, whether all channels are free */
428 	for (i=0;i<4;i++) {
429 		if ((1<<i) & dmabits) {
430 			if (channel[i].isaudio)
431 				return; /* allocated */
432 			else
433 				channel[i].isaudio=-1; /* allocate */
434 		}
435 	}
436 
437 	custom.dmacon = dmabits;	/* turn off the correct channels */
438 
439 	/* load the channels */
440 	for (ch = 0; ch < 4; ch++) {
441 		if ((dmabits & (ch << ch)) == 0)
442 			continue;
443 		custom.aud[ch].len = len;
444 		custom.aud[ch].lc = data;
445 		custom.aud[ch].per = period;
446 		custom.aud[ch].vol = volume;
447 		channel[ch].play_count = count;
448 	}
449 	/*
450 	 * turn on interrupts and enable DMA for channels and
451 	 */
452 	custom.intena = INTF_SETCLR | (dmabits << INTB_AUD0);
453 	custom.dmacon = DMAF_SETCLR | DMAF_MASTER |dmabits;
454 }
455 
456 /*
457  * Chipmem allocator.
458  */
459 
460 static TAILQ_HEAD(chiplist, mem_node) chip_list;
461 static TAILQ_HEAD(freelist, mem_node) free_list;
462 static u_long   chip_total;		/* total free. */
463 static u_long   chip_size;		/* size of it all. */
464 
465 void
cc_init_chipmem(void)466 cc_init_chipmem(void)
467 {
468 	int s = splhigh ();
469 	struct mem_node *mem;
470 
471 	chip_size = chipmem_end - (chipmem_start + PAGE_SIZE);
472 	chip_total = chip_size - sizeof(*mem);
473 
474 	mem = (struct mem_node *)chipmem_steal(chip_size);
475 	mem->size = chip_total;
476 
477 	TAILQ_INIT(&chip_list);
478 	TAILQ_INIT(&free_list);
479 
480 	TAILQ_INSERT_HEAD(&chip_list, mem, link);
481 	TAILQ_INSERT_HEAD(&free_list, mem, free_link);
482 	splx(s);
483 }
484 
485 void *
alloc_chipmem(u_long size)486 alloc_chipmem(u_long size)
487 {
488 	int s;
489 	struct mem_node *mn, *new;
490 
491 	if (size == 0)
492 		return NULL;
493 
494 	s = splhigh();
495 
496 	if (size & ~(CM_BLOCKMASK))
497 		size = (size & CM_BLOCKMASK) + CM_BLOCKSIZE;
498 
499 	/*
500 	 * walk list of available nodes.
501 	 */
502 	TAILQ_FOREACH(mn, &free_list, free_link)
503 		if (size <= mn->size)
504 			break;
505 
506 	if (mn == NULL) {
507 		splx(s);
508 		return NULL;
509 	}
510 
511 	if ((mn->size - size) <= sizeof (*mn)) {
512 		/*
513 		 * our allocation would not leave room
514 		 * for a new node in between.
515 		 */
516 		TAILQ_REMOVE(&free_list, mn, free_link);
517 		mn->type = MNODE_USED;
518 		size = mn->size;	 /* increase size. (or same) */
519 		chip_total -= mn->size;
520 		splx(s);
521 		return ((void *)&mn[1]);
522 	}
523 
524 	/*
525 	 * split the node's memory.
526 	 */
527 	new = mn;
528 	new->size -= size + sizeof(struct mem_node);
529 	mn = (struct mem_node *)(MNODES_MEM(new) + new->size);
530 	mn->size = size;
531 
532 	/*
533 	 * add split node to node list
534 	 * and mark as not on free list
535 	 */
536 	TAILQ_INSERT_AFTER(&chip_list, new, mn, link);
537 	mn->type = MNODE_USED;
538 
539 	chip_total -= size + sizeof(struct mem_node);
540 	splx(s);
541 	return ((void *)&mn[1]);
542 }
543 
544 void
free_chipmem(void * mem)545 free_chipmem(void *mem)
546 {
547 	struct mem_node *mn, *next, *prev;
548 	int s;
549 
550 	if (mem == NULL)
551 		return;
552 
553 	s = splhigh();
554 	mn = (struct mem_node *)mem - 1;
555 	next = TAILQ_NEXT(mn, link);
556 	prev = TAILQ_PREV(mn, chiplist, link);
557 
558 	/*
559 	 * check ahead of us.
560 	 */
561 	if (next->type == MNODE_FREE) {
562 		/*
563 		 * if next is: a valid node and a free node. ==> merge
564 		 */
565 		TAILQ_INSERT_BEFORE(next, mn, free_link);
566 		mn->type = MNODE_FREE;
567 		TAILQ_REMOVE(&chip_list, next, link);
568 		TAILQ_REMOVE(&free_list, next, free_link);
569 		chip_total += mn->size + sizeof(struct mem_node);
570 		mn->size += next->size + sizeof(struct mem_node);
571 	}
572 	if (prev->type == MNODE_FREE) {
573 		/*
574 		 * if prev is: a valid node and a free node. ==> merge
575 		 */
576 		if (mn->type != MNODE_FREE)
577 			chip_total += mn->size + sizeof(struct mem_node);
578 		else {
579 			/* already on free list */
580 			TAILQ_REMOVE(&free_list, mn, free_link);
581 			mn->type = MNODE_USED;
582 			chip_total += sizeof(struct mem_node);
583 		}
584 		TAILQ_REMOVE(&chip_list, mn, link);
585 		prev->size += mn->size + sizeof(struct mem_node);
586 	} else if (mn->type != MNODE_FREE) {
587 		/*
588 		 * we still are not on free list and we need to be.
589 		 * <-- | -->
590 		 */
591 		while (next != NULL && prev != NULL) {
592 			if (next->type == MNODE_FREE) {
593 				TAILQ_INSERT_BEFORE(next, mn, free_link);
594 				mn->type = MNODE_FREE;
595 				break;
596 			}
597 			if (prev->type == MNODE_FREE) {
598 				TAILQ_INSERT_AFTER(&free_list, prev, mn,
599 				    free_link);
600 				mn->type = MNODE_FREE;
601 				break;
602 			}
603 			prev = TAILQ_PREV(prev, chiplist, link);
604 			next = TAILQ_NEXT(next, link);
605 		}
606 		if (mn->type != MNODE_FREE) {
607 			if (next == NULL) {
608 				/*
609 				 * we are not on list so we can add
610 				 * ourselves to the tail. (we walked to it.)
611 				 */
612 				TAILQ_INSERT_TAIL(&free_list,mn,free_link);
613 			} else {
614 				TAILQ_INSERT_HEAD(&free_list,mn,free_link);
615 			}
616 			mn->type = MNODE_FREE;
617 		}
618 		chip_total += mn->size;	/* add our helpings to the pool. */
619 	}
620 	splx(s);
621 }
622 
623 u_long
sizeof_chipmem(void * mem)624 sizeof_chipmem(void *mem)
625 {
626 	struct mem_node *mn;
627 
628 	if (mem == NULL)
629 		return (0);
630 	mn = mem;
631 	mn--;
632 	return (mn->size);
633 }
634 
635 u_long
avail_chipmem(int largest)636 avail_chipmem(int largest)
637 {
638 	struct mem_node *mn;
639 	u_long val;
640 	int s;
641 
642 	val = 0;
643 	if (largest == 0)
644 		val = chip_total;
645 	else {
646 		s = splhigh();
647 		TAILQ_FOREACH(mn, &free_list, free_link) {
648 			if (mn->size > val)
649 				val = mn->size;
650 		}
651 		splx(s);
652 	}
653 	return (val);
654 }
655