/* $NetBSD: btvmeii.c,v 1.28 2024/04/24 02:31:26 thorpej Exp $ */

/*
* Copyright (c) 1999
*      Matthias Drochner.  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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
*/

/*
* Driver for the Bit3/SBS PCI-VME adapter Model 2706.
* Uses the common Tundra Universe code.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: btvmeii.c,v 1.28 2024/04/24 02:31:26 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

#include <sys/bus.h>
#include <sys/kmem.h>
#include <sys/vmem.h>

#include <dev/pci/ppbreg.h>

#include <dev/vme/vmereg.h>
#include <dev/vme/vmevar.h>

#include <dev/pci/universe_pci_var.h>

static int b3_2706_match(device_t, cfdata_t, void *);
static void b3_2706_attach(device_t, device_t, void *);

/* exported via tag structs */
int b3_2706_map_vme(void *, vme_addr_t, vme_size_t,
                     vme_am_t, vme_datasize_t, vme_swap_t,
                     bus_space_tag_t *, bus_space_handle_t *, vme_mapresc_t*);
void b3_2706_unmap_vme(void *, vme_mapresc_t);

int b3_2706_vme_probe(void *, vme_addr_t, vme_size_t, vme_am_t,
                       vme_datasize_t,
                       int (*)(void *, bus_space_tag_t, bus_space_handle_t),
                       void *);

int b3_2706_map_vmeint(void *, int, int, vme_intr_handle_t *);
void *b3_2706_establish_vmeint(void *, vme_intr_handle_t, int,
                                int (*)(void *), void *);
void b3_2706_disestablish_vmeint(void *, void *);
void b3_2706_vmeint(void *, int, int);

int b3_2706_dmamap_create(void *, vme_size_t,
                           vme_am_t, vme_datasize_t, vme_swap_t,
                           int, vme_size_t, vme_addr_t,
                           int, bus_dmamap_t *);
void b3_2706_dmamap_destroy(void *, bus_dmamap_t);

int b3_2706_dmamem_alloc(void *, vme_size_t,
                             vme_am_t, vme_datasize_t, vme_swap_t,
                             bus_dma_segment_t *, int, int *, int);
void b3_2706_dmamem_free(void *, bus_dma_segment_t *, int);

struct b3_2706_vmemaprescs {
       int wnd;
       unsigned long pcibase, maplen;
       bus_space_handle_t handle;
       u_int32_t len;
};

struct b3_2706_vmeintrhand {
       TAILQ_ENTRY(b3_2706_vmeintrhand) ih_next;
       int (*ih_fun)(void*);
       void *ih_arg;
       int ih_level;
       int ih_vector;
       int ih_prior;
       u_long ih_count;
};

struct b3_2706_softc {
       struct univ_pci_data univdata;
       bus_space_tag_t swapt, vmet;
       bus_space_handle_t swaph;
       bus_addr_t vmepbase;

       int windowused[8];
       struct b3_2706_vmemaprescs vmemaprescs[8];
       vmem_t *vme_arena;

       struct vme_chipset_tag sc_vct;

       /* list of VME interrupt handlers */
       TAILQ_HEAD(, b3_2706_vmeintrhand) intrhdls;
       int strayintrs;
};

CFATTACH_DECL_NEW(btvmeii, sizeof(struct b3_2706_softc),
   b3_2706_match, b3_2706_attach, NULL, NULL);

/*
* The adapter consists of a DEC PCI-PCI-bridge with two
* PCI devices behind it: A Tundra Universe as device 4 and
* some FPGA with glue logics as device 8.
* As long as the autoconf code doesn't provide more support
* for dependent devices, we have to duplicate a part of the
* "ppb" functions here.
*/

static int
b3_2706_match(device_t parent, cfdata_t match, void *aux)
{
       struct pci_attach_args *pa = aux;
       pci_chipset_tag_t pc = pa->pa_pc;
       int secbus;
       pcitag_t tag;
       pcireg_t id;

       if ((PCI_VENDOR(pa->pa_id) != PCI_VENDOR_DEC)
           || (PCI_PRODUCT(pa->pa_id) != PCI_PRODUCT_DEC_21152))
               return (0);

       secbus = PCI_BRIDGE_BUS_NUM_SECONDARY(pci_conf_read(pc, pa->pa_tag,
               PCI_BRIDGE_BUS_REG));
       if (secbus == 0) {
               printf("b3_2706_match: ppb not configured\n");
               return (0);
       }

       tag = pci_make_tag(pc, secbus, 4, 0);
       id = pci_conf_read(pc, tag, PCI_ID_REG);

       if ((PCI_VENDOR(id) != PCI_VENDOR_NEWBRIDGE)
           || (PCI_PRODUCT(id) != PCI_PRODUCT_NEWBRIDGE_CA91CX42)) {
#ifdef DEBUG
               printf("b3_2706_match: no tundra\n");
#endif
               return (0);
       }

       tag = pci_make_tag(pc, secbus, 8, 0);
       id = pci_conf_read(pc, tag, PCI_ID_REG);

       if ((PCI_VENDOR(id) != PCI_VENDOR_BIT3)
           || (PCI_PRODUCT(id) != PCI_PRODUCT_BIT3_PCIVME2706)) {
#ifdef DEBUG
               printf("b3_2706_match: no bit3 chip\n");
#endif
               return (0);
       }

       return (5); /* beat "ppb" */
}

static void
b3_2706_attach(device_t parent, device_t self, void *aux)
{
       struct b3_2706_softc *sc = device_private(self);
       struct pci_attach_args *pa = aux;
       pci_chipset_tag_t pc = pa->pa_pc;
       struct pci_attach_args aa;
       int secbus;
       pcireg_t intr;
       pcitag_t tag;
       bus_addr_t swappbase;
       int i;

       struct vmebus_attach_args vaa;

       aprint_naive(": VME bus adapter\n");
       aprint_normal("\n");

       secbus = PCI_BRIDGE_BUS_NUM_SECONDARY(pci_conf_read(pc, pa->pa_tag,
               PCI_BRIDGE_BUS_REG));

       memcpy(&aa, pa, sizeof(struct pci_attach_args));
       aa.pa_device = 4;
       aa.pa_function = 0;
       aa.pa_tag = pci_make_tag(pc, secbus, 4, 0);
       aa.pa_intrswiz += 4;
       intr = pci_conf_read(pc, aa.pa_tag, PCI_INTERRUPT_REG);
       /*
        * swizzle it based on the number of
        * busses we're behind and our device
        * number.
        */
       aa.pa_intrpin = ((1 + aa.pa_intrswiz - 1) % 4) + 1;
       aa.pa_intrline = PCI_INTERRUPT_LINE(intr);

       if (univ_pci_attach(&sc->univdata, &aa, device_xname(self),
                           b3_2706_vmeint, sc)) {
               aprint_error_dev(self, "error initializing universe chip\n");
               return;
       }

       /*
        * don't waste KVM - the byteswap register is aliased in
        * a 512k window, we need it only once
        */
       tag = pci_make_tag(pc, secbus, 8, 0);
       sc->swapt = pa->pa_memt;
       if (pci_mapreg_info(pc, tag, 0x10,
                           PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_32BIT,
                           &swappbase, 0, 0) ||
           bus_space_map(sc->swapt, swappbase, 4, 0, &sc->swaph)) {
               aprint_error_dev(self, "can't map byteswap register\n");
               return;
       }
       /*
        * Set up cycle specific byteswap mode.
        * XXX Readback yields "all-ones" for me, and it doesn't seem
        * to matter what I write into the register - the data don't
        * get swapped. Adapter fault or documentation bug?
        */
       bus_space_write_4(sc->swapt, sc->swaph, 0, 0x00000490);

       /* VME space is mapped as needed */
       sc->vmet = pa->pa_memt;
       if (pci_mapreg_info(pc, tag, 0x14,
                           PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_32BIT,
                           &sc->vmepbase, 0, 0)) {
               aprint_error_dev(self, "VME range not assigned\n");
               return;
       }
#ifdef BIT3DEBUG
       aprint_debug_dev(self, "VME window @%lx\n",
           (long)sc->vmepbase);
#endif

       for (i = 0; i < 8; i++) {
               sc->windowused[i] = 0;
       }
       sc->vme_arena = vmem_create("pcivme",
                                   sc->vmepbase,       /* base */
                                   32*1024*1024,       /* size */
                                   1,                  /* quantum */
                                   NULL,               /* allocfn */
                                   NULL,               /* releasefn */
                                   NULL,               /* source */
                                   0,                  /* qcache_max */
                                   VM_SLEEP,
                                   IPL_NONE);

       sc->sc_vct.cookie = self;
       sc->sc_vct.vct_probe = b3_2706_vme_probe;
       sc->sc_vct.vct_map = b3_2706_map_vme;
       sc->sc_vct.vct_unmap = b3_2706_unmap_vme;
       sc->sc_vct.vct_int_map = b3_2706_map_vmeint;
       sc->sc_vct.vct_int_establish = b3_2706_establish_vmeint;
       sc->sc_vct.vct_int_disestablish = b3_2706_disestablish_vmeint;
       sc->sc_vct.vct_dmamap_create = b3_2706_dmamap_create;
       sc->sc_vct.vct_dmamap_destroy = b3_2706_dmamap_destroy;
       sc->sc_vct.vct_dmamem_alloc = b3_2706_dmamem_alloc;
       sc->sc_vct.vct_dmamem_free = b3_2706_dmamem_free;

       vaa.va_vct = &(sc->sc_vct);
       vaa.va_bdt = pa->pa_dmat; /* XXX */
       vaa.va_slaveconfig = 0; /* XXX CSR window? */

       config_found(self, &vaa, 0, CFARGS_NONE);
}

#define sc ((struct b3_2706_softc*)vsc)

int
b3_2706_map_vme(void *vsc, vme_addr_t vmeaddr, vme_size_t len, vme_am_t am, vme_datasize_t datasizes, vme_swap_t swap, bus_space_tag_t *tag, bus_space_handle_t *handle, vme_mapresc_t *resc)
{
       int idx, i, wnd, res;
       unsigned long boundary, maplen;
       vme_addr_t vmebase, vmeend;
       vmem_addr_t pcibase;
       static int windoworder[8] = {1, 2, 3, 5, 6, 7, 0, 4};

       /* prefer windows with fine granularity for small mappings */
       wnd = -1;
       if (len <= 32*1024)
               idx = 6;
       else
               idx = 0;
       for (i = 0; i < 8; i++) {
               if (!sc->windowused[windoworder[idx]]) {
                       wnd = windoworder[idx];
                       sc->windowused[wnd] = 1;
                       break;
               }
               idx = (idx + 1) % 8;
       }
       if (wnd == -1)
               return (ENOSPC);

       boundary = (wnd & 3) ? 64*1024 : 4*1024;

       /* first mapped address */
       vmebase = vmeaddr & ~(boundary - 1);
       /* base of last mapped page */
       vmeend = (vmeaddr + len - 1) & ~(boundary - 1);
       /* bytes in outgoing window required */
       maplen = vmeend - vmebase + boundary;

       if (vmem_xalloc(sc->vme_arena,
                       maplen,                 /* size */
                       boundary,               /* align */
                       0,                      /* phase */
                       0,                      /* boundary */
                       VMEM_ADDR_MIN,          /* minaddr */
                       VMEM_ADDR_MAX,          /* maxaddr */
                       VM_BESTFIT | VM_NOSLEEP,
                       &pcibase)) {
               sc->windowused[wnd] = 0;
               return (ENOMEM);
       }

       res = univ_pci_mapvme(&sc->univdata, wnd, vmebase, maplen,
                             am, datasizes, pcibase);
       if (res) {
               vmem_xfree(sc->vme_arena, pcibase, maplen);
               sc->windowused[wnd] = 0;
               return (res);
       }

       res = bus_space_map(sc->vmet, pcibase + (vmeaddr - vmebase), len,
                           0, handle);
       if (res) {
               univ_pci_unmapvme(&sc->univdata, wnd);
               vmem_xfree(sc->vme_arena, pcibase, maplen);
               sc->windowused[wnd] = 0;
               return (res);
       }

       *tag = sc->vmet;

       /*
        * save all data needed for later unmapping
        */
       sc->vmemaprescs[wnd].wnd = wnd;
       sc->vmemaprescs[wnd].pcibase = pcibase;
       sc->vmemaprescs[wnd].maplen = maplen;
       sc->vmemaprescs[wnd].handle = *handle;
       sc->vmemaprescs[wnd].len = len;
       *resc = &sc->vmemaprescs[wnd];
       return (0);
}

void
b3_2706_unmap_vme(void *vsc, vme_mapresc_t resc)
{
       struct b3_2706_vmemaprescs *r = resc;

       bus_space_unmap(sc->vmet, r->handle, r->len);
       vmem_xfree(sc->vme_arena, r->pcibase, r->maplen);

       if (!sc->windowused[r->wnd])
               panic("b3_2706_unmap_vme: bad window");
       univ_pci_unmapvme(&sc->univdata, r->wnd);
       sc->windowused[r->wnd] = 0;
}

int
b3_2706_vme_probe(void *vsc, vme_addr_t addr, vme_size_t len, vme_am_t am, vme_datasize_t datasize, int (*callback)(void *, bus_space_tag_t, bus_space_handle_t), void *cbarg)
{
       bus_space_tag_t tag;
       bus_space_handle_t handle;
       vme_mapresc_t resc;
       int res, i;
       volatile u_int32_t dummy;

       res = b3_2706_map_vme(vsc, addr, len, am, datasize, 0,
                             &tag, &handle, &resc);
       if (res)
               return (res);

       if (univ_pci_vmebuserr(&sc->univdata, 1))
               printf("b3_2706_vme_badaddr: TA bit not clean - reset\n");

       if (callback)
               res = (*callback)(cbarg, tag, handle);
       else {
               for (i = 0; i < len;) {
                       switch (datasize) {
                           case VME_D8:
                               dummy = bus_space_read_1(tag, handle, i);
                               (void)dummy;
                               i++;
                               break;
                           case VME_D16:
                               dummy = bus_space_read_2(tag, handle, i);
                               (void)dummy;
                               i += 2;
                               break;
                           case VME_D32:
                               dummy = bus_space_read_4(tag, handle, i);
                               (void)dummy;
                               i += 4;
                               break;
                           default:
                               panic("b3_2706_vme_probe: invalid datasize %x",
                                     datasize);
                       }
               }
       }

       if (univ_pci_vmebuserr(&sc->univdata, 0)) {
#ifdef BIT3DEBUG
               printf("b3_2706_vme_badaddr: caught TA\n");
#endif
               univ_pci_vmebuserr(&sc->univdata, 1);
               res = EIO;
       }

       b3_2706_unmap_vme(vsc, resc);
       return (res);
}

int
b3_2706_map_vmeint(void *vsc, int level, int vector, vme_intr_handle_t *handlep)
{

       *handlep = (void *)(long)((level << 8) | vector); /* XXX */
       return (0);
}

void *
b3_2706_establish_vmeint(void *vsc, vme_intr_handle_t handle, int prior, int (*func)(void *), void *arg)
{
       struct b3_2706_vmeintrhand *ih;
       long lv;
       int s;

       ih = kmem_alloc(sizeof *ih, KM_SLEEP);

       lv = (long)handle; /* XXX */

       ih->ih_fun = func;
       ih->ih_arg = arg;
       ih->ih_level = lv >> 8;
       ih->ih_vector = lv & 0xff;
       ih->ih_prior = prior;
       ih->ih_count = 0;

       s = splhigh();
       TAILQ_INSERT_TAIL(&(sc->intrhdls), ih, ih_next);
       splx(s);

       return (ih);
}

void
b3_2706_disestablish_vmeint(void *vsc, void *cookie)
{
       struct b3_2706_vmeintrhand *ih = cookie;
       int s;

       if (!ih) {
               printf("b3_2706_unmap_vmeint: NULL arg\n");
               return;
       }

       s = splhigh();
       TAILQ_REMOVE(&(sc->intrhdls), ih, ih_next);
       splx(s);

       kmem_free(ih, sizeof(*ih));
}

void
b3_2706_vmeint(void *vsc, int level, int vector)
{
       struct b3_2706_vmeintrhand *ih;
       int found;

#ifdef BIT3DEBUG
       printf("b3_2706_vmeint: VME IRQ %d, vec %x\n", level, vector);
#endif
       found = 0;

       for (ih = sc->intrhdls.tqh_first; ih;
            ih = ih->ih_next.tqe_next) {
               if ((ih->ih_level == level) &&
                   ((ih->ih_vector == -1) ||
                    (ih->ih_vector == vector))) {
                       int s, res;
                       /*
                        * We should raise the interrupt level
                        * to ih->ih_prior here. How to do this
                        * machine-independently?
                        * To be safe, raise to the maximum.
                        */
                       s = splhigh();
                       found |= (res = (*(ih->ih_fun))(ih->ih_arg));
                       splx(s);
                       if (res)
                               ih->ih_count++;
                       if (res == 1)
                               break;
               }
       }
       if (!found)
               sc->strayintrs++;
}

int
b3_2706_dmamap_create(void *vsc, vme_size_t len, vme_am_t am,
   vme_datasize_t datasize, vme_swap_t swap, int nsegs, vme_size_t segsz,
   vme_addr_t bound, int flags, bus_dmamap_t *mapp)
{
       return (EINVAL);
}

void
b3_2706_dmamap_destroy(void *vsc, bus_dmamap_t map)
{
}

int
b3_2706_dmamem_alloc(void *vsc, vme_size_t len, vme_am_t am, vme_datasize_t datasizes, vme_swap_t swap, bus_dma_segment_t *segs, int nsegs, int *rsegs, int flags)
{
       return (EINVAL);
}

void
b3_2706_dmamem_free(void *vsc, bus_dma_segment_t *segs, int nsegs)
{
}

#undef sc