/*	$OpenBSD: octeon_iobus.c,v 1.14 2015/07/20 19:44:32 pirofti 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 iobus driver.
 * It handles configuration of all devices on the processor bus except 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/octeonvar.h>
#include <machine/octeonreg.h>

#include <octeon/dev/iobusvar.h>
#include <octeon/dev/cn30xxgmxreg.h>
#include <octeon/dev/octhcireg.h>	/* USBN_BASE */

int	iobusmatch(struct device *, void *, void *);
void	iobusattach(struct device *, struct device *, void *);
int	iobusprint(void *, const char *);
int	iobussubmatch(struct device *, void *, void *);
int	iobussearch(struct device *, void *, void *);

u_int8_t iobus_read_1(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int16_t iobus_read_2(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int32_t iobus_read_4(bus_space_tag_t, bus_space_handle_t, bus_size_t);
u_int64_t iobus_read_8(bus_space_tag_t, bus_space_handle_t, bus_size_t);

void	 iobus_write_1(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int8_t);
void	 iobus_write_2(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int16_t);
void	 iobus_write_4(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int32_t);
void	 iobus_write_8(bus_space_tag_t, bus_space_handle_t, bus_size_t,
	    u_int64_t);

void	 iobus_read_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_2(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 iobus_read_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_4(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);
void	 iobus_read_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    u_int8_t *, bus_size_t);
void	 iobus_write_raw_8(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
	    const u_int8_t *, bus_size_t);

bus_addr_t iobus_pa_to_device(paddr_t);
paddr_t	 iobus_device_to_pa(bus_addr_t);

struct cfattach iobus_ca = {
	sizeof(struct device), iobusmatch, iobusattach
};

struct cfdriver iobus_cd = {
	NULL, "iobus", DV_DULL
};

bus_space_t iobus_tag = {
	.bus_base = PHYS_TO_XKPHYS(0, 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 =		iobus_space_map,
	._space_unmap =		iobus_space_unmap,
	._space_subregion =	generic_space_region,
	._space_vaddr =		generic_space_vaddr
};

bus_space_handle_t iobus_h;

struct machine_bus_dma_tag iobus_bus_dma_tag = {
	NULL,			/* _cookie */
	_dmamap_create,
	_dmamap_destroy,
	_dmamap_load,
	_dmamap_load_mbuf,
	_dmamap_load_uio,
	_dmamap_load_raw,
	_dmamap_load_buffer,
	_dmamap_unload,
	_dmamap_sync,
	_dmamem_alloc,
	_dmamem_free,
	_dmamem_map,
	_dmamem_unmap,
	_dmamem_mmap,
	iobus_pa_to_device,
	iobus_device_to_pa,
	0
};

/*
 * List of iobus child devices whose base addresses are too large to be
 * recorded in the kernel configuration file. So look them up from here instead.
 */

static const struct octeon_iobus_addrs iobus_addrs[] = {
	{ "octcf",	OCTEON_CF_BASE  },
	{ "cn30xxgmx",	GMX0_BASE_PORT0 },
	{ "octrng",	OCTEON_RNG_BASE },
	{ "dwctwo",	USBN_BASE       },
	{ "amdcf",	OCTEON_AMDCF_BASE},
};

/* There can only be one. */
int	iobus_found;

/*
 * Match bus only to targets which have this bus.
 */
int
iobusmatch(struct device *parent, void *match, void *aux)
{
	if (iobus_found)
		return (0);

	return (1);
}

int
iobusprint(void *aux, const char *iobus)
{
	struct iobus_attach_args *aa = aux;

	if (iobus != NULL)
		printf("%s at %s", aa->aa_name, iobus);

	if (aa->aa_addr != 0)
		printf(" base 0x%lx", aa->aa_addr);
	if (aa->aa_irq >= 0)
		printf(" irq %d", aa->aa_irq);

	return (UNCONF);
}

int
iobussubmatch(struct device *parent, void *vcf, void *args)
{
	struct cfdata *cf = vcf;
	struct iobus_attach_args *aa = args;

	if (strcmp(cf->cf_driver->cd_name, aa->aa_name) != 0)
		return 0;

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

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

void
iobusattach(struct device *parent, struct device *self, void *aux)
{
	struct device *sc = self;
	struct octeon_config oc;

	/*
	 * Map and setup CIU control registers.
	 */
	if (bus_space_map(&iobus_tag, OCTEON_CIU_BASE, OCTEON_CIU_SIZE, 0,
		&iobus_h)) {
		printf(": can't map CIU control registers\n");
		return;
	}

	iobus_found = 1;

	printf("\n");

	octeon_intr_init();

	/* XXX */
	oc.mc_iobus_bust = &iobus_tag;
	oc.mc_iobus_dmat = &iobus_bus_dma_tag;
	void	cn30xxfpa_bootstrap(struct octeon_config *);
	cn30xxfpa_bootstrap(&oc);
	void	cn30xxpow_bootstrap(struct octeon_config *);
	cn30xxpow_bootstrap(&oc);

	/*
	 * Attach all subdevices as described in the config file.
	 */
	config_search(iobussearch, self, sc);
}

int
iobussearch(struct device *parent, void *v, void *aux)
{
	struct iobus_attach_args aa;
	struct cfdata *cf = v;
	int i;

	aa.aa_name = cf->cf_driver->cd_name;
	aa.aa_bust = &iobus_tag;
	aa.aa_dmat = &iobus_bus_dma_tag;
	aa.aa_addr = cf->cf_loc[0];
	aa.aa_irq  = cf->cf_loc[1];
	aa.aa_unitno = cf->cf_unit;

	/* No address specified, try to look it up. */
	if (aa.aa_addr == -1) {
		for (i = 0; i < nitems(iobus_addrs); i++) {
			if (strcmp(iobus_addrs[i].name, cf->cf_driver->cd_name) == 0)
				aa.aa_addr = iobus_addrs[i].address;
		}
	}

	if (cf->cf_attach->ca_match(parent, cf, &aa) == 0)
		return 0;

	config_attach(parent, cf, &aa, iobusprint);
	return 1;
}

int
iobus_space_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_KSEG0)) {
		*bshp = PHYS_TO_CKSEG0(offs);
		return 0;
	}
	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;
}

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

/*
 * Iobus bus_dma helpers.
 */

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

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