/*	$OpenBSD: pci_machdep.c,v 1.6 2015/03/30 13:54:14 mpi Exp $	*/

/*
 * Copyright (c) 2013 Martin Pieuchot
 * Copyright (c) 1997 Per Fogelstrom
 *
 * 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 AUTHOR ``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 AUTHOR 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/param.h>
#include <sys/systm.h>

#include <machine/bus.h>
#include <machine/autoconf.h>

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

#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_pci.h>


struct powerpc_bus_dma_tag pci_bus_dma_tag = {
	NULL,
	_dmamap_create,
	_dmamap_destroy,
	_dmamap_load,
	_dmamap_load_mbuf,
	_dmamap_load_uio,
	_dmamap_load_raw,
	_dmamap_unload,
	_dmamap_sync,
	_dmamem_alloc,
	_dmamem_alloc_range,
	_dmamem_free,
	_dmamem_map,
	_dmamem_unmap,
	_dmamem_mmap
};

void
pci_attach_hook(struct device *parent, struct device *self,
    struct pcibus_attach_args *pba)
{
}

int
pci_bus_maxdevs(pci_chipset_tag_t pc, int busno)
{
	return (32);
}

pcitag_t
pci_make_tag(pci_chipset_tag_t pc, int b, int d, int f)
{
	struct ofw_pci_register reg;
	pcitag_t tag;
	int node, busrange[2];

	if (pc->busnode[b])
		return PCITAG_CREATE(0, b, d, f);

	node = pc->pc_node;

	/*
	 * Because ht(4) controller nodes do not have a "bus-range"
	 * property,  we need to start iterating from one of their
	 * PCI bridge nodes to be able to find our devices.
	 */
	if (OF_getprop(node, "bus-range", &busrange, sizeof(busrange)) < 0)
		node = OF_child(pc->pc_node);

	for (; node; node = OF_peer(node)) {
		/*
		 * Check for PCI-PCI bridges.  If the device we want is
		 * in the bus-range for that bridge, work our way down.
		 */
		while ((OF_getprop(node, "bus-range", &busrange,
			sizeof(busrange)) == sizeof(busrange)) &&
			(b >= busrange[0] && b <= busrange[1])) {
			node = OF_child(node);
		}

		if (OF_getprop(node, "reg", &reg, sizeof(reg)) < sizeof(reg))
			continue;

		if (b != OFW_PCI_PHYS_HI_BUS(reg.phys_hi))
			continue;
		if (d != OFW_PCI_PHYS_HI_DEVICE(reg.phys_hi))
			continue;
		if (f != OFW_PCI_PHYS_HI_FUNCTION(reg.phys_hi))
			continue;

		tag = PCITAG_CREATE(node, b, d, f);

		return (tag);
	}

	return (PCITAG_CREATE(-1, b, d, f));
}

void
pci_decompose_tag(pci_chipset_tag_t pc, pcitag_t tag, int *b, int *d, int *f)
{
	if (b != NULL)
		*b = PCITAG_BUS(tag);
	if (d != NULL)
		*d = PCITAG_DEV(tag);
	if (f != NULL)
		*f = PCITAG_FUN(tag);
}

int
pci_conf_size(pci_chipset_tag_t pc, pcitag_t tag)
{
	return (PCI_CONFIG_SPACE_SIZE);
}

pcireg_t
pci_conf_read(pci_chipset_tag_t pc, pcitag_t tag, int reg)
{
        if (PCITAG_NODE(tag) != -1)
		return (*(pc)->pc_conf_read)(pc->pc_conf_v, tag, reg);

        return ((pcireg_t)~0);
}

void
pci_conf_write(pci_chipset_tag_t pc, pcitag_t tag, int reg, pcireg_t data)
{
        if (PCITAG_NODE(tag) != -1)
		(*(pc)->pc_conf_write)(pc->pc_conf_v, tag, reg, data);
}

int
pci_intr_map(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
{
	struct ofw_pci_register reg;
	int node = PCITAG_NODE(pa->pa_tag);
	int intr[4], len;

	if (OF_getprop(node, "reg", &reg, sizeof(reg)) < sizeof(reg))
		return (ENODEV);

	/* Try to get the old Apple OFW interrupt property first. */
	len = OF_getprop(node, "AAPL,interrupts", &intr, sizeof(intr));
	if (len == sizeof(intr[0]))
		goto found;

	len = OF_getprop(node, "interrupts", intr, sizeof(intr));
	if (len < sizeof(intr[0]))
		return (ENODEV);

	reg.size_hi = intr[0];
	if (ofw_intr_map(OF_parent(node), (uint32_t *)&reg, intr)) {
		/*
		 * This can fail on some machines where the parent's
		 * node doesn't have any "interrupt-map" and friends.
		 *
		 * In this case just trust what we got in "interrupts".
		 */
	}

found:
	*ihp = intr[0];

	return (0);
}

int
pci_intr_map_msi(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
{
	return (-1);
}

int
pci_intr_line(pci_chipset_tag_t pc, pci_intr_handle_t ih)
{
	return (ih);
}

const char *
pci_intr_string(pci_chipset_tag_t pc, pci_intr_handle_t ih)
{
	static char str[16];

	snprintf(str, sizeof(str), "irq %ld", ih);

	return (str);
}

void *
pci_intr_establish(pci_chipset_tag_t pc, pci_intr_handle_t ih, int lvl,
    int (*func)(void *), void *arg, const char *what)
{
	return (*intr_establish_func)(pc, ih, IST_LEVEL, lvl, func, arg, what);
}

void
pci_intr_disestablish(pci_chipset_tag_t pc, void *cookie)
{
	(*intr_disestablish_func)(pc, cookie);
}

int
pci_ether_hw_addr(pci_chipset_tag_t pc, uint8_t *oaddr)
{
	uint8_t laddr[6];
	int node, len;

	node = OF_finddevice("enet");
	len = OF_getprop(node, "local-mac-address", laddr, sizeof(laddr));
	if (sizeof(laddr) == len) {
		memcpy(oaddr, laddr, sizeof(laddr));
		return (1);
	}

	oaddr[0] = oaddr[1] = oaddr[2] = 0xff;
	oaddr[3] = oaddr[4] = oaddr[5] = 0xff;

	return (0);
}

int
ofw_enumerate_pcibus(struct pci_softc *sc,
    int (*match)(struct pci_attach_args *), struct pci_attach_args *pap)
{
	pci_chipset_tag_t pc = sc->sc_pc;
	struct ofw_pci_register reg;
	int len, node, b, d, f, ret;
	char compat[32];
	pcireg_t bhlcr;
	pcitag_t tag;

	if (sc->sc_bridgetag)
		node = PCITAG_NODE(*sc->sc_bridgetag);
	else
		node = pc->pc_node;

	/* The AGP bridge is not in the device-tree. */
	len = OF_getprop(node, "compatible", compat, sizeof(compat));
	if (len > 0 && strcmp(compat, "u3-agp") == 0) {
		tag = PCITAG_CREATE(0, sc->sc_bus, 11, 0);
		ret = pci_probe_device(sc, tag, match, pap);
		if (match != NULL && ret != 0)
			return (ret);
	}

	for (node = OF_child(node); node; node = OF_peer(node)) {
		if (OF_getprop(node, "reg", &reg, sizeof(reg)) < sizeof(reg))
			continue;

		b = OFW_PCI_PHYS_HI_BUS(reg.phys_hi);
		d = OFW_PCI_PHYS_HI_DEVICE(reg.phys_hi);
		f = OFW_PCI_PHYS_HI_FUNCTION(reg.phys_hi);

		tag = PCITAG_CREATE(node, b, d, f);

		bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG);
		if (PCI_HDRTYPE_TYPE(bhlcr) > 2)
			continue;

		ret = pci_probe_device(sc, tag, match, pap);
		if (match != NULL && ret != 0)
			return (ret);
	}

	return (0);
}

int
ofw_intr_map(int node, uint32_t *addr, uint32_t *intr)
{
	uint32_t imap[144], mmask[8], *mp, *mp1;
	uint32_t acells, icells, mcells;
	int ilen, mlen, i, step = 0;
	int parent;

	ilen = OF_getprop(node, "interrupt-map", imap, sizeof(imap));
	mlen = OF_getprop(node, "interrupt-map-mask", mmask, sizeof(mmask));
	if (ilen < 0 || mlen < 0)
		return (-1);

	if ((OF_getprop(node, "#address-cells", &acells, 4) < 0) ||
	    (OF_getprop(node, "#interrupt-cells", &icells, 4) < 0))
		return (-1);

	mcells = acells + icells;
	if (mcells != (mlen / sizeof(mmask[0])))
		return (-1);

	for (i = 0; i < mcells; i++)
		addr[i] &= mmask[i];

	/* interrupt-map is formatted as follows
	 * int * #address-cells, int * #interrupt-cells, int, int, int
	 * eg
	 * address-cells = 3
	 * interrupt-cells = 1
	 * 00001000 00000000 00000000 00000000 ff911258 00000034 00000001
	 * 00001800 00000000 00000000 00000000 ff911258 00000035 00000001
	 * 00002000 00000000 00000000 00000000 ff911258 00000036 00000001
	 * | address cells          | | intr | |node| | irq  | |edge/level|
	 *                            | cells|        | interrupt cells   |
	 *                                            | of node           |
	 * or at least something close to that.
	 */
	for (mp = imap; ilen > mlen; mp += step) {
		mp1 = mp + mcells;
		parent = *mp1;

		if (bcmp(mp, addr, mlen) == 0) {
			char ic[20];

			/*
			 * If we have a match and the parent is not an
			 * interrupt controller continue recursively.
			 */
			if (OF_getprop(parent, "interrupt-controller", ic, 20))
				return ofw_intr_map(parent, &mp1[1], intr);

			*intr = mp1[1];
			return (0);
		}

		if (OF_getprop(parent, "#address-cells", &acells, 4) < 0)
			acells = 0;
		if (OF_getprop(parent, "#interrupt-cells", &icells, 4) < 0)
			break;

		step = mcells + 1 + acells + icells;
		ilen -= step * sizeof(imap[0]);
	}

	return (-1);
}
