#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/intr.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/ppbreg.h>
#define PCI_HT_LAST_ISMG (0x01 << 16)
#define PCI_HT_IMSG_LO(idx) (((2 * (idx)) + 0x10) << 16)
#define HT_MASK (1 << 0)
#define HT_ACTIVELOW (1 << 1)
#define HT_EOI (1 << 5)
#define PCI_HT_IMSG_HI(idx) (PCI_HT_IMSG_LO(idx) + 1)
#define HT_PASSPW (1U << 30)
#define HT_WAITEOI (1U << 31)
#define HT_APPL_EOIOFF(idx) (0x60 + (((idx) >> 3) & ~3))
#define HT_APPL_WEOI(idx) (1 << ((idx) & 0x1f))
struct ht_intr_msg {
unsigned int him_idx;
int him_ist;
pcireg_t him_weoi;
};
#define hpb_MAX_IMSG 128
struct hpb_softc {
struct device sc_dev;
pci_chipset_tag_t sc_pc;
pcitag_t sc_tag;
pcireg_t sc_id;
unsigned int sc_off;
unsigned int sc_nirq;
struct ht_intr_msg sc_imap[hpb_MAX_IMSG];
};
int hpb_match(struct device *, void *, void *);
void hpb_attach(struct device *, struct device *, void *);
int hpb_print(void *, const char *);
void hpb_eoi(int);
int hpb_enable_irq(int, int);
int hpb_disable_irq(int, int);
const struct cfattach hpb_ca = {
sizeof(struct hpb_softc), hpb_match, hpb_attach
};
struct cfdriver hpb_cd = {
NULL, "hpb", DV_DULL,
};
int
hpb_match(struct device *parent, void *match, void *aux)
{
struct pci_attach_args *pa = aux;
pci_chipset_tag_t pc = pa->pa_pc;
pcitag_t tag = pa->pa_tag;
if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_PCI)
return (0);
if (!pci_get_ht_capability(pc, tag, PCI_HT_CAP_INTR, NULL, NULL))
return (0);
return (10);
}
void
hpb_attach(struct device *parent, struct device *self, void *aux)
{
struct hpb_softc *sc = (struct hpb_softc *)self;
struct pci_attach_args *pa = aux;
struct pcibus_attach_args pba;
pci_chipset_tag_t pc = pa->pa_pc;
pcitag_t tag = pa->pa_tag;
int idx, irq, off;
pcireg_t busdata, reg;
if (!pci_get_ht_capability(pc, tag, PCI_HT_CAP_INTR, &off, NULL))
panic("A clown ate your HT capability");
pci_conf_write(pc, tag, off, PCI_HT_LAST_ISMG);
reg = pci_conf_read(pc, tag, off + PCI_HT_INTR_DATA);
sc->sc_pc = pc;
sc->sc_tag = tag;
sc->sc_id = pa->pa_id;
sc->sc_off = off;
sc->sc_nirq = ((reg >> 16) & 0xff);
if (sc->sc_nirq == 0 || sc->sc_nirq > hpb_MAX_IMSG)
return;
printf(": %u sources\n", sc->sc_nirq);
for (idx = 0; idx < sc->sc_nirq; idx++) {
pci_conf_write(pc, tag, off, PCI_HT_IMSG_LO(idx));
reg = pci_conf_read(pc, tag, off + PCI_HT_INTR_DATA);
pci_conf_write(pc, tag, off + PCI_HT_INTR_DATA, reg | HT_MASK);
irq = (reg >> 16) & 0xff;
#ifdef DIAGNOSTIC
if (sc->sc_imap[irq].him_idx != 0) {
printf("%s: multiple definition for irq %d\n",
sc->sc_dev.dv_xname, irq);
continue;
}
#endif
pci_conf_write(pc, tag, off, PCI_HT_IMSG_HI(idx));
reg = pci_conf_read(pc, tag, off + PCI_HT_INTR_DATA);
sc->sc_imap[irq].him_idx = idx;
sc->sc_imap[irq].him_weoi = reg | HT_WAITEOI;
}
busdata = pci_conf_read(pc, pa->pa_tag, PPB_REG_BUSINFO);
memset(&pba, 0, sizeof(pba));
pba.pba_busname = "pci";
pba.pba_iot = pa->pa_iot;
pba.pba_memt = pa->pa_memt;
pba.pba_dmat = pa->pa_dmat;
pba.pba_pc = pc;
pba.pba_domain = pa->pa_domain;
pba.pba_bus = PPB_BUSINFO_SECONDARY(busdata);
pba.pba_bridgetag = &sc->sc_tag;
config_found(self, &pba, hpb_print);
}
int
hpb_print(void *aux, const char *pnp)
{
struct pcibus_attach_args *pba = aux;
if (pnp)
printf("%s at %s", pba->pba_busname, pnp);
printf(" bus %d", pba->pba_bus);
return (UNCONF);
}
void
hpb_eoi(int irq)
{
struct hpb_softc *sc = hpb_cd.cd_devs[0];
pci_chipset_tag_t pc = sc->sc_pc;
pcitag_t tag = sc->sc_tag;
int idx;
if (irq >= sc->sc_nirq || sc->sc_imap[irq].him_weoi == 0 ||
sc->sc_imap[irq].him_ist != IST_LEVEL)
return;
idx = sc->sc_imap[irq].him_idx;
if (PCI_VENDOR(sc->sc_id) == PCI_VENDOR_APPLE) {
pci_conf_write(pc, tag, HT_APPL_EOIOFF(idx), HT_APPL_WEOI(idx));
} else {
pci_conf_write(pc, tag, sc->sc_off, PCI_HT_IMSG_HI(idx));
pci_conf_write(pc, tag, sc->sc_off + PCI_HT_INTR_DATA,
sc->sc_imap[irq].him_weoi);
}
}
int
hpb_enable_irq(int irq, int ist)
{
struct hpb_softc *sc = hpb_cd.cd_devs[0];
pci_chipset_tag_t pc = sc->sc_pc;
pcitag_t tag = sc->sc_tag;
pcireg_t reg;
int idx;
if (irq >= sc->sc_nirq || sc->sc_imap[irq].him_weoi == 0)
return (0);
idx = sc->sc_imap[irq].him_idx;
sc->sc_imap[irq].him_ist = ist;
pci_conf_write(pc, tag, sc->sc_off, PCI_HT_IMSG_LO(idx));
reg = pci_conf_read(pc, tag, sc->sc_off + PCI_HT_INTR_DATA);
pci_conf_write(pc, tag, sc->sc_off + PCI_HT_INTR_DATA, reg | HT_MASK);
reg &= ~(HT_ACTIVELOW | HT_EOI | HT_MASK);
if (ist == IST_LEVEL)
reg |= HT_ACTIVELOW | HT_EOI;
pci_conf_write(pc, tag, sc->sc_off + PCI_HT_INTR_DATA, reg);
return (1);
}
int
hpb_disable_irq(int irq, int ist)
{
struct hpb_softc *sc = hpb_cd.cd_devs[0];
pci_chipset_tag_t pc = sc->sc_pc;
pcitag_t tag = sc->sc_tag;
pcireg_t reg;
int idx;
if (irq > sc->sc_nirq || sc->sc_imap[irq].him_weoi == 0)
return (0);
idx = sc->sc_imap[irq].him_idx;
pci_conf_write(pc, tag, sc->sc_off, PCI_HT_IMSG_LO(idx));
reg = pci_conf_read(pc, tag, sc->sc_off + PCI_HT_INTR_DATA);
pci_conf_write(pc, tag, sc->sc_off + PCI_HT_INTR_DATA, reg | HT_MASK);
return (1);
}