/*      $NetBSD: sti_pci.c,v 1.2 2017/10/05 06:37:45 msaitoh Exp $      */

/*      $OpenBSD: sti_pci.c,v 1.7 2009/02/06 22:51:04 miod Exp $        */

/*
* Copyright (c) 2006, 2007 Miodrag Vallat.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice, this permission notice, and the disclaimer below
* appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

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

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

#include <dev/wscons/wsdisplayvar.h>

#include <dev/ic/stireg.h>
#include <dev/ic/stivar.h>

#ifdef STIDEBUG
#define DPRINTF(s)      do {    \
       if (stidebug)           \
               printf s;       \
} while(0)

extern int stidebug;
#else
#define DPRINTF(s)      /* */
#endif

int     sti_pci_match(device_t, cfdata_t, void *);
void    sti_pci_attach(device_t, device_t, void *);

void    sti_pci_end_attach(device_t dev);

struct  sti_pci_softc {
       device_t                sc_dev;

       struct sti_softc        sc_base;

       pci_chipset_tag_t       sc_pc;
       pcitag_t                sc_tag;

       bus_space_handle_t      sc_romh;
};

CFATTACH_DECL_NEW(sti_pci, sizeof(struct sti_pci_softc),
   sti_pci_match, sti_pci_attach, NULL, NULL);

int     sti_readbar(struct sti_softc *, struct pci_attach_args *, u_int, int);
int     sti_check_rom(struct sti_pci_softc *, struct pci_attach_args *);
void    sti_pci_enable_rom(struct sti_softc *);
void    sti_pci_disable_rom(struct sti_softc *);
void    sti_pci_enable_rom_internal(struct sti_pci_softc *);
void    sti_pci_disable_rom_internal(struct sti_pci_softc *);

int     sti_pci_is_console(struct pci_attach_args *, bus_addr_t *);

#define PCI_ROM_SIZE(mr)                                                \
           (PCI_MAPREG_ROM_ADDR(mr) & -PCI_MAPREG_ROM_ADDR(mr))

int
sti_pci_match(device_t parent, cfdata_t cf, void *aux)
{
       struct pci_attach_args *paa = aux;

       if (PCI_VENDOR(paa->pa_id) != PCI_VENDOR_HP)
               return 0;

       if (PCI_PRODUCT(paa->pa_id) == PCI_PRODUCT_HP_VISUALIZE_EG ||
           PCI_PRODUCT(paa->pa_id) == PCI_PRODUCT_HP_VISUALIZE_FX2 ||
           PCI_PRODUCT(paa->pa_id) == PCI_PRODUCT_HP_VISUALIZE_FX4 ||
           PCI_PRODUCT(paa->pa_id) == PCI_PRODUCT_HP_VISUALIZE_FX6 ||
           PCI_PRODUCT(paa->pa_id) == PCI_PRODUCT_HP_VISUALIZE_FXE)
               return 1;

       return 0;
}

void
sti_pci_attach(device_t parent, device_t self, void *aux)
{
       struct sti_pci_softc *spc = device_private(self);
       struct pci_attach_args *paa = aux;
       int ret;

       spc->sc_dev = self;

       spc->sc_pc = paa->pa_pc;
       spc->sc_tag = paa->pa_tag;
       spc->sc_base.sc_dev = self;
       spc->sc_base.sc_enable_rom = sti_pci_enable_rom;
       spc->sc_base.sc_disable_rom = sti_pci_disable_rom;

       aprint_normal("\n");

       if (sti_check_rom(spc, paa) != 0)
               return;

       aprint_normal("%s", device_xname(self));
       ret = sti_pci_is_console(paa, spc->sc_base. bases);
       if (ret != 0)
               spc->sc_base.sc_flags |= STI_CONSOLE;

       ret = sti_attach_common(&spc->sc_base, paa->pa_iot, paa->pa_memt,
           spc->sc_romh, STI_CODEBASE_MAIN);
       if (ret == 0)
               config_interrupts(self, sti_pci_end_attach);

}

void sti_pci_end_attach(device_t dev)
{
       struct sti_pci_softc *spc = device_private(dev);
       struct sti_softc *sc = &spc->sc_base;

       sti_end_attach(sc);
}


/*
* Grovel the STI ROM image.
*/
int
sti_check_rom(struct sti_pci_softc *spc, struct pci_attach_args *pa)
{
       struct sti_softc *sc = &spc->sc_base;
       pcireg_t address, mask;
       bus_space_handle_t romh;
       bus_size_t romsize, subsize, stiromsize;
       bus_addr_t selected, offs, suboffs;
       uint32_t tmp;
       int i;
       int rc;

       /* sort of inline sti_pci_enable_rom(sc) */
       address = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM);
       pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM,
           ~PCI_MAPREG_ROM_ENABLE);
       mask = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM);
       address |= PCI_MAPREG_ROM_ENABLE;
       pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM, address);
       sc->sc_flags |= STI_ROM_ENABLED;
       /*
        * Map the complete ROM for now.
        */

       romsize = PCI_ROM_SIZE(mask);
       DPRINTF(("%s: mapping rom @ %lx for %lx\n", __func__,
           (long)PCI_MAPREG_ROM_ADDR(address), (long)romsize));

       rc = bus_space_map(pa->pa_memt, PCI_MAPREG_ROM_ADDR(address), romsize,
           0, &romh);
       if (rc != 0) {
               aprint_error_dev(sc->sc_dev, "can't map PCI ROM (%d)\n", rc);
               goto fail2;
       }

       sti_pci_disable_rom_internal(spc);
       /*
        * Iterate over the ROM images, pick the best candidate.
        */

       selected = (bus_addr_t)-1;
       for (offs = 0; offs < romsize; offs += subsize) {
               sti_pci_enable_rom_internal(spc);
               /*
                * Check for a valid ROM header.
                */
               tmp = bus_space_read_4(pa->pa_memt, romh, offs + 0);
               tmp = le32toh(tmp);
               if (tmp != 0x55aa0000) {
                       sti_pci_disable_rom_internal(spc);
                       if (offs == 0) {
                               aprint_error_dev(sc->sc_dev,
                                   "invalid PCI ROM header signature (%08x)\n",
                                    tmp);
                               rc = EINVAL;
                       }
                       break;
               }

               /*
                * Check ROM type.
                */
               tmp = bus_space_read_4(pa->pa_memt, romh, offs + 4);
               tmp = le32toh(tmp);
               if (tmp != 0x00000001) {        /* 1 == STI ROM */
                       sti_pci_disable_rom_internal(spc);
                       if (offs == 0) {
                               aprint_error_dev(sc->sc_dev,
                                   "invalid PCI ROM type (%08x)\n", tmp);
                               rc = EINVAL;
                       }
                       break;
               }

               subsize = (bus_addr_t)bus_space_read_2(pa->pa_memt, romh,
                   offs + 0x0c);
               subsize <<= 9;

#ifdef STIDEBUG
               sti_pci_disable_rom_internal(spc);
               DPRINTF(("ROM offset %08x size %08x type %08x",
                   (u_int)offs, (u_int)subsize, tmp));
               sti_pci_enable_rom_internal(spc);
#endif

               /*
                * Check for a valid ROM data structure.
                * We do not need it except to know what architecture the ROM
                * code is for.
                */

               suboffs = offs +(bus_addr_t)bus_space_read_2(pa->pa_memt, romh,
                   offs + 0x18);
               tmp = bus_space_read_4(pa->pa_memt, romh, suboffs + 0);
               tmp = le32toh(tmp);
               if (tmp != 0x50434952) {        /* PCIR */
                       sti_pci_disable_rom_internal(spc);
                       if (offs == 0) {
                               aprint_error_dev(sc->sc_dev, "invalid PCI data"
                                   " signature (%08x)\n", tmp);
                               rc = EINVAL;
                       } else {
                               DPRINTF((" invalid PCI data signature %08x\n",
                                   tmp));
                               continue;
                       }
               }

               tmp = bus_space_read_1(pa->pa_memt, romh, suboffs + 0x14);
               sti_pci_disable_rom_internal(spc);
               DPRINTF((" code %02x", tmp));

               switch (tmp) {
#ifdef __hppa__
               case 0x10:
                       if (selected == (bus_addr_t)-1)
                               selected = offs;
                       break;
#endif
#ifdef __i386__
               case 0x00:
                       if (selected == (bus_addr_t)-1)
                               selected = offs;
                       break;
#endif
               default:
#ifdef STIDEBUG
                       DPRINTF((" (wrong architecture)"));
#endif
                       break;
               }
               DPRINTF(("%s\n", selected == offs ? " -> SELECTED" : ""));
       }

       if (selected == (bus_addr_t)-1) {
               if (rc == 0) {
                       aprint_error_dev(sc->sc_dev, "found no ROM with "
                           "correct microcode architecture\n");
                       rc = ENOEXEC;
               }
               goto fail;
       }

       /*
        * Read the STI region BAR assignments.
        */

       sti_pci_enable_rom_internal(spc);
       offs = selected +
           (bus_addr_t)bus_space_read_2(pa->pa_memt, romh, selected + 0x0e);
       for (i = 0; i < STI_REGION_MAX; i++) {
               rc = sti_readbar(sc, pa, i,
                   bus_space_read_1(pa->pa_memt, romh, offs + i));
               if (rc != 0)
                       goto fail;
       }

       /*
        * Find out where the STI ROM itself lies, and its size.
        */

       offs = selected +
           (bus_addr_t)bus_space_read_4(pa->pa_memt, romh, selected + 0x08);
       stiromsize = (bus_addr_t)bus_space_read_4(pa->pa_memt, romh,
           offs + 0x18);
       stiromsize = le32toh(stiromsize);
       sti_pci_disable_rom_internal(spc);

       /*
        * Replace our mapping with a smaller mapping of only the area
        * we are interested in.
        */

       DPRINTF(("remapping rom @ %lx for %lx\n",
           (long)(PCI_MAPREG_ROM_ADDR(address) + offs), (long)stiromsize));
       bus_space_unmap(pa->pa_memt, romh, romsize);
       rc = bus_space_map(pa->pa_memt, PCI_MAPREG_ROM_ADDR(address) + offs,
           stiromsize, 0, &spc->sc_romh);
       if (rc != 0) {
               aprint_error_dev(sc->sc_dev, "can't map STI ROM (%d)\n",
                   rc);
               goto fail2;
       }
       sti_pci_disable_rom_internal(spc);
       sc->sc_flags &= ~STI_ROM_ENABLED;

       return 0;

fail:
       bus_space_unmap(pa->pa_memt, romh, romsize);
fail2:
       sti_pci_disable_rom_internal(spc);

       return rc;
}

/*
* Decode a BAR register.
*/
int
sti_readbar(struct sti_softc *sc, struct pci_attach_args *pa, u_int region,
   int bar)
{
       bus_addr_t addr;
       bus_size_t size;
       uint32_t cf;
       int rc;

       if (bar == 0) {
               sc->bases[region] = 0;
               return (0);
       }

#ifdef DIAGNOSTIC
       if (bar < PCI_MAPREG_START || bar > PCI_MAPREG_PPB_END) {
               sti_pci_disable_rom(sc);
               printf("%s: unexpected bar %02x for region %d\n",
                   device_xname(sc->sc_dev), bar, region);
               sti_pci_enable_rom(sc);
       }
#endif

       cf = pci_conf_read(pa->pa_pc, pa->pa_tag, bar);

       rc = pci_mapreg_info(pa->pa_pc, pa->pa_tag, bar, PCI_MAPREG_TYPE(cf),
           &addr, &size, NULL);

       if (rc != 0) {
               sti_pci_disable_rom(sc);
               aprint_error_dev(sc->sc_dev, "invalid bar %02x for region %d\n",
                   bar, region);
               sti_pci_enable_rom(sc);
               return (rc);
       }

       sc->bases[region] = addr;
       return (0);
}

/*
* Enable PCI ROM.
*/
void
sti_pci_enable_rom_internal(struct sti_pci_softc *spc)
{
       pcireg_t address;

       KASSERT(spc != NULL);

       address = pci_conf_read(spc->sc_pc, spc->sc_tag, PCI_MAPREG_ROM);
       address |= PCI_MAPREG_ROM_ENABLE;
       pci_conf_write(spc->sc_pc, spc->sc_tag, PCI_MAPREG_ROM, address);
}

void
sti_pci_enable_rom(struct sti_softc *sc)
{
       struct sti_pci_softc *spc = device_private(sc->sc_dev);

       if (!ISSET(sc->sc_flags, STI_ROM_ENABLED)) {
               sti_pci_enable_rom_internal(spc);
       }
       SET(sc->sc_flags, STI_ROM_ENABLED);
}

/*
* Disable PCI ROM.
*/
void
sti_pci_disable_rom_internal(struct sti_pci_softc *spc)
{
       pcireg_t address;

       KASSERT(spc != NULL);

       address = pci_conf_read(spc->sc_pc, spc->sc_tag, PCI_MAPREG_ROM);
       address &= ~PCI_MAPREG_ROM_ENABLE;
       pci_conf_write(spc->sc_pc, spc->sc_tag, PCI_MAPREG_ROM, address);
}

void
sti_pci_disable_rom(struct sti_softc *sc)
{
       struct sti_pci_softc *spc = device_private(sc->sc_dev);

       if (ISSET(sc->sc_flags, STI_ROM_ENABLED)) {
               sti_pci_disable_rom_internal(spc);
       }
       CLR(sc->sc_flags, STI_ROM_ENABLED);
}