root/sys/arch/amiga/amiga/cc.c
/*      $NetBSD: cc.c,v 1.28 2021/08/12 20:13:54 andvar Exp $   */

/*
 * Copyright (c) 1994 Christian E. Hopps
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Christian E. Hopps.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: cc.c,v 1.28 2021/08/12 20:13:54 andvar Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/queue.h>

#include <amiga/amiga/custom.h>
#include <amiga/amiga/cc.h>
#include "audio.h"

vaddr_t CUSTOMADDR, CUSTOMbase;

#if defined (__GNUC__)
#define INLINE inline
#else
#define INLINE
#endif

/* init all the "custom chips" */
void
custom_chips_init(void)
{
        cc_init_chipmem();
        cc_init_vbl();
        cc_init_audio();
        cc_init_blitter();
        cc_init_copper();
}

/*
 * Vertical blank interrupt server chains.
 */
LIST_HEAD(vbllist, vbl_node) vbl_list;

void
turn_vbl_function_off(struct vbl_node *n)
{
        if (n->flags & VBLNF_OFF)
                return;

        n->flags |= VBLNF_TURNOFF;
        while ((n->flags & VBLNF_OFF) == 0)
                ;
}

/* allow function to be called on next vbl interrupt. */
void
turn_vbl_function_on(struct vbl_node *n)
{
        n->flags &= (short) ~(VBLNF_OFF);
}

void
add_vbl_function(struct vbl_node *add, short priority, void *data)
{
        int s;
        struct vbl_node *n, *prev;

        s = spl3();
        prev = NULL;
        LIST_FOREACH(n, &vbl_list, link) {
                if (add->priority > n->priority) {
                        /* insert add_node before. */
                        if (prev == NULL) {
                                LIST_INSERT_HEAD(&vbl_list, add, link);
                        } else {
                                LIST_INSERT_AFTER(prev, add, link);
                        }
                        add = NULL;
                        break;
                }
                prev = n;
        }
        if (add != NULL) {
                if (prev == NULL) {
                        LIST_INSERT_HEAD(&vbl_list, add, link);
                } else {
                        LIST_INSERT_AFTER(prev, add, link);
                }
        }
        splx(s);
}

void
remove_vbl_function(struct vbl_node *n)
{
        int s;

        s = spl3();
        LIST_REMOVE(n, link);
        splx(s);
}

/* Level 3 hardware interrupt */
void
vbl_handler(void)
{
        struct vbl_node *n;

        /* handle all vbl functions */
        LIST_FOREACH(n, &vbl_list, link) {
                if (n->flags & VBLNF_TURNOFF) {
                        n->flags |= VBLNF_OFF;
                        n->flags &= ~(VBLNF_TURNOFF);
                } else {
                        if (n != NULL)
                                n->function(n->data);
                }
        }
        custom.intreq = INTF_VERTB;
}

void
cc_init_vbl(void)
{
        LIST_INIT(&vbl_list);
        /*
         * enable vertical blank interrupts
         */
        custom.intena = INTF_SETCLR | INTF_VERTB;
}


/*
 * Blitter stuff.
 */

void
cc_init_blitter(void)
{
}

/* test twice to cover blitter bugs if BLTDONE (BUSY) is set it is not done. */
int
is_blitter_busy(void)
{
        u_short bb;

        bb = (custom.dmaconr & DMAF_BLTDONE);
        if ((custom.dmaconr & DMAF_BLTDONE) || bb)
                return (1);
        return (0);
}

void
wait_blit(void)
{
        /*
         * V40 state this covers all blitter bugs.
         */
        while (is_blitter_busy())
                ;
}

void
blitter_handler(void)
{
        custom.intreq = INTF_BLIT;
}


void
do_blit(u_short size)
{
        custom.bltsize = size;
}

void
set_blitter_control(u_short con0, u_short con1)
{
        custom.bltcon0 = con0;
        custom.bltcon1 = con1;
}

void
set_blitter_mods(u_short a, u_short b, u_short c, u_short d)
{
        custom.bltamod = a;
        custom.bltbmod = b;
        custom.bltcmod = c;
        custom.bltdmod = d;
}

void
set_blitter_masks(u_short fm, u_short lm)
{
        custom.bltafwm = fm;
        custom.bltalwm = lm;
}

void
set_blitter_data(u_short da, u_short db, u_short dc)
{
        custom.bltadat = da;
        custom.bltbdat = db;
        custom.bltcdat = dc;
}

void
set_blitter_pointers(void *a, void *b, void *c, void *d)
{
        custom.bltapt = a;
        custom.bltbpt = b;
        custom.bltcpt = c;
        custom.bltdpt = d;
}

/*
 * Copper Stuff.
 */


/*
 * Wait till end of frame. We should probably better use the
 * sleep/wakeup system newly introduced in the vbl manager
 */
void
wait_tof(void)
{
        /*
         * wait until bottom of frame.
         */
        while ((custom.vposr & 0x0007) == 0)
                ;

        /*
         * wait until until top of frame.
         */
        while (custom.vposr & 0x0007)
                ;

        if (custom.vposr & 0x8000)
                return;
        /*
         * we are on short frame.
         * wait for long frame bit set
         */
        while ((custom.vposr & 0x8000) == 0)
                ;
}

cop_t *
find_copper_inst(cop_t *l, u_short inst)
{
        cop_t *r = NULL;
        while ((l->cp.data & 0xff01ff01) != 0xff01ff00) {
                if (l->cp.inst.opcode == inst) {
                        r = l;
                        break;
                }
                l++;
        }
        return (r);
}

void
install_copper_list(cop_t *l)
{
        wait_tof();
        wait_tof();
        custom.cop1lc = l;
}


void
cc_init_copper(void)
{
}

/*
 * level 3 interrupt
 */
void
copper_handler(void)
{
        custom.intreq = INTF_COPER;
}

/*
 * Audio stuff.
 */


/* - channel[4] */
/* the data for each audio channel and what to do with it. */
struct audio_channel channel[4];

/* audio vbl node for vbl function  */
struct vbl_node audio_vbl_node;

void
cc_init_audio(void)
{
        int i;

        /*
         * disable all audio interrupts
         */
        custom.intena = INTF_AUD0|INTF_AUD1|INTF_AUD2|INTF_AUD3;

        /*
         * initialize audio channels to off.
         */
        for (i = 0; i < 4; i++) {
                channel[i].play_count = 0;
                channel[i].isaudio = 0;
                channel[i].handler = NULL;
        }
}


/*
 * Audio Interrupt Handler
 */
void
audio_handler(void)
{
        u_short audio_dma, flag, ir;
        int i;

        audio_dma = custom.dmaconr;

        /*
         * only check channels who have DMA enabled.
         */
        audio_dma &= (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3);

        /*
         * disable all audio interrupts with DMA set
         */
        custom.intena = (audio_dma << INTB_AUD0) & AUCC_ALLINTF;

        /*
         * if no audio DMA enabled then exit quick.
         */
        if (!audio_dma) {
                /*
                 * clear all interrupts.
                 */
                custom.intreq = AUCC_ALLINTF;
                goto out;
        }
        for (i = 0; i < AUCC_MAXINT; i++) {
                flag = (1 << i);
                ir = custom.intreqr;
                /*
                 * is this channel's interrupt is set?
                 */
                if ((ir & (flag << INTB_AUD0)) == 0)
                        continue;
#if NAUDIO>0
                custom.intreq=(flag<<INTB_AUD0);
                /* call audio handler with channel number */
                if (channel[i].isaudio==1)
                        if (channel[i].handler)
                                (*channel[i].handler)(i);
#endif

                if (channel[i].play_count)
                        channel[i].play_count--;
                else {
                        /*
                         * disable DMA to this channel and
                         * disable interrupts to this channel
                         */
                        custom.dmacon = flag;
                        custom.intena = (flag << INTB_AUD0);
                        if (channel[i].isaudio==-1)
                                channel[i].isaudio=0;
                }
                /*
                 * clear this channels interrupt.
                 */
                custom.intreq = (flag << INTB_AUD0);
        }

out:
        /*
         * enable audio interrupts with DMA still set.
         */
        audio_dma = custom.dmaconr;
        audio_dma &= (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3);
        custom.intena = INTF_SETCLR | (audio_dma << INTB_AUD0);
}

void
play_sample(u_short len, u_short *data, u_short period, u_short volume, u_short channels, u_long count)
{
        u_short dmabits, ch;
        register int i;

        dmabits = channels & 0xf;

        /* check to see, whether all channels are free */
        for (i=0;i<4;i++) {
                if ((1<<i) & dmabits) {
                        if (channel[i].isaudio)
                                return; /* allocated */
                        else
                                channel[i].isaudio=-1; /* allocate */
                }
        }

        custom.dmacon = dmabits;        /* turn off the correct channels */

        /* load the channels */
        for (ch = 0; ch < 4; ch++) {
                if ((dmabits & (ch << ch)) == 0)
                        continue;
                custom.aud[ch].len = len;
                custom.aud[ch].lc = data;
                custom.aud[ch].per = period;
                custom.aud[ch].vol = volume;
                channel[ch].play_count = count;
        }
        /*
         * turn on interrupts and enable DMA for channels and
         */
        custom.intena = INTF_SETCLR | (dmabits << INTB_AUD0);
        custom.dmacon = DMAF_SETCLR | DMAF_MASTER |dmabits;
}

/*
 * Chipmem allocator.
 */

static TAILQ_HEAD(chiplist, mem_node) chip_list;
static TAILQ_HEAD(freelist, mem_node) free_list;
static u_long   chip_total;             /* total free. */
static u_long   chip_size;              /* size of it all. */

void
cc_init_chipmem(void)
{
        int s = splhigh ();
        struct mem_node *mem;

        chip_size = chipmem_end - (chipmem_start + PAGE_SIZE);
        chip_total = chip_size - sizeof(*mem);

        mem = (struct mem_node *)chipmem_steal(chip_size);
        mem->size = chip_total;

        TAILQ_INIT(&chip_list);
        TAILQ_INIT(&free_list);

        TAILQ_INSERT_HEAD(&chip_list, mem, link);
        TAILQ_INSERT_HEAD(&free_list, mem, free_link);
        splx(s);
}

void *
alloc_chipmem(u_long size)
{
        int s;
        struct mem_node *mn, *new;

        if (size == 0)
                return NULL;

        s = splhigh();

        if (size & ~(CM_BLOCKMASK))
                size = (size & CM_BLOCKMASK) + CM_BLOCKSIZE;

        /*
         * walk list of available nodes.
         */
        TAILQ_FOREACH(mn, &free_list, free_link)
                if (size <= mn->size)
                        break;

        if (mn == NULL) {
                splx(s);
                return NULL;
        }

        if ((mn->size - size) <= sizeof (*mn)) {
                /*
                 * our allocation would not leave room
                 * for a new node in between.
                 */
                TAILQ_REMOVE(&free_list, mn, free_link);
                mn->type = MNODE_USED;
                size = mn->size;         /* increase size. (or same) */
                chip_total -= mn->size;
                splx(s);
                return ((void *)&mn[1]);
        }

        /*
         * split the node's memory.
         */
        new = mn;
        new->size -= size + sizeof(struct mem_node);
        mn = (struct mem_node *)(MNODES_MEM(new) + new->size);
        mn->size = size;

        /*
         * add split node to node list
         * and mark as not on free list
         */
        TAILQ_INSERT_AFTER(&chip_list, new, mn, link);
        mn->type = MNODE_USED;

        chip_total -= size + sizeof(struct mem_node);
        splx(s);
        return ((void *)&mn[1]);
}

void
free_chipmem(void *mem)
{
        struct mem_node *mn, *next, *prev;
        int s;

        if (mem == NULL)
                return;

        s = splhigh();
        mn = (struct mem_node *)mem - 1;
        next = TAILQ_NEXT(mn, link);
        prev = TAILQ_PREV(mn, chiplist, link);

        /*
         * check ahead of us.
         */
        if (next->type == MNODE_FREE) {
                /*
                 * if next is: a valid node and a free node. ==> merge
                 */
                TAILQ_INSERT_BEFORE(next, mn, free_link);
                mn->type = MNODE_FREE;
                TAILQ_REMOVE(&chip_list, next, link);
                TAILQ_REMOVE(&free_list, next, free_link);
                chip_total += mn->size + sizeof(struct mem_node);
                mn->size += next->size + sizeof(struct mem_node);
        }
        if (prev->type == MNODE_FREE) {
                /*
                 * if prev is: a valid node and a free node. ==> merge
                 */
                if (mn->type != MNODE_FREE)
                        chip_total += mn->size + sizeof(struct mem_node);
                else {
                        /* already on free list */
                        TAILQ_REMOVE(&free_list, mn, free_link);
                        mn->type = MNODE_USED;
                        chip_total += sizeof(struct mem_node);
                }
                TAILQ_REMOVE(&chip_list, mn, link);
                prev->size += mn->size + sizeof(struct mem_node);
        } else if (mn->type != MNODE_FREE) {
                /*
                 * we still are not on free list and we need to be.
                 * <-- | -->
                 */
                while (next != NULL && prev != NULL) {
                        if (next->type == MNODE_FREE) {
                                TAILQ_INSERT_BEFORE(next, mn, free_link);
                                mn->type = MNODE_FREE;
                                break;
                        }
                        if (prev->type == MNODE_FREE) {
                                TAILQ_INSERT_AFTER(&free_list, prev, mn,
                                    free_link);
                                mn->type = MNODE_FREE;
                                break;
                        }
                        prev = TAILQ_PREV(prev, chiplist, link);
                        next = TAILQ_NEXT(next, link);
                }
                if (mn->type != MNODE_FREE) {
                        if (next == NULL) {
                                /*
                                 * we are not on list so we can add
                                 * ourselves to the tail. (we walked to it.)
                                 */
                                TAILQ_INSERT_TAIL(&free_list,mn,free_link);
                        } else {
                                TAILQ_INSERT_HEAD(&free_list,mn,free_link);
                        }
                        mn->type = MNODE_FREE;
                }
                chip_total += mn->size; /* add our helpings to the pool. */
        }
        splx(s);
}

u_long
sizeof_chipmem(void *mem)
{
        struct mem_node *mn;

        if (mem == NULL)
                return (0);
        mn = mem;
        mn--;
        return (mn->size);
}

u_long
avail_chipmem(int largest)
{
        struct mem_node *mn;
        u_long val;
        int s;

        val = 0;
        if (largest == 0)
                val = chip_total;
        else {
                s = splhigh();
                TAILQ_FOREACH(mn, &free_list, free_link) {
                        if (mn->size > val)
                                val = mn->size;
                }
                splx(s);
        }
        return (val);
}