code:
/*	$OpenBSD: octeon_pcibus.c,v 1.16 2014/08/11 19:00:50 miod Exp $	*/
/*	$NetBSD: bonito_mainbus.c,v 1.11 2008/04/28 20:23:10 martin Exp $	*/
/*	$NetBSD: bonito_pci.c,v 1.5 2008/04/28 20:23:28 martin Exp $	*/

/*
 * Copyright (c) 2009, 2010 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 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.
 */
/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * 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.
 */

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

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

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

#include <octeon/dev/iobusvar.h>
#include <octeon/dev/octeon_pcibus.h>
#include <octeon/dev/octeon_pcibusvar.h>

#include <uvm/uvm_extern.h>

#ifdef DEBUG
#define OCTEON_PCIDEBUG(p) printf p
#else
#define OCTEON_PCIDEBUG(p)
#endif

#define REG_READ32(addr)	(*(volatile uint32_t *)(addr))
#define REG_WRITE32(addr, data)	(*(volatile uint32_t *)(addr) = (uint32_t)(data))

int	octeon_pcibus_match(struct device *, void *, void *);
void	octeon_pcibus_attach(struct device *, struct device *, void *);
int	octeon_pcibus_intr_map(int dev, int fn, int pin);

const struct cfattach pcibus_ca = {
	sizeof(struct octeon_pcibus_softc),
	octeon_pcibus_match, octeon_pcibus_attach
};

struct cfdriver pcibus_cd = {
	NULL, "pcibus", DV_DULL
};

bus_addr_t  octeon_pcibus_pa_to_device(paddr_t);
paddr_t     octeon_pcibus_device_to_pa(bus_addr_t);
void        octeon_pcibus_attach_hook(struct device *, struct device *,
                                      struct pcibus_attach_args *);
int         octeon_pcibus_bus_maxdevs(void *, int);
pcitag_t    octeon_pcibus_make_tag(void *, int, int, int);
void        octeon_pcibus_decompose_tag(void *, pcitag_t, int *, int *, int *);
int	    octeon_pcibus_pci_conf_size(void *, pcitag_t);
pcireg_t    octeon_pcibus_pci_conf_read(void *, pcitag_t, int);
void        octeon_pcibus_pci_conf_write(void *, pcitag_t, int, pcireg_t);
int         octeon_pcibus_pci_intr_map(struct pci_attach_args *,
                                       pci_intr_handle_t *);
const char *octeon_pcibus_pci_intr_string(void *, pci_intr_handle_t);
void       *octeon_pcibus_pci_intr_establish(void *, pci_intr_handle_t, int,
                                             int (*)(void *), void *, char *);
void        octeon_pcibus_pci_intr_disestablish(void *, void *);

struct machine_bus_dma_tag octeon_pcibus_bus_dma_tag = {
	._cookie = NULL,
	._dmamap_create =	_dmamap_create,
	._dmamap_destroy =	_dmamap_destroy,
	._dmamap_load =		_dmamap_load,
	._dmamap_load_mbuf =	_dmamap_load_mbuf,
	._dmamap_load_uio =	_dmamap_load_uio,
	._dmamap_load_raw =	_dmamap_load_raw,
	._dmamap_load_buffer =	_dmamap_load_buffer,
	._dmamap_unload =	_dmamap_unload,
	._dmamap_sync =		_dmamap_sync,
	._dmamem_alloc =	_dmamem_alloc,
	._dmamem_free =		_dmamem_free,
	._dmamem_map =		_dmamem_map,
	._dmamem_unmap =	_dmamem_unmap,
	._dmamem_mmap =		_dmamem_mmap,
	._pa_to_device =	octeon_pcibus_pa_to_device,
	._device_to_pa =	octeon_pcibus_device_to_pa
};

int octeon_pcibus_io_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
    bus_space_handle_t *);
int octeon_pcibus_mem_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
    bus_space_handle_t *);

#define _OCTEON_PCIBUS_PCIIO_BASE	0x00001000
#define _OCTEON_PCIBUS_PCIIO_SIZE	0x08000000
#define _OCTEON_PCIBUS_PCIMEM_BASE	0x80000000
#define _OCTEON_PCIBUS_PCIMEM_SIZE	0x40000000

struct mips_bus_space octeon_pcibus_pci_io_space_tag = {
	.bus_base = PHYS_TO_XKPHYS(_OCTEON_PCIBUS_PCIIO_BASE, CCA_NC),
	.bus_private = NULL,
	._space_read_1 =	generic_space_read_1,
	._space_write_1 =	generic_space_write_1,
	._space_read_2 =	generic_space_read_2,
	._space_write_2 =	generic_space_write_2,
	._space_read_4 =	generic_space_read_4,
	._space_write_4 =	generic_space_write_4,
	._space_read_8 =	generic_space_read_8,
	._space_write_8 =	generic_space_write_8,
	._space_read_raw_2 =	generic_space_read_raw_2,
	._space_write_raw_2 =	generic_space_write_raw_2,
	._space_read_raw_4 =	generic_space_read_raw_4,
	._space_write_raw_4 =	generic_space_write_raw_4,
	._space_read_raw_8 =	generic_space_read_raw_8,
	._space_write_raw_8 =	generic_space_write_raw_8,
	._space_map =		octeon_pcibus_io_map,
	._space_unmap =		generic_space_unmap,
	._space_subregion =	generic_space_region,
	._space_vaddr =		generic_space_vaddr
};

struct mips_bus_space octeon_pcibus_pci_mem_space_tag = {
	.bus_base = PHYS_TO_XKPHYS(_OCTEON_PCIBUS_PCIMEM_BASE, CCA_NC),
	.bus_private = NULL,
	._space_read_1 =	generic_space_read_1,
	._space_write_1 =	generic_space_write_1,
	._space_read_2 =	generic_space_read_2,
	._space_write_2 =	generic_space_write_2,
	._space_read_4 =	generic_space_read_4,
	._space_write_4 =	generic_space_write_4,
	._space_read_8 =	generic_space_read_8,
	._space_write_8 =	generic_space_write_8,
	._space_read_raw_2 =	generic_space_read_raw_2,
	._space_write_raw_2 =	generic_space_write_raw_2,
	._space_read_raw_4 =	generic_space_read_raw_4,
	._space_write_raw_4 =	generic_space_write_raw_4,
	._space_read_raw_8 =	generic_space_read_raw_8,
	._space_write_raw_8 =	generic_space_write_raw_8,
	._space_map =		octeon_pcibus_mem_map,
	._space_unmap =		generic_space_unmap,
	._space_subregion =	generic_space_region,
	._space_vaddr =		generic_space_vaddr
};

int
octeon_pcibus_match(struct device *parent, void *vcf, void *aux)
{
	struct iobus_attach_args *aa = aux;

	if ((octeon_boot_info->config_flags & BOOTINFO_CFG_FLAG_PCI_HOST) == 0) {
		OCTEON_PCIDEBUG(("%s, no PCI host function detected.\n", __func__));
		return 0;
	}

	if (strcmp(aa->aa_name, pcibus_cd.cd_name) == 0)
		return 1;

	return 0;
}

void
octeon_pcibus_attach(struct device *parent, struct device *self, void *aux)
{
	struct octeon_pcibus_softc *sc;
	struct pcibus_attach_args pba;

	sc = (struct octeon_pcibus_softc *)self;
	sc->sc_aa = aux;

	printf("\n");

	/*
	 * Attach PCI bus.
	 */
	sc->sc_pc.pc_attach_hook = octeon_pcibus_attach_hook;
	sc->sc_pc.pc_bus_maxdevs = octeon_pcibus_bus_maxdevs;
	sc->sc_pc.pc_make_tag = octeon_pcibus_make_tag;
	sc->sc_pc.pc_decompose_tag = octeon_pcibus_decompose_tag;

	sc->sc_pc.pc_conf_v = sc;
	sc->sc_pc.pc_conf_size = octeon_pcibus_pci_conf_size;
	sc->sc_pc.pc_conf_read = octeon_pcibus_pci_conf_read;
	sc->sc_pc.pc_conf_write = octeon_pcibus_pci_conf_write;

	sc->sc_pc.pc_intr_v = sc;
	sc->sc_pc.pc_intr_map = octeon_pcibus_pci_intr_map;
	sc->sc_pc.pc_intr_string = octeon_pcibus_pci_intr_string;
	sc->sc_pc.pc_intr_establish = octeon_pcibus_pci_intr_establish;
	sc->sc_pc.pc_intr_disestablish = octeon_pcibus_pci_intr_disestablish;

	bzero(&pba, sizeof pba);
	pba.pba_busname = "pci";
	pba.pba_iot = &octeon_pcibus_pci_io_space_tag;
	pba.pba_memt = &octeon_pcibus_pci_mem_space_tag;
	pba.pba_dmat = &octeon_pcibus_bus_dma_tag;
	pba.pba_pc = &sc->sc_pc;
	pba.pba_domain = pci_ndomains++;
	pba.pba_bus = 0;
	pba.pba_ioex = octeon_pcibus_get_resource_extent(&sc->sc_pc, 1);
	pba.pba_memex = octeon_pcibus_get_resource_extent(&sc->sc_pc, 0);

	config_found(&sc->sc_dev, &pba, octeon_pcibus_print);
}

bus_addr_t
octeon_pcibus_pa_to_device(paddr_t pa)
{
	OCTEON_PCIDEBUG(("%s:%d: pa=%p\n", __func__, __LINE__, (void *)pa));

	return pa & 0x1ffffffffffffUL;
}

paddr_t
octeon_pcibus_device_to_pa(bus_addr_t addr)
{
	OCTEON_PCIDEBUG(("%s:%d: addr=%lx\n", __func__, __LINE__, addr));

	return PHYS_TO_XKPHYS(addr, CCA_NC);
}

int
octeon_pcibus_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;
}

/*
 * various PCI helpers
 */
void
octeon_pcibus_attach_hook(struct device *parent, struct device *self,
    struct pcibus_attach_args *pba)
{
}

/*
 * PCI configuration space access routines
 */
int
octeon_pcibus_bus_maxdevs(void *v, int busno)
{
	return (32);
}

pcitag_t
octeon_pcibus_make_tag(void *unused, int b, int d, int f)
{
	return (b << 16) | (d << 11) | (f << 8);
}

void
octeon_pcibus_decompose_tag(void *unused, pcitag_t tag, int *bp, int *dp, int *fp)
{
	if (bp != NULL)
		*bp = (tag >> 16) & 0xff;
	if (dp != NULL)
		*dp = (tag >> 11) & 0x1f;
	if (fp != NULL)
		*fp = (tag >> 8) & 0x7;
}

int
octeon_pcibus_pci_conf_size(void *v, pcitag_t tag)
{
	return PCI_CONFIG_SPACE_SIZE;
}

pcireg_t
octeon_pcibus_pci_conf_read(void *v, pcitag_t tag, int offset)
{
	pcireg_t data;
	uint64_t cfgoff;

	if (tag == 0){
		if (offset & 0x4){
			cfgoff = OCTEON_PCI_CFG1 + (offset & 0xfff8);
		} else {
			cfgoff = OCTEON_PCI_CFG0 + (offset & 0xfff8);
		}
	} else {
		cfgoff = tag + offset;
		if (offset & 0x4) {
			cfgoff = OCTEON_PCI_CONFIG_BASE1 + (cfgoff & 0xfffffff8);
		} else {
			cfgoff = OCTEON_PCI_CONFIG_BASE0 + (cfgoff & 0xfffffff8);
		}
	}

	data = REG_READ32(cfgoff);
	return data;
}

void
octeon_pcibus_pci_conf_write(void *v, pcitag_t tag, int offset, pcireg_t data)
{
	uint64_t cfgoff;

	if (tag == 0){
		if (offset & 0x4){
			cfgoff = OCTEON_PCI_CFG1 + (offset & 0xfff8);
		} else {
			cfgoff = OCTEON_PCI_CFG0 + (offset & 0xfff8);
		}
	} else {
		cfgoff = tag + offset;
		if (offset & 0x4){
			cfgoff = OCTEON_PCI_CONFIG_BASE1 + (cfgoff & 0xfffffff8);
		} else {
			cfgoff = OCTEON_PCI_CONFIG_BASE0 + (cfgoff & 0xfffffff8);
		}
	}

	REG_WRITE32(cfgoff, data);
}

/*
 * PCI Interrupt handling
 */
int
octeon_pcibus_pci_intr_map(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
{
#if 0
	struct octeon_pcibus_softc *sc = pa->pa_pc->pc_intr_v;
#endif
	int bus, dev, fn, pin;

	*ihp = (pci_intr_handle_t)-1;

	if (pa->pa_intrpin == 0)	/* no interrupt needed */
		return 1;

#ifdef DIAGNOSTIC
	if (pa->pa_intrpin > 4) {
		printf("%s: bad interrupt pin %d\n", __func__, pa->pa_intrpin);
		return 1;
	}
#endif

	pci_decompose_tag(pa->pa_pc, pa->pa_tag, &bus, &dev, &fn);
	if (pa->pa_bridgetag) {
		pin = PPB_INTERRUPT_SWIZZLE(pa->pa_rawintrpin, dev);
		*ihp = pa->pa_bridgeih[pin - 1];
	} else {
		if (bus == 0)
			*ihp = octeon_pcibus_intr_map(dev, fn, pa->pa_intrpin);

		if (*ihp == (pci_intr_handle_t)-1)
			return 1;
	}

	return 0;
}

const char *
octeon_pcibus_pci_intr_string(void *cookie, pci_intr_handle_t ih)
{
	static char irqstr[sizeof("irq 0123456789")];

	snprintf(irqstr, sizeof irqstr, "irq %ld", ih);
	return irqstr;
}

void *
octeon_pcibus_pci_intr_establish(void *cookie, pci_intr_handle_t ih, int level,
    int (*cb)(void *), void *cbarg, char *name)
{
	return octeon_intr_establish(ih, level, cb, cbarg, name);
}

void
octeon_pcibus_pci_intr_disestablish(void *cookie, void *ihp)
{
	struct octeon_pcibus_softc *sc;
	struct iobus_attach_args *aa;

	sc = (struct octeon_pcibus_softc *)cookie;
	aa = sc->sc_aa;

// XXX: this cause panic...
//	iobus_intr_disestablish(ihp);
}

/*
 * bus_space mapping routines.
 */
int
octeon_pcibus_io_map(bus_space_tag_t t, bus_addr_t offs, bus_size_t size, int flags,
    bus_space_handle_t *bshp)
{
	if (ISSET(flags, BUS_SPACE_MAP_CACHEABLE)) {
		offs +=
		    PHYS_TO_XKPHYS(0, CCA_CACHED) - PHYS_TO_XKPHYS(0, CCA_NC);
	}
	*bshp = t->bus_base + offs;
	return 0;
}

int
octeon_pcibus_mem_map(bus_space_tag_t t, bus_addr_t offs, bus_size_t size, int flags,
    bus_space_handle_t *bshp)
{
	if (ISSET(flags, BUS_SPACE_MAP_CACHEABLE)) {
		offs +=
		    PHYS_TO_XKPHYS(0, CCA_CACHED) - PHYS_TO_XKPHYS(0, CCA_NC);
	}
	*bshp = t->bus_base + offs;
	return 0;
}

/*
 * PCI resource handling
 */
struct extent *
octeon_pcibus_get_resource_extent(pci_chipset_tag_t pc, int io)
{
	struct octeon_pcibus_softc *sc = pc->pc_conf_v;
	struct extent *ex;
	char *exname;
	int exnamesz;
	int errors;

	exnamesz = 1 + 16 + 4;
	exname = (char *)malloc(exnamesz, M_DEVBUF, M_NOWAIT);
	if (exname == NULL)
		return NULL;
	snprintf(exname, exnamesz, "%s%s", sc->sc_dev.dv_xname,
	    io ? "_io" : "_mem");

	ex = extent_create(exname, 0, 0xffffffffffffffff, M_DEVBUF, NULL, 0,
	    EX_NOWAIT | EX_FILLED);
	if (ex == NULL)
		goto out;

	exname = NULL;
	errors = 0;
	if (io) {
		if (extent_free(ex, _OCTEON_PCIBUS_PCIIO_BASE, _OCTEON_PCIBUS_PCIIO_SIZE,
		    EX_NOWAIT) != 0)
			errors++;
	} else {
		if (extent_free(ex, _OCTEON_PCIBUS_PCIMEM_BASE, _OCTEON_PCIBUS_PCIMEM_SIZE,
		    EX_NOWAIT) != 0)
			errors++;
	}

	if (errors != 0) {
		extent_destroy(ex);
		ex = NULL;
	}

#ifdef DEBUG
	extent_print(ex);
#endif

out:
	if (exname != NULL)
		free(exname, M_DEVBUF, 0);

	return ex;
}

/*
 * PCI model specific routines
 */

int
octeon_pcibus_intr_map(int dev, int fn, int pin)
{
	return CIU_INT_PCI_INTA + ((pin - 1) & 3);
}


code:
/*	$OpenBSD: octeon_pcibus.h,v 1.3 2015/07/20 01:38:31 jasper Exp $	*/

/*
 * Copyright (c) 2003-2004 Opsycon AB (www.opsycon.com).
 *
 * 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.
 *
 */

#ifndef ___OCTEON_PCIBUS_H__
#define ___OCTEON_PCIBUS_H__

#include <machine/octeonreg.h>

#define OCTEON_PCIBUS_PCIIO_BASE	0x11A0000000000ULL

#define OCTEON_PCI_CFG0			0x80011F0000001804ULL
#define OCTEON_PCI_CFG1			0x80011F0000001800ULL
#define OCTEON_PCI_CONFIG_BASE0		0x8001190000000004ULL
#define OCTEON_PCI_CONFIG_BASE1		0x8001190000000000ULL

#define OCTEON_PCI_CFG2			(OCTEON_PCI_CFG0 + 0x08UL)
#define OCTEON_PCI_CFG3			(OCTEON_PCI_CFG1 + 0x08UL)
#define OCTEON_PCI_CFG4			(OCTEON_PCI_CFG0 + 0x10UL)
#define OCTEON_PCI_CFG5			(OCTEON_PCI_CFG1 + 0x10UL)
#define OCTEON_PCI_CFG6			(OCTEON_PCI_CFG0 + 0x18UL)
#define OCTEON_PCI_CFG7			(OCTEON_PCI_CFG1 + 0x18UL)
#define OCTEON_PCI_CFG8			(OCTEON_PCI_CFG0 + 0x20UL)
#define OCTEON_PCI_CFG9			(OCTEON_PCI_CFG1 + 0x20UL)
#define OCTEON_PCI_CFG10		(OCTEON_PCI_CFG0 + 0x28UL)
#define OCTEON_PCI_CFG11		(OCTEON_PCI_CFG1 + 0x28UL)
#define OCTEON_PCI_CFG12		(OCTEON_PCI_CFG0 + 0x30UL)
#define OCTEON_PCI_CFG13		(OCTEON_PCI_CFG1 + 0x30UL)
#define OCTEON_PCI_CFG14		(OCTEON_PCI_CFG0 + 0x38UL)
#define OCTEON_PCI_CFG15		(OCTEON_PCI_CFG1 + 0x38UL)
#define OCTEON_PCI_CFG16		(OCTEON_PCI_CFG0 + 0x40UL)
#define OCTEON_PCI_CFG17		(OCTEON_PCI_CFG1 + 0x40UL)
#define OCTEON_PCI_CFG18		(OCTEON_PCI_CFG0 + 0x48UL)
#define OCTEON_PCI_CFG19		(OCTEON_PCI_CFG1 + 0x48UL)
#define OCTEON_PCI_CFG20		(OCTEON_PCI_CFG0 + 0x50UL)
#define OCTEON_PCI_CFG21		(OCTEON_PCI_CFG1 + 0x50UL)
#define OCTEON_PCI_CFG22		(OCTEON_PCI_CFG0 + 0x58UL)
#define OCTEON_PCI_CFG23		(OCTEON_PCI_CFG1 + 0x58UL)
#define OCTEON_PCI_CFG24		(OCTEON_PCI_CFG0 + 0x60UL)
#define OCTEON_PCI_CFG25		(OCTEON_PCI_CFG1 + 0x60UL)
#define OCTEON_PCI_CFG26		(OCTEON_PCI_CFG0 + 0x68UL)
#define OCTEON_PCI_CFG27		(OCTEON_PCI_CFG1 + 0x68UL)
#define OCTEON_PCI_CFG28		(OCTEON_PCI_CFG0 + 0x70UL)
#define OCTEON_PCI_CFG29		(OCTEON_PCI_CFG1 + 0x70UL)
#define OCTEON_PCI_CFG30		(OCTEON_PCI_CFG0 + 0x78UL)
#define OCTEON_PCI_CFG31		(OCTEON_PCI_CFG1 + 0x78UL)
#define OCTEON_PCI_CFG32		(OCTEON_PCI_CFG0 + 0x80UL)
#define OCTEON_PCI_CFG33		(OCTEON_PCI_CFG1 + 0x80UL)
#define OCTEON_PCI_CFG34		(OCTEON_PCI_CFG0 + 0x88UL)
#define OCTEON_PCI_CFG35		(OCTEON_PCI_CFG1 + 0x88UL)
#define OCTEON_PCI_CFG36		(OCTEON_PCI_CFG0 + 0x90UL)
#define OCTEON_PCI_CFG37		(OCTEON_PCI_CFG1 + 0x90UL)
#define OCTEON_PCI_CFG38		(OCTEON_PCI_CFG0 + 0x98UL)
#define OCTEON_PCI_CFG39		(OCTEON_PCI_CFG1 + 0x98UL)
#define OCTEON_PCI_CFG40		(OCTEON_PCI_CFG0 + 0xA0UL)
#define OCTEON_PCI_CFG41		(OCTEON_PCI_CFG1 + 0xA0UL)
#define OCTEON_PCI_CFG42		(OCTEON_PCI_CFG0 + 0xA8UL)
#define OCTEON_PCI_CFG43		(OCTEON_PCI_CFG1 + 0xA8UL)
#define OCTEON_PCI_CFG44		(OCTEON_PCI_CFG0 + 0xB0UL)
#define OCTEON_PCI_CFG45		(OCTEON_PCI_CFG1 + 0xB0UL)
#define OCTEON_PCI_CFG46		(OCTEON_PCI_CFG0 + 0xB8UL)
#define OCTEON_PCI_CFG47		(OCTEON_PCI_CFG1 + 0xB8UL)
#define OCTEON_PCI_CFG48		(OCTEON_PCI_CFG0 + 0xC0UL)
#define OCTEON_PCI_CFG49		(OCTEON_PCI_CFG1 + 0xC0UL)
#define OCTEON_PCI_CFG50		(OCTEON_PCI_CFG0 + 0xC8UL)
#define OCTEON_PCI_CFG51		(OCTEON_PCI_CFG1 + 0xC8UL)
#define OCTEON_PCI_CFG52		(OCTEON_PCI_CFG0 + 0xD0UL)
#define OCTEON_PCI_CFG53		(OCTEON_PCI_CFG1 + 0xD0UL)
#define OCTEON_PCI_CFG54		(OCTEON_PCI_CFG0 + 0xD8UL)
#define OCTEON_PCI_CFG55		(OCTEON_PCI_CFG1 + 0xD8UL)
#define OCTEON_PCI_CFG56		(OCTEON_PCI_CFG0 + 0xE0UL)
#define OCTEON_PCI_CFG57		(OCTEON_PCI_CFG1 + 0xE0UL)
#define OCTEON_PCI_CFG58		(OCTEON_PCI_CFG0 + 0xE8UL)
#define OCTEON_PCI_CFG59		(OCTEON_PCI_CFG1 + 0xE8UL)
#define OCTEON_PCI_CFG60		(OCTEON_PCI_CFG0 + 0xF0UL)
#define OCTEON_PCI_CFG61		(OCTEON_PCI_CFG1 + 0xF0UL)
#define OCTEON_PCI_CFG62		(OCTEON_PCI_CFG0 + 0xF8UL)
#define OCTEON_PCI_CFG63		(OCTEON_PCI_CFG1 + 0xF8UL)

#define OCTEON_PCIBUS_PCIMAPCFG_TYPE1	0x00010000

#endif /* ___OCTEON_PCIBUS_H__ */

license: unknown


code:
/*	$OpenBSD: octeon_pcibus_irq.h,v 1.1 2010/10/28 22:52:10 syuu Exp $	*/

/*
 * Copyright (c) 2009, 2010 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 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.
 */

/*
 * Octeon_Pcibus interrupt assignments
 */

#define	OCTEON_PCIBUS_INTRMASK_MBOX		0x0000000f
#define	OCTEON_PCIBUS_INTR_MBOX		0
#define	OCTEON_PCIBUS_INTRMASK_DMARDY		0x00000010
#define	OCTEON_PCIBUS_INTRMASK_DMAEMPTY	0x00000020
#define	OCTEON_PCIBUS_INTRMASK_COPYRDY		0x00000040
#define	OCTEON_PCIBUS_INTRMASK_COPYEMPTY	0x00000080
#define	OCTEON_PCIBUS_INTRMASK_COPYERR		0x00000100
#define	OCTEON_PCIBUS_INTRMASK_PCIIRQ		0x00000200
#define	OCTEON_PCIBUS_INTRMASK_MASTERERR	0x00000400
#define	OCTEON_PCIBUS_INTRMASK_SYSTEMERR	0x00000800
#define	OCTEON_PCIBUS_INTRMASK_DRAMPERR	0x00001000
#define	OCTEON_PCIBUS_INTRMASK_RETRYERR	0x00002000
#define	OCTEON_PCIBUS_INTRMASK_GPIO		0x01ff0000
#define	OCTEON_PCIBUS_INTR_GPIO		16
#define	OCTEON_PCIBUS_INTRMASK_GPIN		0x7e000000
#define	OCTEON_PCIBUS_INTR_GPIN		25

/*
 * Octeon_Pcibus interrupt handling recipes:
 */

#define	INTPRI_OCTEON_PCIBUS		(INTPRI_CLOCK + 1)
#define	INTPRI_ISA		(INTPRI_OCTEON_PCIBUS + 1)

#define	OCTEON_PCIBUS_NDIRECT		32
#define	OCTEON_PCIBUS_NISA		16
#define	OCTEON_PCIBUS_NINTS		(OCTEON_PCIBUS_NDIRECT + OCTEON_PCIBUS_NISA)
#define	OCTEON_PCIBUS_ISA_IRQ(i)	((i) + OCTEON_PCIBUS_NDIRECT)
#define	OCTEON_PCIBUS_DIRECT_IRQ(i)	(i)
#define	OCTEON_PCIBUS_IRQ_IS_ISA(i)	((i) >= OCTEON_PCIBUS_NDIRECT)
#define	OCTEON_PCIBUS_IRQ_TO_ISA(i)	((i) - OCTEON_PCIBUS_NDIRECT)

#define	OCTEON_PCIBUS_DIRECT_MASK(imask)	((imask) & ((1L << OCTEON_PCIBUS_NDIRECT) - 1))
#define	OCTEON_PCIBUS_ISA_MASK(imask)		((imask) >> OCTEON_PCIBUS_NDIRECT)

extern struct intrhand *octeon_pcibus_intrhand[OCTEON_PCIBUS_NINTS];
extern uint64_t octeon_pcibus_intem;
extern uint64_t octeon_pcibus_imask[NIPLS];


code:
/*	$OpenBSD: octeon_pcibusvar.h,v 1.3 2011/05/08 13:24:55 syuu Exp $	*/
/*	$NetBSD: octeon_pcibusvar.h,v 1.4 2008/04/28 20:23:28 martin Exp $	*/

/*-
 * Copyright (c) 2001, 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * 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.
 */

#ifndef _OCTEON_PCIBUSVAR_H_
#define	_OCTEON_PCIBUSVAR_H_

struct extent;

struct octeon_pcibus_softc {
	struct device sc_dev;
	struct mips_pci_chipset sc_pc;
	struct iobus_attach_args *sc_aa;
};

#ifdef _KERNEL
void	 octeon_pcibus_intr_disestablish(void *);
void	*octeon_pcibus_intr_establish(int, int, int, int (*)(void *), void *,
	    const char *);
int	 octeon_pcibus_print(void *, const char *);
struct extent
	*octeon_pcibus_get_resource_extent(pci_chipset_tag_t, int);
void	 octeon_pcibus_setintrmask(int);
#endif /* _KERNEL */

#endif /* _OCTEON_PCIBUSVAR_H_ */


code:
/*	$OpenBSD: octeon_uartbus.c,v 1.6 2015/05/23 12:08:14 jsg Exp $ */

/*
 * Copyright (c) 2000-2004 Opsycon AB  (www.opsycon.se)
 *
 * 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.
 *
 */

/*
 * This is a uartbus, for OCTEON UART.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/atomic.h>

#include <machine/autoconf.h>
#include <machine/intr.h>
#include <machine/octeonreg.h>

#include <octeon/dev/uartbusvar.h>

#include <dev/ic/comreg.h>
#include <dev/ic/ns16550reg.h>
#define	com_lcr	com_cfcr

int	 uartbusmatch(struct device *, void *, void *);
void	 uartbusattach(struct device *, struct device *, void *);
int	 uartbusprint(void *, const char *);
int	 uartbussubmatch(struct device *, void *, void *);

u_int8_t uartbus_read_1(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int16_t uartbus_read_2(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int32_t uartbus_read_4(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int64_t uartbus_read_8(bus_space_tag_t, bus_space_handle_t, bus_size_t);

void	 uartbus_write_1(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int8_t);
void	 uartbus_write_2(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int16_t);
void	 uartbus_write_4(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int32_t);
void	 uartbus_write_8(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int64_t);

void	 uartbus_read_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 uartbus_write_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 uartbus_read_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 uartbus_write_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 uartbus_read_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 uartbus_write_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);

int	 uartbus_space_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
	    bus_space_handle_t *);
void	 uartbus_space_unmap(bus_space_tag_t, bus_space_handle_t, bus_size_t);
int	 uartbus_space_region(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    bus_size_t, bus_space_handle_t *);

void	*uartbus_space_vaddr(bus_space_tag_t, bus_space_handle_t);

bus_addr_t uartbus_pa_to_device(paddr_t);
paddr_t	 uartbus_device_to_pa(bus_addr_t);

bus_size_t uartbus_get_read_reg(bus_size_t);
bus_size_t uartbus_get_write_reg(bus_size_t, u_int8_t);

static int lcr = 0;

struct cfattach uartbus_ca = {
	sizeof(struct device), uartbusmatch, uartbusattach
};

struct cfdriver uartbus_cd = {
	NULL, "uartbus", DV_DULL
};

bus_space_t uartbus_tag = {
	PHYS_TO_XKPHYS(0, CCA_NC),
	NULL,
	uartbus_read_1, uartbus_write_1,
	uartbus_read_2, uartbus_write_2,
	uartbus_read_4, uartbus_write_4,
	uartbus_read_8, uartbus_write_8,
	uartbus_read_raw_2, uartbus_write_raw_2,
	uartbus_read_raw_4, uartbus_write_raw_4,
	uartbus_read_raw_8, uartbus_write_raw_8,
	uartbus_space_map, uartbus_space_unmap, uartbus_space_region,
	uartbus_space_vaddr
};

/*
 * List of uartbus child devices.
 */

#define	UARTBUSDEV(name, addr, i) \
	{ name, &uartbus_tag, addr, i }
struct uartbus_attach_args uartbus_children[] = {
	UARTBUSDEV("com", OCTEON_UART0_BASE, CIU_INT_UART0),
	UARTBUSDEV("com", OCTEON_UART1_BASE, CIU_INT_UART1),
};
#undef	UARTBUSDEV

/*
 * Match bus only to targets which have this bus.
 */
int
uartbusmatch(struct device *parent, void *match, void *aux)
{
	return (1);
}

int
uartbusprint(void *aux, const char *uartbus)
{
	struct uartbus_attach_args *uba = aux;

	if (uartbus != NULL)
		printf("%s at %s", uba->uba_name, uartbus);

	if (uba->uba_baseaddr != 0)
		printf(" base 0x%lx", uba->uba_baseaddr);
	if (uba->uba_intr >= 0)
		printf(" irq %d", uba->uba_intr);

	return (UNCONF);
}

int
uartbussubmatch(struct device *parent, void *vcf, void *args)
{
	struct cfdata *cf = vcf;
	struct uartbus_attach_args *uba = args;

	if (strcmp(cf->cf_driver->cd_name, uba->uba_name) != 0)
		return 0;

	if (cf->cf_loc[0] != -1 && cf->cf_loc[0] != (int)uba->uba_baseaddr)
		return 0;

	return (*cf->cf_attach->ca_match)(parent, cf, uba);
}

void
uartbusattach(struct device *parent, struct device *self, void *aux)
{
	uint i;

	printf("\n");

	/*
	 * Attach subdevices.
	 */
	for (i = 0; i < nitems(uartbus_children); i++)
		config_found_sm(self, uartbus_children + i,
		    uartbusprint, uartbussubmatch);
}

/*
 * Bus access primitives. These are really ugly...
 */

u_int8_t
uartbus_read_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	o = uartbus_get_read_reg(o);
	return (u_int8_t)(volatile uint64_t)*(volatile uint64_t *)(h + o);
}

u_int16_t
uartbus_read_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	panic(__func__);
}

u_int32_t
uartbus_read_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	panic(__func__);
}

u_int64_t
uartbus_read_8(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o)
{
	panic(__func__);
}

void
uartbus_write_1(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, u_int8_t v)
{
	o = uartbus_get_write_reg(o, 1);
	*(volatile uint64_t *)(h + o) = (volatile uint64_t)v;
}

void
uartbus_write_2(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, u_int16_t v)
{
	panic(__func__);
}

void
uartbus_write_4(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, u_int32_t v)
{
	panic(__func__);
}

void
uartbus_write_8(bus_space_tag_t t, bus_space_handle_t h, bus_size_t o, u_int64_t v)
{
	panic(__func__);
}

void
uartbus_read_raw_2(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

void
uartbus_write_raw_2(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

void
uartbus_read_raw_4(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

void
uartbus_write_raw_4(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

void
uartbus_read_raw_8(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

void
uartbus_write_raw_8(bus_space_tag_t t, bus_space_handle_t h, bus_addr_t o,
    const u_int8_t *buf, bus_size_t len)
{
	panic(__func__);
}

int
uartbus_space_map(bus_space_tag_t t, bus_addr_t offs, bus_size_t size,
    int flags, bus_space_handle_t *bshp)
{
	*bshp = t->bus_base + offs;

	return 0;
}

void
uartbus_space_unmap(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t size)
{
}

int
uartbus_space_region(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, bus_size_t size, bus_space_handle_t *nbshp)
{
	*nbshp = bsh + offset;
	return (0);
}

void *
uartbus_space_vaddr(bus_space_tag_t t, bus_space_handle_t h)
{
	return (void *)h;
}

/*
 * uartbus bus_dma helpers.
 */

bus_addr_t
uartbus_pa_to_device(paddr_t pa)
{
	return (bus_addr_t)pa;
}

paddr_t
uartbus_device_to_pa(bus_addr_t addr)
{
	return (paddr_t)addr;
}

bus_size_t
uartbus_get_read_reg(bus_size_t o)
{
	if (lcr & LCR_DLAB)
		switch(o) {
			case com_dlbl:
				return (bus_size_t)0x80;
			case com_dlbh:
				return (bus_size_t)0x88;
		}

	return (bus_size_t)(o << 3);
}

bus_size_t
uartbus_get_write_reg(bus_size_t o, u_int8_t v)
{
	if (o == com_lcr)
		lcr = v;

	switch(o) {
		case com_data:
			return (bus_size_t)0x40;
		case com_fifo:
			return (bus_size_t)0x50;
	}

	if (lcr & LCR_DLAB)
		switch(o) {
			case com_dlbl:
				return (bus_size_t)0x80;
			case com_dlbh:
				return (bus_size_t)0x88;
		}

	return (bus_size_t)(o << 3);
}


code:
/*	$OpenBSD: octhci.c,v 1.17 2015/02/11 01:40:57 uebayasi Exp $	*/

/*
 * Copyright (c) 2014 Paul Irofti <pirofti@openbsd.org>
 *
 * Permission to use, copy, modify, and/or 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/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/pool.h>

#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/octeonreg.h>
#include <machine/octeonvar.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usb_mem.h>

#include <octeon/dev/iobusvar.h>
#include <octeon/dev/octhcireg.h>

#ifdef OCTHCI_DEBUG
#define DPRINTF(x)	do { if (octhcidebug) printf x; } while(0)
#define DPRINTFN(n,x)	do { if (octhcidebug>(n)) printf x; } while (0)
int octhcidebug = 3;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

#define DEVNAME(sc)		((sc)->sc_bus.bdev.dv_xname)

struct octhci_softc {
	struct usbd_bus sc_bus;		/* base device */

	void *sc_ih;			/* interrupt handler */

	bus_space_tag_t sc_bust;	/* iobus space */
	bus_space_handle_t sc_regn;	/* usbn register space */
	bus_space_handle_t sc_regc;	/* usbc register space */
        bus_space_handle_t sc_dma_reg;	/* dma register space */

	int sc_noport;			/* Maximum number of ports */

	u_int8_t sc_conf;		/* Device configuration */
	struct usbd_xfer *sc_intrxfer;	/* Root HUB interrupt xfer */

	char sc_vendor[16];		/* Vendor string for root hub */
	int sc_id_vendor;		/* Vendor ID for root hub */

	int sc_port_connect;		/* Device connected to port */
	int sc_port_change;		/* Connect status changed */
	int sc_port_reset;		/* Port reset finished */
};

struct pool *octhcixfer;

struct octhci_xfer {
	struct usbd_xfer	 xfer;
};

int octhci_match(struct device *, void *, void *);
void octhci_attach(struct device *, struct device *, void *);

int octhci_init(struct octhci_softc *);
void octhci_init_core(struct octhci_softc *);
void octhci_init_host(struct octhci_softc *);
int octhci_intr(void *);

int octhci_intr1(struct octhci_softc *);
int octhci_intr_host_port(struct octhci_softc *);
int octhci_intr_host_chan(struct octhci_softc *);
int octhci_intr_host_chan_n(struct octhci_softc *, int);

const struct cfattach octhci_ca = {
	sizeof(struct octhci_softc), octhci_match, octhci_attach,
};

struct cfdriver octhci_cd = {
	NULL, "octhci", DV_DULL
};

inline void octhci_regn_set(struct octhci_softc *, bus_size_t, uint64_t);
inline void octhci_regn_clear(struct octhci_softc *, bus_size_t, uint64_t);

inline void octhci_regc_set(struct octhci_softc *, bus_size_t, uint32_t);
inline void octhci_regc_clear(struct octhci_softc *, bus_size_t, uint32_t);
inline uint32_t octhci_regc_read(struct octhci_softc *, bus_size_t);
inline void octhci_regc_write(struct octhci_softc *, bus_size_t, uint32_t);

struct octhci_soft_td {
	struct octhci_soft_td *next;
	struct octhci_soft_td *prev;
	struct octhci_soft_td *std;
	octhci_physaddr_t physaddr;
	struct usb_dma dma;             /* TD's DMA infos */
	int offs;                       /* TD's offset in struct usb_dma */
	int islot;
};

usbd_status octhci_open(struct usbd_pipe *pipe);
void octhci_softintr(void *);
void octhci_poll(struct usbd_bus *);
struct usbd_xfer * octhci_allocx(struct usbd_bus *);
void octhci_freex(struct usbd_bus *, struct usbd_xfer *);

struct usbd_bus_methods octhci_bus_methods = {
	.open_pipe = octhci_open,
	.soft_intr = octhci_softintr,
	.do_poll = octhci_poll,
	.allocx = octhci_allocx,
	.freex = octhci_freex,
};

#define OCTHCI_INTR_ENDPT 1
usbd_status octhci_root_ctrl_transfer(struct usbd_xfer *xfer);
usbd_status octhci_root_ctrl_start(struct usbd_xfer *xfer);
void octhci_root_ctrl_abort(struct usbd_xfer *xfer);
void octhci_root_ctrl_close(struct usbd_pipe *pipe);
void octhci_root_ctrl_done(struct usbd_xfer *xfer);

struct usbd_pipe_methods octhci_root_ctrl_methods = {
	.transfer = octhci_root_ctrl_transfer,
	.start = octhci_root_ctrl_start,
	.abort = octhci_root_ctrl_abort,
	.close = octhci_root_ctrl_close,
	.done = octhci_root_ctrl_done,
};

usbd_status octhci_root_intr_transfer(struct usbd_xfer *xfer);
usbd_status octhci_root_intr_start(struct usbd_xfer *xfer);
void octhci_root_intr_abort(struct usbd_xfer *xfer);
void octhci_root_intr_close(struct usbd_pipe *pipe);
void octhci_root_intr_done(struct usbd_xfer *xfer);

struct usbd_pipe_methods octhci_root_intr_methods = {
	.transfer = octhci_root_intr_transfer,
	.start = octhci_root_intr_start,
	.abort = octhci_root_intr_abort,
	.close = octhci_root_intr_close,
	.done = octhci_root_intr_done,
};

int
octhci_match(struct device *parent, void *match, void *aux)
{
	struct iobus_attach_args *aa = aux;
	struct cfdata *cf = match;

	/* XXX: check for board type */
	if (aa->aa_name == NULL ||
	    strcmp(aa->aa_name, cf->cf_driver->cd_name) != 0)
		return (0);

	return (1);
}

void
octhci_attach(struct device *parent, struct device *self, void *aux)
{
	struct octhci_softc *sc = (struct octhci_softc *)self;
	struct iobus_attach_args *aa = aux;

	int rc;

	sc->sc_bust = aa->aa_bust;
	rc = bus_space_map(aa->aa_bust, USBN_BASE, USBN_SIZE,
	    0, &sc->sc_regn);
	if (rc != 0)
		panic(": can't map registers");

	/* dma registers */
	rc = bus_space_map(aa->aa_bust, USBN_2_BASE, USBN_2_SIZE,
	    0, &sc->sc_dma_reg);
	if (rc != 0)
		panic(": can't map dma registers");

	/* control registers */
	rc = bus_space_map(aa->aa_bust, USBC_BASE, USBC_SIZE,
	    0, &sc->sc_regc);
	if (rc != 0)
		panic(": can't map control registers");

	/*
	 * XXX: assume only one USB port is available.
	 * Should write a get_nports routine to get the actual number based
	 * on the model.
	 */
	sc->sc_noport = 1;

	sc->sc_bus.usbrev = USBREV_2_0;

	if (octhci_init(sc))
		return;

	octhci_init_core(sc);
	octhci_init_host(sc);

	sc->sc_ih = octeon_intr_establish(CIU_INT_USB, IPL_USB, octhci_intr,
	    (void *)sc, sc->sc_bus.bdev.dv_xname);
	if (sc->sc_ih == NULL)
		panic(": interrupt establish failed");

	sc->sc_bus.methods = &octhci_bus_methods;
	sc->sc_bus.pipe_size = sizeof(struct usbd_pipe);
	sc->sc_bus.dmatag = aa->aa_dmat;

	config_found((void *)sc, &sc->sc_bus, usbctlprint);
}

int
octhci_init(struct octhci_softc *sc)
{
	uint64_t clk;

	if (octhcixfer == NULL) {
		octhcixfer = malloc(sizeof(struct pool), M_DEVBUF, M_NOWAIT);
		if (octhcixfer == NULL) {
			printf("%s: unable to allocate pool descriptor\n",
			    DEVNAME(sc));
			return (ENOMEM);
		}
		pool_init(octhcixfer, sizeof(struct octhci_xfer), 0, 0, 0,
		    "octhcixfer", NULL);
	}

	/*
	 * Clock setup.
	 */
	clk = bus_space_read_8(sc->sc_bust, sc->sc_regn, USBN_CLK_CTL_OFFSET);
	clk |= USBN_CLK_CTL_POR;
	clk &= ~(USBN_CLK_CTL_HRST | USBN_CLK_CTL_PRST | USBN_CLK_CTL_HCLK_RST |
	    USBN_CLK_CTL_ENABLE | USBN_CLK_CTL_P_C_SEL | USBN_CLK_CTL_P_RTYPE);
	clk |= SET_USBN_CLK_CTL_DIVIDE(0x4ULL)
	    | SET_USBN_CLK_CTL_DIVIDE2(0x0ULL);

	bus_space_write_8(sc->sc_bust, sc->sc_regn, USBN_CLK_CTL_OFFSET, clk);
	bus_space_read_8(sc->sc_bust, sc->sc_regn, USBN_CLK_CTL_OFFSET);

	/*
	 * Reset HCLK and wait for it to stabilize.
	 */
	octhci_regn_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_HCLK_RST);
	delay(64);

	octhci_regn_clear(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_POR);

	/*
	 * Wait for the PHY clock to start.
	 */
	delay(1000);

	octhci_regn_set(sc, USBN_USBP_CTL_STATUS_OFFSET,
	    USBN_USBP_CTL_STATUS_ATE_RESET);
	delay(10);

	octhci_regn_clear(sc, USBN_USBP_CTL_STATUS_OFFSET,
			USBN_USBP_CTL_STATUS_ATE_RESET);
	octhci_regn_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_PRST);

	/*
	 * Select host mode.
	 */
	octhci_regn_clear(sc, USBN_USBP_CTL_STATUS_OFFSET,
	    USBN_USBP_CTL_STATUS_HST_MODE);
	delay(1);

	octhci_regn_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_HRST);

	/*
	 * Enable clock.
	 */
	octhci_regn_set(sc, USBN_CLK_CTL_OFFSET, USBN_CLK_CTL_ENABLE);
	delay(1);

	return (0);
}

void
octhci_init_core(struct octhci_softc *sc)
{
	uint32_t value, version, minor, major;
	uint8_t chan;

	value = octhci_regc_read(sc, USBC_GSNPSID_OFFSET);
	/* 0x4F54XXXA */
	minor = (value >> 4) & 0xf;
	major = ((value >> 8) & 0xf) / 2;
	version = (value >> 12) & 0xf;
	printf(": core version %d pass %d.%d\n", version, major, minor);

	value = 0;
	value |= (USBC_GAHBCFG_DMAEN | USBC_GAHBCFG_NPTXFEMPLVL |
	    USBC_GAHBCFG_PTXFEMPLVL | USBC_GAHBCFG_GLBLINTRMSK);
	octhci_regc_write(sc, USBC_GAHBCFG_OFFSET, value);

	/* XXX: CN5XXX: usb->idle_hardware_channels = 0xf7 */

	value = octhci_regc_read(sc, USBC_GUSBCFG_OFFSET);
	value &= ~USBC_GUSBCFG_TOUTCAL;
	value &= ~USBC_GUSBCFG_DDRSEL;
	value &= ~USBC_GUSBCFG_USBTRDTIM;
	value |= (5U << USBC_GUSBCFG_USBTRDTIM_OFFSET);
	value &= ~USBC_GUSBCFG_PHYLPWRCLKSEL;
	octhci_regc_write(sc, USBC_GUSBCFG_OFFSET, value);

	value = octhci_regc_read(sc, USBC_GINTMSK_OFFSET);
	value |= USBC_GINTMSK_OTGINTMSK | USBC_GINTMSK_MODEMISMSK |
	    USBC_GINTMSK_HCHINTMSK;
	value &= ~USBC_GINTMSK_SOFMSK;
	octhci_regc_write(sc, USBC_GINTMSK_OFFSET, value);

	for (chan = 0; chan < 8; chan++) {
		octhci_regc_write(sc, USBC_HCINTMSK0_OFFSET + chan * 32, 0);
	}
}

void
octhci_init_host(struct octhci_softc *sc)
{
	uint32_t value;

	octhci_regc_set(sc, USBC_GINTMSK_OFFSET, USBC_GINTSTS_PRTINT);
	octhci_regc_set(sc, USBC_GINTMSK_OFFSET, USBC_GINTSTS_DISCONNINT);

	/* Clear USBC_HCFG_FSLSSUPP and USBC_HCFG_FSLSPCLKSEL */
	value = octhci_regc_read(sc, USBC_HCFG_OFFSET);
	value &= USBC_HCFG_XXX_31_3;
	octhci_regc_write(sc, USBC_HCFG_OFFSET, value);

	octhci_regc_set(sc, USBC_HPRT_OFFSET, USBC_HPRT_PRTPWR);
}

int
octhci_intr(void *v)
{
	struct octhci_softc *sc = v;

	if (sc == NULL || sc->sc_bus.dying) {
		DPRINTFN(16, ("octhci_intr: dying\n"));
		return (0);
	}

	/* If we get an interrupt while polling, then just ignore it. */
	if (sc->sc_bus.use_polling) {
		DPRINTFN(16, ("octhci_intr: ignore interrupt while polling\n"));
		return (0);
	}

	return (octhci_intr1(sc));
}

int
octhci_intr1(struct octhci_softc *sc)
{
	uint32_t intsts, intmsk;

	intsts = octhci_regc_read(sc, USBC_GINTSTS_OFFSET);
	intmsk = octhci_regc_read(sc, USBC_GINTMSK_OFFSET);

	if ((intsts & intmsk) == 0) {
		DPRINTFN(16, ("%s: masked interrupt\n", DEVNAME(sc)));
		return (0);
	}

	sc->sc_bus.intr_context++;
	sc->sc_bus.no_intrs++;

	octhci_regc_write(sc, USBC_GINTSTS_OFFSET, intsts); /* Acknowledge */

	if (intsts & USBC_GINTSTS_WKUPINT) {
		DPRINTFN(16, ("%s: wake-up interrupt\n", DEVNAME(sc)));
		octhci_regc_set(sc, USBC_GINTSTS_OFFSET,
		    USBC_GINTSTS_WKUPINT);
	}
	if (intsts & USBC_GINTSTS_SESSREQINT) {
		DPRINTFN(16, ("%s: session request interrupt\n", DEVNAME(sc)));
		octhci_regc_set(sc, USBC_GINTSTS_OFFSET,
		    USBC_GINTSTS_SESSREQINT);
	}
	if (intsts & USBC_GINTSTS_DISCONNINT) {
		DPRINTFN(16, ("%s: disco interrupt\n", DEVNAME(sc)));
		sc->sc_port_connect = 0;
		octhci_regc_set(sc, USBC_GINTSTS_OFFSET,
		    USBC_GINTSTS_DISCONNINT);
	}
	if (intsts & USBC_GINTSTS_CONIDSTSCHNG) {
		DPRINTFN(16, ("%s: connector ID interrupt\n", DEVNAME(sc)));
		octhci_regc_set(sc, USBC_GINTSTS_OFFSET,
		    USBC_GINTSTS_CONIDSTSCHNG);
	}
	if (intsts & USBC_GINTSTS_HCHINT) {
		DPRINTFN(16, ("%s: host channel interrupt\n", DEVNAME(sc)));
		octhci_intr_host_chan(sc);
	}
	if (intsts & USBC_GINTSTS_PRTINT) {
		DPRINTFN(16, ("%s: host port interrupt\n", DEVNAME(sc)));
		octhci_intr_host_port(sc);
	}
	if (intsts & USBC_GINTSTS_MODEMIS) {
		DPRINTFN(16, ("%s: mode missmatch\n", DEVNAME(sc)));
	}
	if (intsts & USBC_GINTSTS_OTGINT) {
		DPRINTFN(16, ("%s: OTG interrupt\n", DEVNAME(sc)));
	}

	usb_schedsoftintr(&sc->sc_bus);

	sc->sc_bus.intr_context--;
	return (1);
}

#define OCTHCI_PS_CLEAR \
    (USBC_HPRT_PRTENCHNG | USBC_HPRT_PRTOVRCURRCHNG | USBC_HPRT_PRTCONNDET)
int
octhci_intr_host_port(struct octhci_softc *sc)
{
	struct usbd_xfer *xfer = sc->sc_intrxfer;
	u_char *p;
	int i, m;
	uint32_t hprt = octhci_regc_read(sc, USBC_HPRT_OFFSET);

	if (xfer == NULL)
		goto ack;

	p = KERNADDR(&xfer->dmabuf, 0);
	m = min(sc->sc_noport, xfer->length * 8 - 1);
	memset(p, 0, xfer->length);
	for (i = 1; i <= m; i++) {
		/* Pick out CHANGE bits from the status reg. */
		if (hprt & OCTHCI_PS_CLEAR)
			p[i/8] |= 1 << (i%8);
	}
	DPRINTF(("%s: change=0x%02x\n", __func__, *p));
	xfer->actlen = xfer->length;
	xfer->status = USBD_NORMAL_COMPLETION;

	usb_transfer_complete(xfer);

ack:
	octhci_regc_write(sc, USBC_HPRT_OFFSET, hprt);	/* Acknowledge */

	return (USBD_NORMAL_COMPLETION);
}

int
octhci_intr_host_chan(struct octhci_softc *sc)
{
	int chan = 0;
	uint32_t haint;

	haint = octhci_regc_read(sc, USBC_HAINT_OFFSET);
	haint &= octhci_regc_read(sc, USBC_HAINTMSK_OFFSET);

	DPRINTFN(16, ("%s: haint %X\n", DEVNAME(sc), haint));

	for (; haint != 0; haint ^= (1 << chan)) {
		chan = ffs32(haint) - 1;
		octhci_intr_host_chan_n(sc, chan);
	}

	return (USBD_NORMAL_COMPLETION);
}

int
octhci_intr_host_chan_n(struct octhci_softc *sc, int chan)
{
	uint32_t hcintn;

	hcintn = octhci_regc_read(sc, USBC_HCINT0_OFFSET + chan * 0x20);
	hcintn &= octhci_regc_read(sc, USBC_HCINTMSK0_OFFSET + chan * 0x20);

	DPRINTFN(16, ("%s: chan %d hcintn %d\n", DEVNAME(sc), chan, hcintn));

	/* Acknowledge */
	octhci_regc_write(sc, USBC_HCINT0_OFFSET + chan * 0x20, hcintn);

	return (USBD_NORMAL_COMPLETION);
}

inline void
octhci_regn_set(struct octhci_softc *sc, bus_size_t offset, uint64_t bits)
{
	uint64_t value;

	value = bus_space_read_8(sc->sc_bust, sc->sc_regn, offset);
	value |= bits;

	bus_space_write_8(sc->sc_bust, sc->sc_regn, offset, value);
	/* guarantee completion of the store operation on RSL registers*/
	bus_space_read_8(sc->sc_bust, sc->sc_regn, offset);
}

inline void
octhci_regn_clear(struct octhci_softc *sc, bus_size_t offset, uint64_t bits)
{
	uint64_t value;

	value = bus_space_read_8(sc->sc_bust, sc->sc_regn, offset);
	value &= ~bits;

	bus_space_write_8(sc->sc_bust, sc->sc_regn, offset, value);
	/* guarantee completion of the store operation on RSL registers*/
	bus_space_read_8(sc->sc_bust, sc->sc_regn, offset);
}

inline void
octhci_regc_set(struct octhci_softc *sc, bus_size_t offset, uint32_t bits)
{
	uint32_t value;

	value = octhci_regc_read(sc, offset);
	value |= bits;
	octhci_regc_write(sc, offset, value);
}

inline void
octhci_regc_clear(struct octhci_softc *sc, bus_size_t offset, uint32_t bits)
{
	uint32_t value;

	value = octhci_regc_read(sc, offset);
	value &= ~bits;
	octhci_regc_write(sc, offset, value);
}

/* XXX: fix the endianess in the include and ditch these methods */
inline void
octhci_regc_write(struct octhci_softc *sc, bus_size_t offset, uint32_t value)
{
	/*
	 * In the Cavium document, CSR addresses are written in little-endian
	 * format. so the address should be swapped on the core running in
	 * big-endian.
	 */
	bus_space_write_4(sc->sc_bust, sc->sc_regc, (offset^4), value);
}

inline uint32_t
octhci_regc_read(struct octhci_softc *sc, bus_size_t offset)
{
	/*
	 * In the Cavium document, CSR addresses are written in little-endian
	 * format. so the address should be swapped on the core running in
	 * big-endian.
	 */
	return bus_space_read_4(sc->sc_bust, sc->sc_regc, (offset^4));
}

/*
 * USBD Bus Methods.
 */

usbd_status
octhci_open(struct usbd_pipe *pipe)
{
	struct octhci_softc *sc = (struct octhci_softc *)pipe->device->bus;
	usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc;

	if (sc->sc_bus.dying)
		return (USBD_IOERROR);

	/* Root Hub */
	if (pipe->device->depth == 0) {
		switch (ed->bEndpointAddress) {
		case USB_CONTROL_ENDPOINT:
			DPRINTF(("Root Hub Control\n"));
			pipe->methods = &octhci_root_ctrl_methods;
			break;
		case UE_DIR_IN | OCTHCI_INTR_ENDPT:
			DPRINTF(("Root Hub Interrupt\n"));
			pipe->methods = &octhci_root_intr_methods;
			break;
		default:
			DPRINTF(("%s: bad bEndpointAddress 0x%02x\n", __func__,
			    ed->bEndpointAddress));
			pipe->methods = NULL;
			return (USBD_INVAL);
		}
		return (USBD_NORMAL_COMPLETION);
	}

	DPRINTFN(16, ("%s: Not a Root Hub\n", DEVNAME(sc)));
	/* XXX: not supported yet */
	return (USBD_INVAL);
}

void
octhci_softintr(void *aux)
{
}

void
octhci_poll(struct usbd_bus *bus)
{
	struct octhci_softc *sc = (struct octhci_softc *)bus;

	if (octhci_regc_read(sc, USBC_GINTSTS_OFFSET))
		octhci_intr1(sc);
}

struct usbd_xfer *
octhci_allocx(struct usbd_bus *bus)
{
	struct octhci_xfer *xx;

	xx = pool_get(octhcixfer, PR_NOWAIT | PR_ZERO);
#ifdef DIAGNOSTIC
	if (xx != NULL)
		xx->xfer.busy_free = XFER_ONQU;
#endif
	return ((struct usbd_xfer *)xx);
}

void
octhci_freex(struct usbd_bus *bus, struct usbd_xfer *xfer)
{
	struct octhci_xfer *xx = (struct octhci_xfer*)xfer;

#ifdef DIAGNOSTIC
	if (xfer->busy_free != XFER_ONQU) {
		printf("%s: xfer=%p not busy, 0x%08x\n", __func__, xfer,
		    xfer->busy_free);
		return;
	}
#endif
	pool_put(octhcixfer, xx);
}

/* Root hub descriptors. */
usb_device_descriptor_t octhci_devd = {
	USB_DEVICE_DESCRIPTOR_SIZE,
	UDESC_DEVICE,		/* type */
	{0x00, 0x02},		/* USB version */
	UDCLASS_HUB,		/* class */
	UDSUBCLASS_HUB,		/* subclass */
	UDPROTO_HSHUBSTT,	/* protocol */
	64,			/* max packet */
	{0},{0},{0x00,0x01},	/* device id */
	1,2,0,			/* string indexes */
	1			/* # of configurations */
};

const usb_config_descriptor_t octhci_confd = {
	USB_CONFIG_DESCRIPTOR_SIZE,
	UDESC_CONFIG,
	{USB_CONFIG_DESCRIPTOR_SIZE +
	 USB_INTERFACE_DESCRIPTOR_SIZE +
	 USB_ENDPOINT_DESCRIPTOR_SIZE},
	1,
	1,
	0,
	UC_SELF_POWERED,
	0                      /* max power */
};

const usb_interface_descriptor_t octhci_ifcd = {
	USB_INTERFACE_DESCRIPTOR_SIZE,
	UDESC_INTERFACE,
	0,
	0,
	1,
	UICLASS_HUB,
	UISUBCLASS_HUB,
	UIPROTO_HSHUBSTT,	/* XXX */
	0
};

const usb_endpoint_descriptor_t octhci_endpd = {
	USB_ENDPOINT_DESCRIPTOR_SIZE,
	UDESC_ENDPOINT,
	UE_DIR_IN | OCTHCI_INTR_ENDPT,
	UE_INTERRUPT,
	{8, 0},                 /* max packet */
	12
};

const usb_hub_descriptor_t octhci_hubd = {
	USB_HUB_DESCRIPTOR_SIZE,
	UDESC_HUB,
	0,
	{0,0},
	0,
	0,
	{0},
};

/*
 * USBD Root Control Pipe Methods.
 */

usbd_status
octhci_root_ctrl_transfer(struct usbd_xfer *xfer)
{
	usbd_status err;

	err = usb_insert_transfer(xfer);
	if (err)
		return (err);

	return (octhci_root_ctrl_start(SIMPLEQ_FIRST(&xfer->pipe->queue)));
}

usbd_status
octhci_root_ctrl_start(struct usbd_xfer *xfer)
{
	struct octhci_softc *sc = (struct octhci_softc *)xfer->device->bus;
	usb_port_status_t ps;
	usb_device_request_t *req;
	void *buf = NULL;
	usb_hub_descriptor_t hubd;
	usbd_status err;
	int s, len, value, index;
	int l, totlen = 0;
	int i;
	/* int port; */
	uint32_t v;

	KASSERT(xfer->rqflags & URQ_REQUEST);

	if (sc->sc_bus.dying)
		return (USBD_IOERROR);

	req = &xfer->request;

	DPRINTFN(4,("%s: type=0x%02x request=%02x\n", __func__,
	    req->bmRequestType, req->bRequest));

	len = UGETW(req->wLength);
	value = UGETW(req->wValue);
	index = UGETW(req->wIndex);

	if (len != 0)
		buf = KERNADDR(&xfer->dmabuf, 0);

#define C(x,y) ((x) | ((y) << 8))
	switch(C(req->bRequest, req->bmRequestType)) {
	case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
	case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
	case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
		/*
		 * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
		 * for the integrated root hub.
		 */
		break;
	case C(UR_GET_CONFIG, UT_READ_DEVICE):
		if (len > 0) {
			*(uint8_t *)buf = sc->sc_conf;
			totlen = 1;
		}
		break;
	case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
		DPRINTFN(8,("octhci_root_ctrl_start: wValue=0x%04x\n", value));
		switch(value >> 8) {
		case UDESC_DEVICE:
			if ((value & 0xff) != 0) {
				err = USBD_IOERROR;
				goto ret;
			}
			totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE);
			USETW(octhci_devd.idVendor, sc->sc_id_vendor);
			memcpy(buf, &octhci_devd, l);
			break;
		/*
		 * We can't really operate at another speed, but the spec says
		 * we need this descriptor.
		 */
		case UDESC_OTHER_SPEED_CONFIGURATION:
		case UDESC_CONFIG:
			if ((value & 0xff) != 0) {
				err = USBD_IOERROR;
				goto ret;
			}
			totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE);
			memcpy(buf, &octhci_confd, l);
			((usb_config_descriptor_t *)buf)->bDescriptorType =
			    value >> 8;
			buf = (char *)buf + l;
			len -= l;
			l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE);
			totlen += l;
			memcpy(buf, &octhci_ifcd, l);
			buf = (char *)buf + l;
			len -= l;
			l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE);
			totlen += l;
			memcpy(buf, &octhci_endpd, l);
			break;
		case UDESC_STRING:
			if (len == 0)
				break;
			*(u_int8_t *)buf = 0;
			totlen = 1;
			switch (value & 0xff) {
			case 0: /* Language table */
				totlen = usbd_str(buf, len, "\001");
				break;
			case 1: /* Vendor */
				totlen = usbd_str(buf, len, sc->sc_vendor);
				break;
			case 2: /* Product */
				totlen = usbd_str(buf, len, "octHCI root hub");
				break;
			}
			break;
		default:
			err = USBD_IOERROR;
			goto ret;
		}
		break;
	case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
		if (len > 0) {
			*(uint8_t *)buf = 0;
			totlen = 1;
		}
		break;
	case C(UR_GET_STATUS, UT_READ_DEVICE):
		if (len > 1) {
			USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED);
			totlen = 2;
		}
		break;
	case C(UR_GET_STATUS, UT_READ_INTERFACE):
	case C(UR_GET_STATUS, UT_READ_ENDPOINT):
		if (len > 1) {
			USETW(((usb_status_t *)buf)->wStatus, 0);
			totlen = 2;
		}
		break;
	case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
		if (value >= USB_MAX_DEVICES) {
			err = USBD_IOERROR;
			goto ret;
		}
		break;
	case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
		if (value != 0 && value != 1) {
			err = USBD_IOERROR;
			goto ret;
		}
		sc->sc_conf = value;
		break;
	case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
		break;
	case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
	case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
	case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
		err = USBD_IOERROR;
		goto ret;
	case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
		break;
	case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
		break;
	/* Hub requests */
	case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
		break;
	case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
		DPRINTFN(8, ("octhci_root_ctrl_start: UR_CLEAR_PORT_FEATURE "
		    "port=%d feature=%d\n", index, value));
		if (index < 1 || index > sc->sc_noport) {
			err = USBD_IOERROR;
			goto ret;
		}
		switch (value) {
		case UHF_PORT_ENABLE:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTENA);
			break;
		case UHF_PORT_SUSPEND:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTSUSP);
			break;
		case UHF_PORT_POWER:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTPWR);
			break;
		case UHF_PORT_INDICATOR:
			break;
		case UHF_C_PORT_CONNECTION:
			break;
		case UHF_C_PORT_ENABLE:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTENCHNG);
			break;
		case UHF_C_PORT_SUSPEND:
			break;
		case UHF_C_PORT_OVER_CURRENT:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTOVRCURRCHNG);
			break;
		case UHF_C_PORT_RESET:
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTRST);
			sc->sc_port_reset = 0;
			break;
		default:
			err = USBD_IOERROR;
			goto ret;
		}
		break;

	case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
		if (len == 0)
			break;
		if ((value & 0xff) != 0) {
			err = USBD_IOERROR;
			goto ret;
		}
		hubd = octhci_hubd;
		hubd.bNbrPorts = sc->sc_noport;

		/* Taken from the SDK Root Hub example */
		USETW(hubd.wHubCharacteristics, UHD_OC_INDIVIDUAL);
		hubd.bPwrOn2PwrGood = 1;
		hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE;

		l = min(len, hubd.bDescLength);
		totlen = l;
		memcpy(buf, &hubd, l);
		break;
	case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
		if (len != 16) {
			err = USBD_IOERROR;
			goto ret;
		}
		memset(buf, 0, len);
		totlen = len;
		break;
	case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
		DPRINTFN(8,("octhci_root_ctrl_start: get port status i=%d\n",
		    index));
		if (index < 1 || index > sc->sc_noport) {
			err = USBD_IOERROR;
			goto ret;
		}
		if (len != 4) {
			err = USBD_IOERROR;
			goto ret;
		}

		v = octhci_regc_read(sc, USBC_HPRT_OFFSET);
		DPRINTFN(8,("octhci_root_ctrl_start: port status=0x%04x\n", v));
		switch ((v & USBC_HPRT_PRTSPD) >> USBC_HPRT_PRTSPD_OFFSET) {
		case USBC_HPRT_PRTSPD_FULL:
			i = UPS_FULL_SPEED;
			break;
		case USBC_HPRT_PRTSPD_LOW:
			i = UPS_LOW_SPEED;
			break;
		case USBC_HPRT_PRTSPD_HIGH:
		default:
			i = UPS_HIGH_SPEED;
			break;
		}
		if (v & USBC_HPRT_PRTCONNSTS) {
			i |= UPS_CURRENT_CONNECT_STATUS;
			sc->sc_port_change = (sc->sc_port_connect != 1);
			sc->sc_port_connect = 1;
		}
		if (v & USBC_HPRT_PRTENA)
			i |= UPS_PORT_ENABLED;
		if (v & USBC_HPRT_PRTOVRCURRACT)
			i |= UPS_OVERCURRENT_INDICATOR;
		if (v & USBC_HPRT_PRTRST)
			i |= UPS_RESET;
		if (v & USBC_HPRT_PRTPWR)
			i |= UPS_PORT_POWER;
		USETW(ps.wPortStatus, i);

		i = 0;
		if (v & USBC_HPRT_PRTCONNDET || sc->sc_port_change)
			i |= UPS_C_CONNECT_STATUS;
		if (v & USBC_HPRT_PRTENCHNG)
			i |= UPS_C_PORT_ENABLED;
		if (v & USBC_HPRT_PRTOVRCURRCHNG)
			i |= UPS_C_OVERCURRENT_INDICATOR;
		if (sc->sc_port_reset)
			i |= UPS_C_PORT_RESET;
		USETW(ps.wPortChange, i);

		l = min(len, sizeof ps);
		memcpy(buf, &ps, l);
		totlen = l;
		break;
	case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
		err = USBD_IOERROR;
		goto ret;
	case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
		break;
	case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):

		i = index >> 8;
		index &= 0x00ff;

		if (index < 1 || index > sc->sc_noport) {
			err = USBD_IOERROR;
			goto ret;
		}

		switch (value) {
		case UHF_PORT_ENABLE:
			/* The application can't set the enable bit */
			break;
		case UHF_PORT_SUSPEND:
			DPRINTFN(6, ("suspend port %u (LPM=%u)\n", index, i));
			octhci_regc_set(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTSUSP);
			break;
		case UHF_PORT_RESET:
			DPRINTFN(6, ("reset port %d\n", index));
			/* Start reset sequence. */
			octhci_regc_set(sc, USBC_HPRT_OFFSET, USBC_HPRT_PRTRST);
			/* Wait for reset to complete. */
			usb_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY);
			if (sc->sc_bus.dying) {
				err = USBD_IOERROR;
				goto ret;
			}
			/* Terminate reset sequence. */
			octhci_regc_clear(sc, USBC_HPRT_OFFSET,
			    USBC_HPRT_PRTRST);
			sc->sc_port_reset = 1;
			break;
		case UHF_PORT_POWER:
			DPRINTFN(3, ("set port power %d\n", index));
			octhci_regc_set(sc, USBC_HPRT_OFFSET, USBC_HPRT_PRTPWR);
			break;
		case UHF_PORT_INDICATOR:
			DPRINTFN(3, ("set port indicator %d\n", index));
			break;
		default:
			err = USBD_IOERROR;
			goto ret;
		}
		break;
	case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
	case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
	case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER):
	case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER):
		break;
	default:
		err = USBD_IOERROR;
		goto ret;
	}
	xfer->actlen = totlen;
	err = USBD_NORMAL_COMPLETION;
ret:
	xfer->status = err;
	s = splusb();
	usb_transfer_complete(xfer);
	splx(s);
	return (USBD_IN_PROGRESS);
}

void
octhci_root_ctrl_abort(struct usbd_xfer *xfer)
{
}

void
octhci_root_ctrl_close(struct usbd_pipe *pipe)
{
}

void
octhci_root_ctrl_done(struct usbd_xfer *xfer)
{
}

/*
 * USBD Root Interrupt Pipe Methods.
 */

usbd_status
octhci_root_intr_transfer(struct usbd_xfer *xfer)
{
	usbd_status err;

	err = usb_insert_transfer(xfer);
	if (err)
		return (err);

	return (octhci_root_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue)));
}

usbd_status
octhci_root_intr_start(struct usbd_xfer *xfer)
{
	struct octhci_softc *sc = (struct octhci_softc *)xfer->device->bus;

	if (sc->sc_bus.dying)
		return (USBD_IOERROR);

	sc->sc_intrxfer = xfer;

	return (USBD_IN_PROGRESS);
}

void
octhci_root_intr_abort(struct usbd_xfer *xfer)
{
	struct octhci_softc *sc = (struct octhci_softc *)xfer->device->bus;
	int s;

	sc->sc_intrxfer = NULL;

	xfer->status = USBD_CANCELLED;
	s = splusb();
	usb_transfer_complete(xfer);
	splx(s);
}

void
octhci_root_intr_close(struct usbd_pipe *pipe)
{
}

void
octhci_root_intr_done(struct usbd_xfer *xfer)
{
}