root/sys/dev/cardbus/if_tlp_cardbus.c
/*      $NetBSD: if_tlp_cardbus.c,v 1.72 2022/09/25 17:33:19 thorpej Exp $      */

/*-
 * Copyright (c) 1999, 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * 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.
 */

/*
 * CardBus bus front-end for the Digital Semiconductor ``Tulip'' (21x4x)
 * Ethernet controller family driver.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_tlp_cardbus.c,v 1.72 2022/09/25 17:33:19 thorpej Exp $");

#include "opt_inet.h"

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

#include <machine/endian.h>

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

#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif


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

#include <dev/mii/miivar.h>
#include <dev/mii/mii_bitbang.h>

#include <dev/ic/tulipreg.h>
#include <dev/ic/tulipvar.h>

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

#include <dev/cardbus/cardbusvar.h>
#include <dev/pci/pcidevs.h>

/*
 * PCI configuration space registers used by the Tulip.
 */
#define TULIP_PCI_IOBA PCI_BAR(0)       /* i/o mapped base */
#define TULIP_PCI_MMBA PCI_BAR(1)       /* memory mapped base */
#define TULIP_PCI_CFDA          0x40    /* configuration driver area */

#define CFDA_SLEEP              0x80000000      /* sleep mode */
#define CFDA_SNOOZE             0x40000000      /* snooze mode */

struct tulip_cardbus_softc {
        struct tulip_softc sc_tulip;    /* real Tulip softc */

        /* CardBus-specific goo. */
        void    *sc_ih;                 /* interrupt handle */
        cardbus_devfunc_t sc_ct;        /* our CardBus devfuncs */
        pcitag_t sc_tag;                /* our CardBus tag */
        pcireg_t sc_csr;
        bus_size_t sc_mapsize;          /* the size of mapped bus space
                                           region */

        int     sc_bar_reg;             /* which BAR to use */
        pcireg_t sc_bar_val;            /* value of the BAR */
};

int     tlp_cardbus_match(device_t, cfdata_t, void *);
void    tlp_cardbus_attach(device_t, device_t, void *);
int     tlp_cardbus_detach(device_t, int);

CFATTACH_DECL_NEW(tlp_cardbus, sizeof(struct tulip_cardbus_softc),
    tlp_cardbus_match, tlp_cardbus_attach, tlp_cardbus_detach, tlp_activate);

const struct tulip_cardbus_product {
        u_int32_t       tcp_vendor;     /* PCI vendor ID */
        u_int32_t       tcp_product;    /* PCI product ID */
        tulip_chip_t    tcp_chip;       /* base Tulip chip type */
} tlp_cardbus_products[] = {
        { PCI_VENDOR_DEC,       PCI_PRODUCT_DEC_21142,
          TULIP_CHIP_21142 },

        { PCI_VENDOR_XIRCOM,    PCI_PRODUCT_XIRCOM_X3201_3_21143,
          TULIP_CHIP_X3201_3 },

        { PCI_VENDOR_ADMTEK,    PCI_PRODUCT_ADMTEK_AN983,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_ACCTON,    PCI_PRODUCT_ACCTON_EN2242,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_ABOCOM,    PCI_PRODUCT_ABOCOM_FE2500,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_ABOCOM,    PCI_PRODUCT_ABOCOM_PCM200,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_ABOCOM,    PCI_PRODUCT_ABOCOM_FE2500MX,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_HAWKING,   PCI_PRODUCT_HAWKING_PN672TX,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_ADMTEK,    PCI_PRODUCT_ADMTEK_AN985,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_MICROSOFT, PCI_PRODUCT_MICROSOFT_MN120,
          TULIP_CHIP_AN985 },

        { PCI_VENDOR_LINKSYS, PCI_PRODUCT_LINKSYS_PCMPC200,
          TULIP_CHIP_AN985 },

        { 0,                            0,
          TULIP_CHIP_INVALID },
};

struct tlp_cardbus_quirks {
        void            (*tpq_func)(struct tulip_cardbus_softc *,
                            const u_int8_t *);
        u_int8_t        tpq_oui[3];
};

void    tlp_cardbus_lxt_quirks(struct tulip_cardbus_softc *,
            const u_int8_t *);

const struct tlp_cardbus_quirks tlp_cardbus_21142_quirks[] = {
        { tlp_cardbus_lxt_quirks,       { 0x00, 0x40, 0x05 } },
        { NULL,                         { 0, 0, 0 } }
};

void    tlp_cardbus_setup(struct tulip_cardbus_softc *);

int     tlp_cardbus_enable(struct tulip_softc *);
void    tlp_cardbus_disable(struct tulip_softc *);
void    tlp_cardbus_power(struct tulip_softc *, int);

void    tlp_cardbus_x3201_reset(struct tulip_softc *);

const struct tulip_cardbus_product *tlp_cardbus_lookup
   (const struct cardbus_attach_args *);
void tlp_cardbus_get_quirks(struct tulip_cardbus_softc *,
    const u_int8_t *, const struct tlp_cardbus_quirks *);

const struct tulip_cardbus_product *
tlp_cardbus_lookup(const struct cardbus_attach_args *ca)
{
        const struct tulip_cardbus_product *tcp;

        for (tcp = tlp_cardbus_products; tcp->tcp_chip != TULIP_CHIP_INVALID;
             tcp++) {
                if (PCI_VENDOR(ca->ca_id) == tcp->tcp_vendor &&
                    PCI_PRODUCT(ca->ca_id) == tcp->tcp_product)
                        return (tcp);
        }
        return (NULL);
}

void
tlp_cardbus_get_quirks(struct tulip_cardbus_softc *csc, const u_int8_t *enaddr,
    const struct tlp_cardbus_quirks *tpq)
{

        for (; tpq->tpq_func != NULL; tpq++) {
                if (tpq->tpq_oui[0] == enaddr[0] &&
                    tpq->tpq_oui[1] == enaddr[1] &&
                    tpq->tpq_oui[2] == enaddr[2]) {
                        (*tpq->tpq_func)(csc, enaddr);
                        return;
                }
        }
}

int
tlp_cardbus_match(device_t parent, cfdata_t match, void *aux)
{
        struct cardbus_attach_args *ca = aux;

        if (tlp_cardbus_lookup(ca) != NULL)
                return (1);

        return (0);
}

void
tlp_cardbus_attach(device_t parent, device_t self, void *aux)
{
        struct tulip_cardbus_softc *csc = device_private(self);
        struct tulip_softc *sc = &csc->sc_tulip;
        struct cardbus_attach_args *ca = aux;
        cardbus_devfunc_t ct = ca->ca_ct;
        const struct tulip_cardbus_product *tcp;
        u_int8_t enaddr[ETHER_ADDR_LEN];
        bus_addr_t adr;
        pcireg_t reg;

        sc->sc_dev = self;
        sc->sc_devno = 0;
        sc->sc_dmat = ca->ca_dmat;
        csc->sc_ct = ct;
        csc->sc_tag = ca->ca_tag;

        tcp = tlp_cardbus_lookup(ca);
        if (tcp == NULL) {
                printf("\n");
                panic("tlp_cardbus_attach: impossible");
        }
        sc->sc_chip = tcp->tcp_chip;

        /*
         * By default, Tulip registers are 8 bytes long (4 bytes
         * followed by a 4 byte pad).
         */
        sc->sc_regshift = 3;

        /*
         * Power management hooks.
         */
        sc->sc_enable = tlp_cardbus_enable;
        sc->sc_disable = tlp_cardbus_disable;
        sc->sc_power = tlp_cardbus_power;

        /*
         * Get revision info, and set some chip-specific variables.
         */
        sc->sc_rev = PCI_REVISION(ca->ca_class);
        switch (sc->sc_chip) {
        case TULIP_CHIP_21142:
                if (sc->sc_rev >= 0x20)
                        sc->sc_chip = TULIP_CHIP_21143;
                break;

        case TULIP_CHIP_AN985:
                /*
                 * The AN983 and AN985 are very similar, and are
                 * differentiated by a "signature" register that
                 * is like, but not identical, to a PCI ID register.
                 */
                reg = Cardbus_conf_read(ct, csc->sc_tag, 0x80);
                switch (reg) {
                case 0x09811317:
                        sc->sc_chip = TULIP_CHIP_AN985;
                        break;

                case 0x09851317:
                        sc->sc_chip = TULIP_CHIP_AN983;
                        break;

                }
                break;

        default:
                /* Nothing. -- to make gcc happy */
                break;
        }

        aprint_normal(": %s Ethernet, pass %d.%d\n",
            tlp_chip_name(sc->sc_chip),
            (sc->sc_rev >> 4) & 0xf, sc->sc_rev & 0xf);

        /*
         * Map the device.
         */
        csc->sc_csr = PCI_COMMAND_MASTER_ENABLE;
        if (Cardbus_mapreg_map(ct, TULIP_PCI_MMBA,
            PCI_MAPREG_TYPE_MEM, 0, &sc->sc_st, &sc->sc_sh, &adr,
            &csc->sc_mapsize) == 0) {
                csc->sc_csr |= PCI_COMMAND_MEM_ENABLE;
                csc->sc_bar_reg = TULIP_PCI_MMBA;
                csc->sc_bar_val = adr | PCI_MAPREG_TYPE_MEM;
        } else if (Cardbus_mapreg_map(ct, TULIP_PCI_IOBA,
            PCI_MAPREG_TYPE_IO, 0, &sc->sc_st, &sc->sc_sh, &adr,
            &csc->sc_mapsize) == 0) {
                csc->sc_csr |= PCI_COMMAND_IO_ENABLE;
                csc->sc_bar_reg = TULIP_PCI_IOBA;
                csc->sc_bar_val = adr | PCI_MAPREG_TYPE_IO;
        } else {
                aprint_error_dev(self, "unable to map device registers\n");
                return;
        }

        /*
         * Bring the chip out of powersave mode and initialize the
         * configuration registers.
         */
        tlp_cardbus_setup(csc);

        /*
         * Read the contents of the Ethernet Address ROM/SROM.
         */
        switch (sc->sc_chip) {
        case TULIP_CHIP_X3201_3:
                /*
                 * No SROM on this chip.
                 */
                break;

        default:
                if (tlp_read_srom(sc) == 0)
                        goto cant_cope;
                break;
        }

        /*
         * Deal with chip/board quirks.  This includes setting up
         * the mediasw, and extracting the Ethernet address from
         * the rombuf.
         */
        switch (sc->sc_chip) {
        case TULIP_CHIP_21142:
        case TULIP_CHIP_21143:
                /* Check for new format SROM. */
                if (tlp_isv_srom_enaddr(sc, enaddr) != 0) {
                        /*
                         * We start out with the 2114x ISV media switch.
                         * When we search for quirks, we may change to
                         * a different switch.
                         */
                        sc->sc_mediasw = &tlp_2114x_isv_mediasw;
                } else if (tlp_parse_old_srom(sc, enaddr) == 0) {
                        /*
                         * Not an ISV SROM, and not in old DEC Address
                         * ROM format.  Try to snarf it out of the CIS.
                         */
                        if (ca->ca_cis.funce.network.netid_present == 0)
                                goto cant_cope;

                        /* Grab the MAC address from the CIS. */
                        memcpy(enaddr, ca->ca_cis.funce.network.netid,
                            sizeof(enaddr));
                }

                /*
                 * Deal with any quirks this board might have.
                 */
                tlp_cardbus_get_quirks(csc, enaddr, tlp_cardbus_21142_quirks);

                /*
                 * If we don't already have a media switch, default to
                 * MII-over-SIO, with no special reset routine.
                 */
                if (sc->sc_mediasw == NULL) {
                        aprint_normal("%s: defaulting to MII-over-SIO; "
                            "no bets...\n", device_xname(self));
                        sc->sc_mediasw = &tlp_sio_mii_mediasw;
                }
                break;

        case TULIP_CHIP_AN983:
        case TULIP_CHIP_AN985:
                /*
                 * The ADMtek AN985's Ethernet address is located
                 * at offset 8 of its EEPROM.
                 */
                memcpy(enaddr, &sc->sc_srom[8], ETHER_ADDR_LEN);

                /*
                 * The ADMtek AN985 can be configured in Single-Chip
                 * mode or MAC-only mode.  Single-Chip uses the built-in
                 * PHY, MAC-only has an external PHY (usually HomePNA).
                 * The selection is based on an EEPROM setting, and both
                 * PHYs are access via MII attached to SIO.
                 *
                 * The AN985 "ghosts" the internal PHY onto all
                 * MII addresses, so we have to use a media init
                 * routine that limits the search.
                 * XXX How does this work with MAC-only mode?
                 */
                sc->sc_mediasw = &tlp_an985_mediasw;
                break;

        case TULIP_CHIP_X3201_3:
                /*
                 * The X3201 doesn't have an SROM.  Lift the MAC address
                 * from the CIS.  Also, we have a special media switch:
                 * MII-on-SIO, plus some special GPIO setup.
                 */
                memcpy(enaddr, ca->ca_cis.funce.network.netid, sizeof(enaddr));
                sc->sc_reset = tlp_cardbus_x3201_reset;
                sc->sc_mediasw = &tlp_sio_mii_mediasw;
                break;

        default:
 cant_cope:
                aprint_error_dev(self, "sorry, unable to handle your board\n");
                return;
        }

        /*
         * Finish off the attach.
         */
        tlp_attach(sc, enaddr);

        /*
         * Power down the socket.
         */
        Cardbus_function_disable(csc->sc_ct);
}

int
tlp_cardbus_detach(device_t self, int flags)
{
        struct tulip_cardbus_softc *csc = device_private(self);
        struct tulip_softc *sc = &csc->sc_tulip;
        struct cardbus_devfunc *ct = csc->sc_ct;
        int rv;

#if defined(DIAGNOSTIC)
        if (ct == NULL)
                panic("%s: data structure lacks", device_xname(self));
#endif

        rv = tlp_detach(sc);
        if (rv)
                return (rv);

        /*
         * Unhook the interrupt handler.
         */
        if (csc->sc_ih != NULL)
                Cardbus_intr_disestablish(ct, csc->sc_ih);

        /*
         * Release bus space and close window.
         */
        if (csc->sc_bar_reg != 0)
                Cardbus_mapreg_unmap(ct, csc->sc_bar_reg,
                    sc->sc_st, sc->sc_sh, csc->sc_mapsize);

        return (0);
}

int
tlp_cardbus_enable(struct tulip_softc *sc)
{
        struct tulip_cardbus_softc *csc = (void *) sc;
        cardbus_devfunc_t ct = csc->sc_ct;

        /*
         * Power on the socket.
         */
        Cardbus_function_enable(ct);

        /*
         * Set up the PCI configuration registers.
         */
        tlp_cardbus_setup(csc);

        /*
         * Map and establish the interrupt.
         */
        csc->sc_ih = Cardbus_intr_establish(ct, IPL_NET, tlp_intr, sc);
        if (csc->sc_ih == NULL) {
                aprint_error_dev(sc->sc_dev,
                                 "unable to establish interrupt\n");
                Cardbus_function_disable(csc->sc_ct);
                return (1);
        }
        return (0);
}

void
tlp_cardbus_disable(struct tulip_softc *sc)
{
        struct tulip_cardbus_softc *csc = (void *) sc;
        cardbus_devfunc_t ct = csc->sc_ct;

        /* Unhook the interrupt handler. */
        Cardbus_intr_disestablish(ct, csc->sc_ih);
        csc->sc_ih = NULL;

        /* Power down the socket. */
        Cardbus_function_disable(ct);
}

void
tlp_cardbus_power(struct tulip_softc *sc, int why)
{

        switch (why) {
        case PWR_RESUME:
                tlp_cardbus_enable(sc);
                break;
        case PWR_SUSPEND:
                tlp_cardbus_disable(sc);
                break;
        }
}

void
tlp_cardbus_setup(struct tulip_cardbus_softc *csc)
{
        struct tulip_softc *sc = &csc->sc_tulip;
        cardbus_devfunc_t ct = csc->sc_ct;
        pcireg_t reg;

        /*
         * Check to see if the device is in power-save mode, and
         * bring it out if necessary.
         */
        switch (sc->sc_chip) {
        case TULIP_CHIP_21142:
        case TULIP_CHIP_21143:
        case TULIP_CHIP_X3201_3:
                /*
                 * Clear the "sleep mode" bit in the CFDA register.
                 */
                reg = Cardbus_conf_read(ct, csc->sc_tag, TULIP_PCI_CFDA);
                if (reg & (CFDA_SLEEP|CFDA_SNOOZE))
                        Cardbus_conf_write(ct, csc->sc_tag, TULIP_PCI_CFDA,
                            reg & ~(CFDA_SLEEP|CFDA_SNOOZE));
                break;

        default:
                /* Nothing. -- to make gcc happy */
                break;
        }

        (void)cardbus_set_powerstate(ct, csc->sc_tag, PCI_PWR_D0);

        /* Program the BAR. */
        Cardbus_conf_write(ct, csc->sc_tag, csc->sc_bar_reg, csc->sc_bar_val);

        /* Enable the appropriate bits in the PCI CSR. */
        reg = Cardbus_conf_read(ct, csc->sc_tag, PCI_COMMAND_STATUS_REG);
        reg &= ~(PCI_COMMAND_IO_ENABLE|PCI_COMMAND_MEM_ENABLE);
        reg |= csc->sc_csr;
        Cardbus_conf_write(ct, csc->sc_tag, PCI_COMMAND_STATUS_REG, reg);

        /*
         * Make sure the latency timer is set to some reasonable
         * value.
         */
        reg = Cardbus_conf_read(ct, csc->sc_tag, PCI_BHLC_REG);
        if (PCI_LATTIMER(reg) < 0x20) {
                reg &= ~(PCI_LATTIMER_MASK << PCI_LATTIMER_SHIFT);
                reg |= (0x20 << PCI_LATTIMER_SHIFT);
                Cardbus_conf_write(ct, csc->sc_tag, PCI_BHLC_REG, reg);
        }
}

void
tlp_cardbus_x3201_reset(struct tulip_softc *sc)
{
        u_int32_t reg;

        reg = TULIP_READ(sc, CSR_SIAGEN);

        /* make GP[2,0] outputs */
        TULIP_WRITE(sc, CSR_SIAGEN, (reg & ~SIAGEN_MD) | SIAGEN_CWE |
            0x00050000);
        TULIP_WRITE(sc, CSR_SIAGEN, (reg & ~SIAGEN_CWE) | SIAGEN_MD);
}

void
tlp_cardbus_lxt_quirks(struct tulip_cardbus_softc *csc, const u_int8_t *enaddr)
{
        struct tulip_softc *sc = &csc->sc_tulip;

        sc->sc_mediasw = &tlp_sio_mii_mediasw;
}