/*      $NetBSD: rtsx_pci.c,v 1.11 2025/01/18 08:12:45 mrg Exp $        */
/*      $OpenBSD: rtsx_pci.c,v 1.7 2014/08/19 17:55:03 phessler Exp $   */


/*
* Copyright (c) 2006 Uwe Stuehler <[email protected]>
* Copyright (c) 2012 Stefan Sperling <[email protected]>
*
* 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 and this permission notice 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rtsx_pci.c,v 1.11 2025/01/18 08:12:45 mrg Exp $");

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

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

#include <dev/ic/rtsxreg.h>
#include <dev/ic/rtsxvar.h>

#include <dev/sdmmc/sdmmcvar.h>

#define RTSX_PCI_BAR            0x10
#define RTSX_PCI_BAR_525A       0x14

struct rtsx_pci_softc {
       struct rtsx_softc sc;
       pci_chipset_tag_t sc_pc;
       void *sc_ih;

       pci_intr_handle_t *sc_pihp;
};

static int rtsx_pci_match(device_t , cfdata_t, void *);
static void rtsx_pci_attach(device_t, device_t, void *);
static int rtsx_pci_detach(device_t, int);

CFATTACH_DECL_NEW(rtsx_pci, sizeof(struct rtsx_pci_softc),
   rtsx_pci_match, rtsx_pci_attach, rtsx_pci_detach, NULL);

#ifdef RTSX_DEBUG
extern int rtsxdebug;
#define DPRINTF(n,s)    do { if ((n) <= rtsxdebug) printf s; } while (0)
#else
#define DPRINTF(n,s)    /**/
#endif

static int
rtsx_pci_match(device_t parent, cfdata_t cf, void *aux)
{
       struct pci_attach_args *pa = aux;

       /*
        * Explicitly match the UNDEFINED device class only. Some RTS5209
        * devices advertise a SYSTEM/SDHC class in addition to the UNDEFINED
        * device class. Let sdhc(4) handle the SYSTEM/SDHC ones.
        */
       if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_REALTEK ||
           PCI_CLASS(pa->pa_class) != PCI_CLASS_UNDEFINED)
               return 0;

       switch (PCI_PRODUCT(pa->pa_id)) {
       case PCI_PRODUCT_REALTEK_RTS5209:
       case PCI_PRODUCT_REALTEK_RTS5227:
       case PCI_PRODUCT_REALTEK_RTS5229:
       case PCI_PRODUCT_REALTEK_RTS522A:
       case PCI_PRODUCT_REALTEK_RTS5249:
       case PCI_PRODUCT_REALTEK_RTS525A:
       case PCI_PRODUCT_REALTEK_RTL8402:
       case PCI_PRODUCT_REALTEK_RTL8411:
       case PCI_PRODUCT_REALTEK_RTL8411B:
               return 1;
       }

       return 0;
}

static void
rtsx_pci_attach(device_t parent, device_t self, void *aux)
{
       struct rtsx_pci_softc *sc = device_private(self);
       struct pci_attach_args *pa = (struct pci_attach_args *)aux;
       pci_chipset_tag_t pc = pa->pa_pc;
       pcitag_t tag = pa->pa_tag;
       pcireg_t reg;
       char const *intrstr;
       bus_space_tag_t iot;
       bus_space_handle_t ioh;
       bus_size_t size;
       uint32_t flags;
       int bar = RTSX_PCI_BAR;
       char intrbuf[PCI_INTRSTR_LEN];

       sc->sc.sc_dev = self;
       sc->sc_pc = pc;

       switch (PCI_PRODUCT(pa->pa_id)) {
       case PCI_PRODUCT_REALTEK_RTS5209:
               flags = RTSX_F_5209;
               break;
       case PCI_PRODUCT_REALTEK_RTS5227:
               flags = RTSX_F_5227;
               break;
       case PCI_PRODUCT_REALTEK_RTS5229:
       case PCI_PRODUCT_REALTEK_RTS5249:
               flags = RTSX_F_5229;
               break;
       case PCI_PRODUCT_REALTEK_RTS525A:
               flags = RTSX_F_525A;
               bar = RTSX_PCI_BAR_525A;
               break;
       case PCI_PRODUCT_REALTEK_RTL8402:
               flags = RTSX_F_8402;
               break;
       case PCI_PRODUCT_REALTEK_RTL8411:
               flags = RTSX_F_8411;
               break;
       case PCI_PRODUCT_REALTEK_RTL8411B:
               flags = RTSX_F_8411B;
               break;
       default:
               flags = 0;
               break;
       }

       pci_aprint_devinfo(pa, NULL);

       if ((pci_conf_read(pc, tag, RTSX_CFG_PCI) & RTSX_CFG_ASIC) != 0) {
               aprint_error_dev(self, "no asic\n");
               return;
       }

       if (pci_mapreg_map(pa, bar, PCI_MAPREG_TYPE_MEM, 0,
           &iot, &ioh, NULL, &size)) {
               aprint_error_dev(self, "couldn't map registers\n");
               return;
       }

       if (pci_intr_alloc(pa, &sc->sc_pihp, NULL, 0)) {
               aprint_error_dev(self, "couldn't map interrupt\n");
               return;
       }
       intrstr = pci_intr_string(pc, sc->sc_pihp[0], intrbuf, sizeof(intrbuf));
       sc->sc_ih = pci_intr_establish_xname(pc, sc->sc_pihp[0], IPL_SDMMC,
           rtsx_intr, &sc->sc, device_xname(self));
       if (sc->sc_ih == NULL) {
               aprint_error_dev(self, "couldn't establish interrupt\n");
               return;
       }
       aprint_normal_dev(self, "interrupting at %s\n", intrstr);

       /* Enable the device */
       reg = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG);
       reg |= PCI_COMMAND_MASTER_ENABLE;
       pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, reg);

       /* Power up the device */
       pci_set_powerstate(pc, tag, PCI_PMCSR_STATE_D0);

       if (rtsx_attach(&sc->sc, iot, ioh, size, pa->pa_dmat, flags) != 0) {
               aprint_error_dev(self, "couldn't initialize chip\n");
               return;
       }

       if (!pmf_device_register1(self, rtsx_suspend, rtsx_resume,
           rtsx_shutdown))
               aprint_error_dev(self, "couldn't establish powerhook\n");
}

static int
rtsx_pci_detach(device_t self, int flags)
{
       struct rtsx_pci_softc *sc = device_private(self);
       int rv;

       rv = rtsx_detach(&sc->sc, flags);
       if (rv)
               return rv;

       pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
       pci_intr_release(sc->sc_pc, sc->sc_pihp, 1);

       return 0;
}