root/sys/arch/mips/alchemy/dev/aupcmcia.c
/* $NetBSD: aupcmcia.c,v 1.13 2022/09/25 12:41:46 andvar Exp $ */

/*-
 * Copyright (c) 2006 Itronix Inc.
 * All rights reserved.
 *
 * Written by Garrett D'Amore for Itronix Inc.
 *
 * 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. The name of Itronix Inc. may not be used to endorse
 *    or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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 "opt_pci.h" */
/* #include "pci.h" */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: aupcmcia.c,v 1.13 2022/09/25 12:41:46 andvar Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/intr.h>
#include <sys/device.h>

#include <dev/pcmcia/pcmciareg.h>
#include <dev/pcmcia/pcmciavar.h>
#include <dev/pcmcia/pcmciachip.h>

#include <mips/alchemy/include/au_himem_space.h>
#include <mips/alchemy/include/aubusvar.h>
#include <mips/alchemy/include/aureg.h>
#include <mips/alchemy/include/auvar.h>

#include <mips/alchemy/dev/aupcmciareg.h>
#include <mips/alchemy/dev/aupcmciavar.h>

/*
 * Borrow PCMCIADEBUG for now.  Generally aupcmcia is the only PCMCIA
 * host on these machines anyway.
 */
#ifdef  PCMCIADEBUG
int     aupcm_debug = 1;
#define DPRINTF(arg)    if (aupcm_debug) printf arg
#else
#define DPRINTF(arg)
#endif  

/*
 * And for information about mappings, etc. use this one.
 */
#ifdef  AUPCMCIANOISY
#define NOISY(arg)      printf arg
#else
#define NOISY(arg)
#endif  

/*
 * Note, we use prefix "aupcm" instead of "aupcmcia", even though our
 * driver is the latter, mostly because my fingers have trouble typing
 * the former.  "aupcm" should be sufficiently unique to avoid
 * confusion.
 */

static int aupcm_mem_alloc(pcmcia_chipset_handle_t, bus_size_t,
    struct pcmcia_mem_handle *);
static void aupcm_mem_free(pcmcia_chipset_handle_t,
    struct pcmcia_mem_handle *);
static int aupcm_mem_map(pcmcia_chipset_handle_t, int, bus_addr_t,
    bus_size_t, struct pcmcia_mem_handle *, bus_size_t *, int *);
static void aupcm_mem_unmap(pcmcia_chipset_handle_t, int);

static int aupcm_io_alloc(pcmcia_chipset_handle_t, bus_addr_t, bus_size_t,
    bus_size_t, struct pcmcia_io_handle *);
static void aupcm_io_free(pcmcia_chipset_handle_t, struct pcmcia_io_handle *);
static int aupcm_io_map(pcmcia_chipset_handle_t, int, bus_addr_t,
    bus_size_t, struct pcmcia_io_handle *, int *);
static void aupcm_io_unmap(pcmcia_chipset_handle_t, int);
static void *aupcm_intr_establish(pcmcia_chipset_handle_t,
    struct pcmcia_function *, int, int (*)(void *), void *);
static void aupcm_intr_disestablish(pcmcia_chipset_handle_t, void *);

static void aupcm_slot_enable(pcmcia_chipset_handle_t);
static void aupcm_slot_disable(pcmcia_chipset_handle_t);
static void aupcm_slot_settype(pcmcia_chipset_handle_t, int);

static int aupcm_match(device_t, struct cfdata *, void *);
static void aupcm_attach(device_t, device_t, void *);

static void aupcm_event_thread(void *);
static int aupcm_card_intr(void *);
static void aupcm_softintr(void *);
static int aupcm_print(void *, const char *);

struct aupcm_slot {
        struct aupcm_softc      *as_softc;
        int                     as_slot;
        int                     as_status;
        int                     as_enabled;
        int                     (*as_intr)(void *);
        int                     as_card_irq;
        int                     as_status_irq;
        void                    *as_intrarg;
        void                    *as_softint;
        void                    *as_hardint;
        const char              *as_name;
        bus_addr_t              as_offset;
        struct mips_bus_space   as_iot;
        struct mips_bus_space   as_attrt;
        struct mips_bus_space   as_memt;
        void                    *as_wins[AUPCMCIA_NWINS];

        device_t                as_pcmcia;
};

/* this structure needs to be exposed... */
struct aupcm_softc {
        device_t                sc_dev;
        pcmcia_chipset_tag_t    sc_pct;

        void                    (*sc_slot_enable)(int);
        void                    (*sc_slot_disable)(int);
        int                     (*sc_slot_status)(int);

        paddr_t                 sc_base;

        int                     sc_wake;
        lwp_t                   *sc_thread;

        int                     sc_nslots;
        struct aupcm_slot       sc_slots[AUPCMCIA_NSLOTS];
};

static struct pcmcia_chip_functions aupcm_functions = {
        aupcm_mem_alloc,
        aupcm_mem_free,
        aupcm_mem_map,
        aupcm_mem_unmap,

        aupcm_io_alloc,
        aupcm_io_free,
        aupcm_io_map,
        aupcm_io_unmap,

        aupcm_intr_establish,
        aupcm_intr_disestablish,

        aupcm_slot_enable,
        aupcm_slot_disable,
        aupcm_slot_settype,
};

static  struct mips_bus_space   aupcm_memt;

CFATTACH_DECL_NEW(aupcmcia, sizeof (struct aupcm_softc),
    aupcm_match, aupcm_attach, NULL, NULL);

int
aupcm_match(device_t parent, struct cfdata *cf, void *aux)
{
        struct aubus_attach_args        *aa = aux;
        static int                      found = 0;

        if (found)
                return 0;

        if (strcmp(aa->aa_name, "aupcmcia") != 0)
                return 0;

        found = 1;

        return 1;
}

void
aupcm_attach(device_t parent, device_t self, void *aux)
{
        /* struct aubus_attach_args     *aa = aux; */
        struct aupcm_softc              *sc = device_private(self);
        static int                      done = 0;
        int                             slot;
        struct aupcmcia_machdep         *md;

        sc->sc_dev = self;

        /* initialize bus space */
        if (done) {
                /* there can be only one. */
                return;
        }

        done = 1;
        /*
         * PCMCIA memory can live within pretty much the entire 32-bit
         * space, modulo 64 MB wraps.  We don't have to support coexisting
         * DMA.
         */
        au_himem_space_init(&aupcm_memt, "pcmciamem",
            PCMCIA_BASE, AUPCMCIA_ATTR_OFFSET, 0xffffffff,
            AU_HIMEM_SPACE_LITTLE_ENDIAN);

        if ((md = aupcmcia_machdep()) == NULL) {
                aprint_error(": unable to get machdep structure\n");
                return;
        }

        sc->sc_nslots = md->am_nslots;
        sc->sc_slot_enable = md->am_slot_enable;
        sc->sc_slot_disable = md->am_slot_disable;
        sc->sc_slot_status = md->am_slot_status;

        aprint_normal(": Alchemy PCMCIA, %d slots\n", sc->sc_nslots);
        aprint_naive("\n");

        sc->sc_pct = (pcmcia_chipset_tag_t)&aupcm_functions;

        for (slot = 0; slot < sc->sc_nslots; slot++) {
                struct aupcm_slot               *sp;
                struct pcmciabus_attach_args    paa;

                sp = &sc->sc_slots[slot];
                sp->as_softc = sc;

                sp->as_slot = slot;
                sp->as_name = md->am_slot_name(slot);
                sp->as_offset = md->am_slot_offset(slot);
                sp->as_card_irq = md->am_slot_irq(slot, AUPCMCIA_IRQ_CARD);
                sp->as_status_irq = md->am_slot_irq(slot,
                    AUPCMCIA_IRQ_INSERT);

                au_himem_space_init(&sp->as_attrt, "pcmciaattr",
                    PCMCIA_BASE + sp->as_offset + AUPCMCIA_ATTR_OFFSET,
                    0, AUPCMCIA_MAP_SIZE, AU_HIMEM_SPACE_LITTLE_ENDIAN);

                au_himem_space_init(&sp->as_memt, "pcmciamem",
                    PCMCIA_BASE + sp->as_offset + AUPCMCIA_MEM_OFFSET,
                    0, AUPCMCIA_MAP_SIZE, AU_HIMEM_SPACE_LITTLE_ENDIAN);

                au_himem_space_init(&sp->as_iot, "pcmciaio",
                    PCMCIA_BASE + sp->as_offset + AUPCMCIA_IO_OFFSET,
                    0, AUPCMCIA_MAP_SIZE,
                    AU_HIMEM_SPACE_LITTLE_ENDIAN | AU_HIMEM_SPACE_IO);

                sp->as_status = 0;
                
                paa.paa_busname = "pcmcia";
                paa.pct = sc->sc_pct;
                paa.pch = (pcmcia_chipset_handle_t)sp;

                sp->as_pcmcia = config_found(self, &paa, aupcm_print,
                    CFARGS_NONE);

                /* if no pcmcia, make sure slot is powered down */
                if (sp->as_pcmcia == NULL) {
                        aupcm_slot_disable(sp);
                        continue;
                }

                /* this makes sure we probe the slot */
                sc->sc_wake |= (1 << slot);
        }

        /*
         * XXX: this would be an excellent time time to establish a handler
         * for the card insertion interrupt, but that's edge triggered, and
         * au_icu.c won't support it right now.  We poll in the event thread
         * for now.  Start by initializing it now.
         */
        if (kthread_create(PRI_NONE, 0, NULL, aupcm_event_thread, sc,
            &sc->sc_thread, "%s", device_xname(sc->sc_dev)) != 0)
                panic("%s: unable to create event kthread",
                    device_xname(sc->sc_dev));
}

int
aupcm_print(void *aux, const char *pnp)
{
        struct pcmciabus_attach_args *paa = aux;
        struct aupcm_slot *sp = paa->pch;

        printf(" socket %d irq %d, %s", sp->as_slot, sp->as_card_irq,
            sp->as_name);

        return (UNCONF);
}

void *
aupcm_intr_establish(pcmcia_chipset_handle_t pch,
    struct pcmcia_function *pf, int level, int (*handler)(void *), void *arg)
{
        struct aupcm_slot       *sp = (struct aupcm_slot *)pch;
        int                     s;

        /*
         * Hmm. perhaps this intr should be a list. well, PCMCIA
         * devices generally only have one interrupt, and so should
         * generally have only one handler.  So we leave it for now.
         * (Other PCMCIA bus drivers do it this way.)
         */
        sp->as_intr = handler;
        sp->as_intrarg = arg;
        sp->as_softint = softint_establish(IPL_SOFTNET, aupcm_softintr, sp);

        /* set up hard interrupt handler for the card IRQs */
        s = splhigh();
        sp->as_hardint = au_intr_establish(sp->as_card_irq, 0,
            IPL_TTY, IST_LEVEL_LOW, aupcm_card_intr, sp);
        /* if card is not powered up, then leave the IRQ masked */
        if (!sp->as_enabled) {
                au_intr_disable(sp->as_card_irq);
        }
        splx(s);

        return (sp->as_softint);
}

void
aupcm_intr_disestablish(pcmcia_chipset_handle_t pch, void *ih)
{
        struct aupcm_slot *sp = (struct aupcm_slot *)pch;

        KASSERT(sp->as_softint == ih);
        /* KASSERT(sp->as_hardint); */
        /* set up hard interrupt handler for the card IRQs */

        au_intr_disestablish(sp->as_hardint);
        sp->as_hardint = 0;

        softint_disestablish(ih);
        sp->as_softint = 0;
        sp->as_intr = NULL;
        sp->as_intrarg = NULL;
}

/*
 * FYI: Hot detach of PCMCIA is supposedly safe because H/W doesn't
 * fault on accesses to missing hardware.
 */
void
aupcm_event_thread(void *arg)
{
        struct aupcm_softc      *sc = arg;
        struct aupcm_slot       *sp;
        int                     s, i, attach, detach;

        for (;;) {
                s = splhigh();
                if (sc->sc_wake == 0) {
                        splx(s);
                        /*
                         * XXX: Currently, the au_icu.c lacks support
                         * for edge-triggered interrupts.  So we
                         * cannot really use the status change
                         * interrupts.  For now we poll (once per sec).
                         * FYI, Linux does it this way, and they *do*
                         * have support for edge triggered interrupts.
                         * Go figure.
                         */
                        tsleep(&sc->sc_wake, PWAIT, "aupcm_event", hz);
                        s = splhigh();
                }
                sc->sc_wake = 0;

                attach = detach = 0;
                for (i = 0; i < sc->sc_nslots; i++) {
                        sp = &sc->sc_slots[i];

                        if (sc->sc_slot_status(sp->as_slot) != 0) {
                                if (!sp->as_status) {
                                        DPRINTF(("%s: card %d insertion\n",
                                            device_xname(sc->sc_dev), i));
                                        attach |= (1 << i);
                                        sp->as_status = 1;
                                }
                        } else {
                                if (sp->as_status) {
                                        DPRINTF(("%s: card %d removal\n",
                                            device_xname(sc->sc_dev), i));
                                        detach |= (1 << i);
                                        sp->as_status = 0;
                                }
                        }
                }
                splx(s);

                for (i = 0; i < sc->sc_nslots; i++) {
                        sp = &sc->sc_slots[i];

                        if (detach & (1 << i)) {
                                aupcm_slot_disable(sp);
                                pcmcia_card_detach(sp->as_pcmcia, DETACH_FORCE);
                        } else if (attach & (1 << i)) {
                                /*
                                 * until the function is enabled, don't
                                 * honor interrupts
                                 */
                                sp->as_enabled = 0;
                                au_intr_disable(sp->as_card_irq);
                                pcmcia_card_attach(sp->as_pcmcia);
                        }
                }
        }
}

#if 0
void
aupcm_status_intr(void *arg)
{
        int s;
        struct aupcm_softc *sc = arg;

        s = splhigh();

        /* kick the status thread so it does its bit */
        sc->sc_wake = 1;
        wakeup(&sc->sc_wake);

        splx(s);
}
#endif

int
aupcm_card_intr(void *arg)
{
        struct aupcm_slot *sp = arg;

        /* disable the hard interrupt for now */
        au_intr_disable(sp->as_card_irq);

        if (sp->as_intr != NULL) {
                softint_schedule(sp->as_softint);
        }

        return 1;
}

void
aupcm_softintr(void *arg)
{
        struct aupcm_slot       *sp = arg;
        int                     s;

        sp->as_intr(sp->as_intrarg);

        s = splhigh();

        if (sp->as_intr && sp->as_enabled) {
                au_intr_enable(sp->as_card_irq);
        }

        splx(s);
}

void
aupcm_slot_enable(pcmcia_chipset_handle_t pch)
{
        struct aupcm_slot       *sp = (struct aupcm_slot *)pch;
        int                     s;

        /* no interrupts while we reset the card, please */
        if (sp->as_intr)
                au_intr_disable(sp->as_card_irq);

        /*
         * XXX: should probably lock to make sure slot_disable and
         * enable not called together.  However, i believe that the
         * event thread basically serializes them anyway.
         */

        sp->as_softc->sc_slot_enable(sp->as_slot);
        /* card is powered up now, honor device interrupts */

        s = splhigh();
        sp->as_enabled = 1;
        if (sp->as_intr)
                au_intr_enable(sp->as_card_irq);
        splx(s);
}

void
aupcm_slot_disable(pcmcia_chipset_handle_t pch)
{
        struct aupcm_slot       *sp = (struct aupcm_slot *)pch;
        int                     s;

        s = splhigh();
        au_intr_disable(sp->as_card_irq);
        sp->as_enabled = 0;
        splx(s);

        sp->as_softc->sc_slot_disable(sp->as_slot);
}

void
aupcm_slot_settype(pcmcia_chipset_handle_t pch, int type)
{
        /* we do nothing now : type == PCMCIA_IFTYPE_IO */
}

int
aupcm_mem_alloc(pcmcia_chipset_handle_t pch, bus_size_t size,
    struct pcmcia_mem_handle *pcmh)
{
        pcmh->memt = NULL;
        pcmh->size = pcmh->realsize = size;
        pcmh->addr = 0;
        pcmh->mhandle = 0;

        return 0;
}

void
aupcm_mem_free(pcmcia_chipset_handle_t pch, struct pcmcia_mem_handle *pcmh)
{
        /* nothing to do */
}

int
aupcm_mem_map(pcmcia_chipset_handle_t pch, int kind, bus_addr_t addr,
    bus_size_t size, struct pcmcia_mem_handle *pcmh, bus_size_t *offsetp,
    int *windowp)
{
        struct aupcm_slot       *sp = (struct aupcm_slot *)pch;
        int                     win, err;
        int                     s;

        s = splhigh();
        for (win = 0; win < AUPCMCIA_NWINS; win++) {
                if (sp->as_wins[win] == NULL) {
                        sp->as_wins[win] = pcmh;
                        break;
                }
        }
        splx(s);

        if (win >= AUPCMCIA_NWINS) {
                return ENOMEM;
        }

        if (kind & PCMCIA_MEM_ATTR) {
                pcmh->memt = &sp->as_attrt;
                NOISY(("mapping ATTR addr %x size %x\n", (uint32_t)addr,
                    (uint32_t)size));
        } else {
                pcmh->memt = &sp->as_memt;
                NOISY(("mapping MEMORY addr %x size %x\n", (uint32_t)addr,
                          (uint32_t)size));
        }

        if ((size + addr) > (64 * 1024 * 1024))
                return EINVAL;

        pcmh->size = size;

        err = bus_space_map(pcmh->memt, addr, size, 0, &pcmh->memh);
        if (err != 0) {
                sp->as_wins[win] = NULL;
                return err;
        }
        *offsetp = 0;
        *windowp = win;

        return 0;
}

void
aupcm_mem_unmap(pcmcia_chipset_handle_t pch, int win)
{
        struct aupcm_slot               *sp = (struct aupcm_slot *)pch;
        struct pcmcia_mem_handle        *pcmh;

        pcmh = (struct pcmcia_mem_handle *)sp->as_wins[win];
        sp->as_wins[win] = NULL;

        NOISY(("memory umap virtual %x\n", (uint32_t)pcmh->memh));
        bus_space_unmap(pcmh->memt, pcmh->memh, pcmh->size);
        pcmh->memt = NULL;
}

int
aupcm_io_alloc(pcmcia_chipset_handle_t pch, bus_addr_t start,
    bus_size_t size, bus_size_t align, struct pcmcia_io_handle *pih)
{
        struct aupcm_slot       *sp = (struct aupcm_slot *)pch;
        bus_space_handle_t      bush;
        int                     err;

        pih->iot = &sp->as_iot;
        pih->size = size;
        pih->flags = 0;
        
        /*
         * start from the initial offset - this gets us a slot
         * specific address, while still leaving the addresses more or
         * less zero-based which is required for x86-style device
         * drivers.
         */
        err = bus_space_alloc(pih->iot, start, 0x100000,
            size, align, 0, 0, &pih->addr, &bush);
        NOISY(("start = %x, addr = %x, size = %x, bush = %x\n",
                  (uint32_t)start, (uint32_t)pih->addr, (uint32_t)size,
                  (uint32_t)bush));

        /* and we convert it back */
        if (err == 0) {
                pih->ihandle = (void *)bush;
        }

        return (err);
}

void
aupcm_io_free(pcmcia_chipset_handle_t pch, struct pcmcia_io_handle *pih)
{
        bus_space_free(pih->iot, (bus_space_handle_t)pih->ihandle,
            pih->size);
}

int
aupcm_io_map(pcmcia_chipset_handle_t pch, int width, bus_addr_t offset,
    bus_size_t size, struct pcmcia_io_handle *pih, int *windowp)
{
        int                     err;

        err = bus_space_subregion(pih->iot, (bus_space_handle_t)pih->ihandle,
            offset, size, &pih->ioh);
        NOISY(("io map offset = %x, size = %x, ih = %x, hdl=%x\n",
                  (uint32_t)offset, (uint32_t)size,
                  (uint32_t)pih->ihandle, (uint32_t)pih->ioh));

        return err;
}

void
aupcm_io_unmap(pcmcia_chipset_handle_t pch, int win)
{
        /* We mustn't unmap/free subregion bus space! */
        NOISY(("io unmap\n"));
}