root/sys/dev/isa/if_ef.c
/*      $NetBSD: if_ef.c,v 1.36 2022/07/12 02:03:57 thorpej Exp $       */

/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Rafal K. Boni.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_ef.c,v 1.36 2022/07/12 02:03:57 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/protosw.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/if_ether.h>

#include <sys/cpu.h>
#include <sys/bus.h>
#include <sys/intr.h>

#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>

#include <dev/ic/i82586reg.h>
#include <dev/ic/i82586var.h>
#include <dev/isa/if_efreg.h>
#include <dev/isa/elink.h>

#ifdef EF_DEBUG
#define DPRINTF(x)      printf x
#else
#define DPRINTF(x)
#endif

struct ef_softc {
        struct ie_softc sc_ie;

        bus_space_tag_t sc_regt;        /* space tag for registers */
        bus_space_handle_t sc_regh;     /* space handle for registers */

        void* sc_ih;                    /* interrupt handle */

        uint8_t card_rev;               /* hardware revision */
        uint8_t card_type;              /* card model -- AUI/BNC or TP */
};

static int ef_media[] = {
        IFM_ETHER | IFM_10_5,
        IFM_ETHER | IFM_10_2,
};
#define NEF_MEDIA       __arraycount(ef_media)

static int eftp_media[] = {
        IFM_ETHER | IFM_10_T,
};
#define NEFTP_MEDIA     __arraycount(eftp_media)

/* Routines required by the MI i82586 driver API */
static void     ef_reset(struct ie_softc *, int);
static void     ef_hwinit(struct ie_softc *);
static void     ef_atten(struct ie_softc *, int);
static int      ef_intrhook(struct ie_softc *, int);

static void     ef_copyin(struct ie_softc *, void *, int, size_t);
static void     ef_copyout(struct ie_softc *, const void *, int, size_t);

static uint16_t ef_read_16(struct ie_softc *, int);
static void     ef_write_16(struct ie_softc *, int, uint16_t);
static void     ef_write_24(struct ie_softc *, int, int);

static void     ef_mediastatus(struct ie_softc *, struct ifmediareq *);

/* Local routines */
static int      ef_port_check(bus_space_tag_t, bus_space_handle_t);

static int      ef_match(device_t, cfdata_t, void *);
static void     ef_attach(device_t, device_t, void *);

/*
 * This keeps track of which ISAs have been through an ie probe sequence.
 * A simple static variable isn't enough, since it's conceivable that
 * a system might have more than one ISA bus.
 *
 * The "isa_bus" member is a pointer to the parent ISA bus device struct
 * which will unique per ISA bus.
 */

#define MAXCARDS_PER_ISABUS     8       /* If you have more than 8, you lose */

struct ef_isabus {
        LIST_ENTRY(ef_isabus) isa_link;
        device_t isa_bus;

        int bus_state;

        struct card {
                bus_addr_t iobase;
                bus_addr_t maddr;
                bus_size_t msize;
                int irq;
                int available;
        } isa_cards[MAXCARDS_PER_ISABUS];
};

static LIST_HEAD(, ef_isabus) ef_isa_buses;
static int ef_isa_buses_inited;

static void
ef_card_add(struct ef_isabus *bus, bus_addr_t iobase, bus_addr_t maddr,
    bus_size_t msiz, int irq)
{
        int idx;

        DPRINTF(("Adding 3c507 at 0x%x, IRQ %d, Mem 0x%lx/%zu\n",
                 (u_int) iobase, irq, (u_long) maddr, msiz));

        for (idx = 0; idx < MAXCARDS_PER_ISABUS; idx++) {
                if (bus->isa_cards[idx].available == 0) {
                        bus->isa_cards[idx].iobase = iobase;
                        bus->isa_cards[idx].maddr = maddr;
                        bus->isa_cards[idx].msize = msiz;
                        bus->isa_cards[idx].irq = irq;
                        bus->isa_cards[idx].available = 1;
                        break;
                }
        }
}

/*
 * 3C507 support routines
 */
static void
ef_reset(struct ie_softc *sc, int why)
{
        struct ef_softc *esc = (struct ef_softc *)sc;

        switch (why) {
        case CHIP_PROBE:
                /* Reset to chip to see if it responds */
                bus_space_write_1(esc->sc_regt, esc->sc_regh,
                                  EF_CTRL, EF_CTRL_RESET);
                DELAY(100);
                bus_space_write_1(esc->sc_regt, esc->sc_regh,
                                  EF_CTRL, EF_CTRL_NORMAL);
                DELAY(100);
                break;

        case CARD_RESET:
                /*
                 * This takes around 10sec, and we can get
                 * by quite well w/out it...
                 */
                break;
        }
}

static void
ef_atten(struct ie_softc *sc, int why)
{
        struct ef_softc *esc = (struct ef_softc *)sc;

        bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_ATTN, 1);
}

static void
ef_hwinit(struct ie_softc *sc)
{
        struct ef_softc *esc = (struct ef_softc *)sc;

        bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_ICTRL, 1);
}

static int
ef_intrhook(struct ie_softc *sc, int where)
{
        unsigned char cr;
        struct ef_softc *esc = (struct ef_softc *)sc;

        switch (where) {
        case INTR_ENTER:
                /* Entering ISR: disable, ack card interrupts */
                cr = bus_space_read_1(esc->sc_regt, esc->sc_regh, EF_CTRL);
                bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_CTRL,
                    cr & ~EF_CTRL_IEN);
                bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_ICTRL, 1);
                break;

        case INTR_EXIT:
                /* Exiting ISR: re-enable card interrupts */
                cr = bus_space_read_1(esc->sc_regt, esc->sc_regh, EF_CTRL);
                bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_CTRL,
                    cr | EF_CTRL_IEN);
                break;

        case INTR_LOOP:
                /* Looping in ISR: ack new interrupts */
                bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_ICTRL, 1);
                break;
    }

    return 1;
}

static uint16_t
ef_read_16(struct ie_softc *sc, int offset)
{

        return bus_space_read_2(sc->bt, sc->bh, offset);
}

static void
ef_copyin(struct ie_softc *sc, void *dst, int offset, size_t size)
{
        int dribble;
        uint8_t *bptr = dst;

        if (offset % 2) {
                *bptr = bus_space_read_1(sc->bt, sc->bh, offset);
                offset++; bptr++; size--;
        }

        dribble = size % 2;
        bus_space_read_region_2(sc->bt, sc->bh, offset, (uint16_t *) bptr,
            size >> 1);

        if (dribble) {
                bptr += size - 1;
                offset += size - 1;
                *bptr = bus_space_read_1(sc->bt, sc->bh, offset);
        }
}

static void
ef_copyout(struct ie_softc *sc, const void *src, int offset, size_t size)
{
        int dribble;
        const uint8_t *bptr = src;

        if (offset % 2) {
                bus_space_write_1(sc->bt, sc->bh, offset, *bptr);
                offset++; bptr++; size--;
        }

        dribble = size % 2;
        bus_space_write_region_2(sc->bt, sc->bh, offset,
            (const uint16_t *)bptr, size >> 1);
        if (dribble) {
                bptr += size - 1;
                offset += size - 1;
                bus_space_write_1(sc->bt, sc->bh, offset, *bptr);
        }
}

static void
ef_write_16(struct ie_softc *sc, int offset, uint16_t value)
{

        bus_space_write_2(sc->bt, sc->bh, offset, value);
}

static void
ef_write_24(struct ie_softc *sc, int offset, int addr)
{

        bus_space_write_4(sc->bt, sc->bh, offset,
            addr + (u_long)sc->sc_maddr - (u_long)sc->sc_iobase);
}

static void
ef_mediastatus(struct ie_softc *sc, struct ifmediareq *ifmr)
{
        struct ifmedia *ifm = &sc->sc_media;

        /* The currently selected media is always the active media. */
        ifmr->ifm_active = ifm->ifm_cur->ifm_media;
}

static int
ef_match(device_t parent, cfdata_t cf, void *aux)
{
        struct isa_attach_args * const ia = aux;
        int idx;
        struct ef_isabus *bus;
        bus_space_handle_t ioh;
        bus_space_tag_t iot = ia->ia_iot;

        if (ISA_DIRECT_CONFIG(ia))
                return 0;

        if (ef_isa_buses_inited == 0) {
                LIST_INIT(&ef_isa_buses);
                ef_isa_buses_inited = 1;
        }

        /* Probe this bus if we haven't done so already. */
        for (bus = ef_isa_buses.lh_first; bus != NULL;
             bus = bus->isa_link.le_next) {
                if (bus->isa_bus == parent)
                        break;
        }

        if (bus == NULL) {
                bus_addr_t iobase;

                /* Mark this bus so we don't probe it again. */
                bus = malloc(sizeof(struct ef_isabus), M_DEVBUF, M_WAITOK);
                bus->bus_state = 0;             /* Nothing done yet */
                bus->isa_bus = parent;

                LIST_INSERT_HEAD(&ef_isa_buses, bus, isa_link);

                if (bus_space_map(iot, ELINK_ID_PORT, 1, 0, &ioh)) {
                        DPRINTF(("3c507 probe: can't map Etherlink ID port\n"));
                        return 0;
                }

                /*
                 * Reset and put card in CONFIG state without
                 * changing address.
                 */
                elink_reset(iot, ioh, device_unit(parent));
                elink_idseq(iot, ioh, ELINK_507_POLY);
                elink_idseq(iot, ioh, ELINK_507_POLY);
                bus_space_write_1(iot, ioh, 0, 0xff);

                /* Unmap the ID port */
                bus_space_unmap(iot, ioh, 1);

                bus->bus_state++;       /* Cards now in CONFIG state */

                for (iobase = EF_IOBASE_LOW; iobase <= EF_IOBASE_HIGH;
                     iobase += EF_IOSIZE) {
                        /* Map the 507's port-space for the probe sequence. */
                        if (bus_space_map(iot, iobase, EF_IOSIZE,
                                          0, &ioh) != 0)
                                continue;

                        /* Now look for the 3Com magic bytes */

                        if (ef_port_check(iot, ioh)) {
                                int irq;
                                uint8_t v;
                                bus_addr_t maddr;
                                bus_addr_t msiz1;
                                bus_space_handle_t memh;

                                irq = bus_space_read_1(iot, ioh, EF_IRQ) &
                                        EF_IRQ_MASK;

                                v = bus_space_read_1(iot, ioh, EF_MADDR);
                                maddr = EF_MADDR_BASE +
                                      ((v & EF_MADDR_MASK) << EF_MADDR_SHIFT);
                                msiz1 = ((v & EF_MSIZE_MASK) + 1) *
                                        EF_MSIZE_STEP;

                                if (bus_space_map(ia->ia_memt, maddr,
                                                  msiz1, 0, &memh) == 0) {
                                            ef_card_add(bus, iobase, maddr,
                                                        msiz1, irq);
                                            bus_space_unmap(ia->ia_memt,
                                                            memh, msiz1);
                                }
                        }
                        bus_space_unmap(iot, ioh, EF_IOSIZE);
                }
        }

        if (ia->ia_nio < 1)
                return 0;
        if (ia->ia_niomem < 1)
                return 0;
        if (ia->ia_nirq < 1)
                return 0;

        for (idx = 0; idx < MAXCARDS_PER_ISABUS; idx++) {
                if (bus->isa_cards[idx].available != 1)
                        continue;

                if (ia->ia_io[0].ir_addr != ISA_UNKNOWN_PORT &&
                    ia->ia_io[0].ir_addr != bus->isa_cards[idx].iobase)
                        continue;

                if (ia->ia_iomem[0].ir_addr != ISA_UNKNOWN_IOMEM &&
                    ia->ia_iomem[0].ir_addr != bus->isa_cards[idx].maddr)
                        continue;

                if (ia->ia_irq[0].ir_irq != ISA_UNKNOWN_IRQ &&
                    ia->ia_irq[0].ir_irq != bus->isa_cards[idx].irq)
                        continue;

                break;
        }

        if (idx == MAXCARDS_PER_ISABUS)
                return 0;

        bus->isa_cards[idx].available++;

        ia->ia_nio = 1;
        ia->ia_io[0].ir_addr = bus->isa_cards[idx].iobase;
        ia->ia_io[0].ir_size = EF_IOSIZE;

        ia->ia_niomem = 1;
        ia->ia_iomem[0].ir_addr = bus->isa_cards[idx].maddr;
        ia->ia_iomem[0].ir_size = bus->isa_cards[idx].msize;

        ia->ia_nirq = 1;
        ia->ia_irq[0].ir_irq = bus->isa_cards[idx].irq;

        ia->ia_ndrq = 0;

        return 1;
}

static void
ef_attach(device_t parent, device_t self, void *aux)
{
        struct ef_softc *esc = device_private(self);
        struct ie_softc *sc = &esc->sc_ie;
        struct isa_attach_args *ia = aux;
        bus_space_tag_t iot = ia->ia_iot;

        int i;
        char vers[20];
        struct ef_isabus *bus;
        uint8_t partno[EF_TYPE_LEN];
        bus_space_handle_t ioh, memh;
        uint8_t ethaddr[ETHER_ADDR_LEN];

        sc->sc_dev = self;
        sc->hwinit = ef_hwinit;
        sc->hwreset = ef_reset;
        sc->chan_attn = ef_atten;
        sc->intrhook = ef_intrhook;

        sc->ie_bus_barrier = NULL;

        sc->memcopyin = ef_copyin;
        sc->memcopyout = ef_copyout;
        sc->ie_bus_read16 = ef_read_16;
        sc->ie_bus_write16 = ef_write_16;
        sc->ie_bus_write24 = ef_write_24;

        sc->sc_msize = 0;

        /*
         * NOP chains don't give any advantage on this card, in fact they
         * seem to slow it down some.  As the doctor says, "if it hurts,
         * don't do it".
         */
        sc->do_xmitnopchain = 0;

        sc->sc_mediachange = NULL;
        sc->sc_mediastatus = ef_mediastatus;

        /* Find the cards parent bus */
        for (bus = ef_isa_buses.lh_first; bus != NULL;
             bus = bus->isa_link.le_next) {

                if (bus->isa_bus == parent)
                        break;
        }

        if (bus == NULL)
                panic("%s: Can't find parent bus!", device_xname(self));


        /* If the bus hasn't been transitioned to the RUN state, do so now */
        if (bus->bus_state == 1) {
                if (bus_space_map(iot, ELINK_ID_PORT, 1, 0, &ioh) != 0) {
                        DPRINTF(("\n%s: Can't map Elink ID port!\n",
                                device_xname(self)));
                        return;
                }

                bus_space_write_1(ia->ia_iot, ioh, 0, 0x00);
                elink_idseq(ia->ia_iot, ioh, ELINK_507_POLY);
                bus_space_write_1(ia->ia_iot, ioh, 0, 0x00);
                bus_space_unmap(ia->ia_iot, ioh, 1);

                bus->bus_state++;
        }

        /* Map i/o space. */
        if (bus_space_map(ia->ia_iot, ia->ia_io[0].ir_addr,
                ia->ia_io[0].ir_size, 0, &ioh) != 0) {

                DPRINTF(("\n%s: can't map i/o space 0x%x-0x%x\n",
                          device_xname(self), ia->ia_io[0].ir_addr,
                          ia->ia_io[0].ir_addr + ia->ia_io[0].ir_size - 1));
                return;
        }

        esc->sc_regt = ia->ia_iot;
        esc->sc_regh = ioh;

        if (bus_space_map(ia->ia_memt, ia->ia_iomem[0].ir_addr,
            ia->ia_iomem[0].ir_size, 0, &memh) != 0) {

                DPRINTF(("\n%s: can't map iomem space 0x%x-0x%x\n",
                        device_xname(self), ia->ia_iomem[0].ir_addr,
                        ia->ia_iomem[0].ir_addr + ia->ia_iomem[0].ir_size
                        - 1));
                bus_space_unmap(ia->ia_iot, ioh, ia->ia_io[0].ir_size);
                return;
        }

        sc->bt = ia->ia_memt;
        sc->bh = memh;

        sc->sc_msize = ia->ia_iomem[0].ir_size;
        sc->sc_maddr = (void *)memh;
        sc->sc_iobase = (char *)sc->sc_maddr + sc->sc_msize - (1 << 24);

        /* Set up pointers to important on-card control structures */
        sc->iscp = 0;
        sc->scb = IE_ISCP_SZ;
        sc->scp = sc->sc_msize + IE_SCP_ADDR - (1 << 24);

        sc->buf_area = sc->scb + IE_SCB_SZ;
        sc->buf_area_sz = sc->sc_msize - IE_ISCP_SZ - IE_SCB_SZ - IE_SCP_SZ;

        /* Zero card memory */
        bus_space_set_region_1(sc->bt, sc->bh, 0, 0, sc->sc_msize);

        /* Set card to 16-bit bus mode */
        bus_space_write_1(sc->bt, sc->bh, IE_SCP_BUS_USE((u_long)sc->scp),
                          IE_SYSBUS_16BIT);

        /* Set up pointers to key structures */
        ef_write_24(sc, IE_SCP_ISCP((u_long)sc->scp), (u_long)sc->iscp);
        ef_write_16(sc, IE_ISCP_SCB((u_long)sc->iscp), (u_long)sc->scb);
        ef_write_24(sc, IE_ISCP_BASE((u_long)sc->iscp), (u_long)sc->iscp);

        /* flush setup of pointers, check if chip answers */
        if (!i82586_proberam(sc)) {
                DPRINTF(("\n%s: can't talk to i82586!\n",
                        device_xname(self)));
                bus_space_unmap(ia->ia_iot, ioh, ia->ia_io[0].ir_size);
                bus_space_unmap(ia->ia_memt, memh, ia->ia_iomem[0].ir_size);
                return;
        }

        /* Set bank 2 for card part number and revision */
        bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_CTRL,
            EF_CTRL_NRST | EF_CTRL_BNK2);

        /* The card revision is encoded in BCD */
        i = bus_space_read_1(esc->sc_regt, esc->sc_regh, EF_REV);
        esc->card_rev = 10 * (i / 16) + (i % 16);

        for (i = 0; i < EF_TYPE_LEN; i++)
                partno[i] = bus_space_read_1(esc->sc_regt, esc->sc_regh,
                    EF_TYPE + i);

        /* Use part number to guess if card is TP or AUI/BNC model */
        esc->card_type = EF_IS_TP(partno) ? EF_CARD_TP : EF_CARD_BNC;

        /* Set bank 0 for ethernet address */
        bus_space_write_1(esc->sc_regt, esc->sc_regh,
            EF_CTRL, EF_CTRL_NORMAL);

        for (i = 0; i < EF_ADDR_LEN; i++)
                ethaddr[i] = bus_space_read_1(esc->sc_regt, esc->sc_regh,
                    EF_ADDR + i);

        snprintf(vers, sizeof(vers), "%s, rev. %d",
                (esc->card_type == EF_CARD_TP) ? "3C507-TP" : "3C507",
                esc->card_rev);

        if (esc->card_type == EF_CARD_TP)
                i82586_attach(sc, vers, ethaddr, eftp_media, NEFTP_MEDIA,
                    eftp_media[0]);
        else {
                uint8_t media = bus_space_read_1(esc->sc_regt, esc->sc_regh,
                                                  EF_MEDIA);
                media = (media & EF_MEDIA_MASK) >> EF_MEDIA_SHIFT;

                i82586_attach(sc, vers, ethaddr, ef_media, NEF_MEDIA,
                              ef_media[media]);
        }

        /* Clear the interrupt latch just in case. */
        bus_space_write_1(esc->sc_regt, esc->sc_regh, EF_ICTRL, 1);

        esc->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq[0].ir_irq,
            IST_EDGE, IPL_NET, i82586_intr, sc);
        if (esc->sc_ih == NULL) {
                DPRINTF(("\n%s: can't establish interrupt\n",
                        device_xname(self)));
        }
}

static int
ef_port_check(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        int i;
        u_char ch;
        const u_char *signature = EF_SIGNATURE;

        for (i = 0; i < strlen(signature); i++) {
                ch = bus_space_read_1(iot, ioh, i);
                if (ch != signature[i])
                        return 0;
        }

        /* If card is mapped in high memory (above 15Meg), we can't use it */
        ch = bus_space_read_1(iot, ioh, EF_MADDR);
        if (ch & EF_MADDR_HIGH)
                return 0;               /* XXX: maybe we should panic?? */

        return 1;
}

CFATTACH_DECL_NEW(ef, sizeof(struct ef_softc),
    ef_match, ef_attach, NULL, NULL);