/*	$NetBSD: bus_space.c,v 1.32 2016/07/11 16:18:56 matt Exp $	*/

/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bus_space.c,v 1.32 2016/07/11 16:18:56 matt Exp $");

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

#include <uvm/uvm_extern.h>

#include <mips/cache.h>
#include <mips/locore.h>
#include <mips/pte.h>

#include <machine/bus_space_hpcmips.h>

#ifdef BUS_SPACE_DEBUG
#define	DPRINTF(arg) printf arg
#else
#define	DPRINTF(arg)
#endif

#define MAX_BUSSPACE_TAG 10

/* proto types */
bus_space_handle_t __hpcmips_cacheable(struct bus_space_tag_hpcmips*,
    bus_addr_t, bus_size_t, int);
bus_space_protos(_);
bus_space_protos(bs_notimpl);

/* variables */
static  struct bus_space_tag_hpcmips __bus_space[MAX_BUSSPACE_TAG];
static int __bus_space_index;
static struct bus_space_tag_hpcmips __sys_bus_space = {
	{
		NULL,
		{
			/* mapping/unmapping */
			__bs_map,
			__bs_unmap,
			__bs_subregion,

			/* allocation/deallocation */
			__bs_alloc,
			__bs_free,

			/* get kernel virtual address */
			bs_notimpl_bs_vaddr, /* there is no linear mapping */

			/* Mmap bus space for user */
			bs_notimpl_bs_mmap,

			/* barrier */
			__bs_barrier,

			/* probe */
			__bs_peek,
			__bs_poke,

			/* read (single) */
			__bs_r_1,
			__bs_r_2,
			__bs_r_4,
			bs_notimpl_bs_r_8,

			/* read multiple */
			__bs_rm_1,
			__bs_rm_2,
			__bs_rm_4,
			bs_notimpl_bs_rm_8,

			/* read region */
			__bs_rr_1,
			__bs_rr_2,
			__bs_rr_4,
			bs_notimpl_bs_rr_8,

			/* write (single) */
			__bs_w_1,
			__bs_w_2,
			__bs_w_4,
			bs_notimpl_bs_w_8,

			/* write multiple */
			__bs_wm_1,
			__bs_wm_2,
			__bs_wm_4,
			bs_notimpl_bs_wm_8,

			/* write region */
			__bs_wr_1,
			__bs_wr_2,
			__bs_wr_4,
			bs_notimpl_bs_wr_8,

			/* set multi */
			__bs_sm_1,
			__bs_sm_2,
			__bs_sm_4,
			bs_notimpl_bs_sm_8,

			/* set region */
			__bs_sr_1,
			__bs_sr_2,
			__bs_sr_4,
			bs_notimpl_bs_sr_8,

			/* copy */
			__bs_c_1,
			__bs_c_2,
			__bs_c_4,
			bs_notimpl_bs_c_8,
		},
	},

	"whole bus space",	/* bus name */
	0,			/* extent base */
	0xffffffff,		/* extent size */
	NULL,			/* pointer for extent structure */
};
static bus_space_tag_t __sys_bus_space_tag = &__sys_bus_space.bst;

bus_space_tag_t
hpcmips_system_bus_space(void)
{

	return (__sys_bus_space_tag);
}

struct bus_space_tag_hpcmips *
hpcmips_system_bus_space_hpcmips(void)
{

	return (&__sys_bus_space);
}

struct bus_space_tag_hpcmips *
hpcmips_alloc_bus_space_tag(void)
{

	if (__bus_space_index >= MAX_BUSSPACE_TAG) {
		panic("hpcmips_internal_alloc_bus_space_tag: tag full.");
	}

	return (&__bus_space[__bus_space_index++]);
}

void
hpcmips_init_bus_space(struct bus_space_tag_hpcmips *t,
    struct bus_space_tag_hpcmips *basetag,
    const char *name, u_int32_t base, u_int32_t size)
{
	u_int32_t pa, endpa;
	vaddr_t va;

	if (basetag != NULL)
		memcpy(t, basetag, sizeof(struct bus_space_tag_hpcmips));
	strncpy(t->name, name, sizeof(t->name));
	t->name[sizeof(t->name) - 1] = '\0';
	t->base = base;
	t->size = size;
	
	/* 
	 * If request physical address is greater than 512MByte,
	 * mapping it to kseg2.
	 */
	if (t->base >= 0x20000000) {
		pa = mips_trunc_page(t->base);
		endpa = mips_round_page(t->base + t->size);

		if (!(va = uvm_km_alloc(kernel_map, endpa - pa, 0,
		    UVM_KMF_VAONLY))) {
			panic("hpcmips_init_bus_space_extent:"
			    "can't allocate kernel virtual");
		}
		DPRINTF(("pa:0x%08x -> kv:0x%08x+0x%08x",
		    (unsigned int)t->base, (unsigned int)va, t->size));
		t->base = va; /* kseg2 addr */
				
		for (; pa < endpa; pa += PAGE_SIZE, va += PAGE_SIZE) {
			pmap_kenter_pa(va, pa, VM_PROT_READ | VM_PROT_WRITE, 0);
		}
		pmap_update(pmap_kernel());
	}

	t->extent = (void*)extent_create(t->name, t->base, 
	    t->base + t->size,
	    0, 0, EX_NOWAIT);
	if (!t->extent) {
		panic("hpcmips_init_bus_space_extent:"
		    "unable to allocate %s map", t->name);
	}
}

static bool
mips_pte_cachechange(struct pmap *pmap, vaddr_t sva, vaddr_t eva,
    pt_entry_t *ptep, uintptr_t flags)
{
	mips_dcache_wbinv_range(sva, eva - sva);

	for (; sva < eva; sva += PAGE_SIZE) {
		pt_entry_t pte = pte_cached_change(*ptep, flags);
		/*
		 * Update the same virtual address entry.
		 */
		*ptep = pte;
		tlb_update_addr(sva, KERNEL_PID, pte, 0);
	}

	return false;
}

bus_space_handle_t
__hpcmips_cacheable(struct bus_space_tag_hpcmips *t, bus_addr_t bpa,
    bus_size_t size, int cacheable)
{
	if (t->base >= MIPS_KSEG2_START) {
		const vaddr_t sva = mips_trunc_page(bpa);
		const vaddr_t eva = mips_round_page(bpa + size);
		pmap_pte_process(pmap_kernel(), sva, eva,
		    mips_pte_cachechange, cacheable);
		return bpa;
	}

	return cacheable ? MIPS_PHYS_TO_KSEG0(bpa) : MIPS_PHYS_TO_KSEG1(bpa);
}

/* ARGSUSED */
int
__bs_map(bus_space_tag_t tx, bus_addr_t bpa, bus_size_t size, int flags,
    bus_space_handle_t *bshp)
{
	struct bus_space_tag_hpcmips *t = (struct bus_space_tag_hpcmips *)tx;
	int err;
	int cacheable = flags & BUS_SPACE_MAP_CACHEABLE;

	DPRINTF(("\tbus_space_map:%#lx(%#lx)+%#lx\n",
	    bpa, bpa + t->base, size));

	if (!t->extent) { /* Before autoconfiguration, can't use extent */
		DPRINTF(("bus_space_map: map temporary region:"
		    "0x%08lx-0x%08lx\n", bpa, bpa+size));
		bpa += t->base;
	} else {
		bpa += t->base;
		if ((err = extent_alloc_region(t->extent, bpa, size, 
		    EX_NOWAIT|EX_MALLOCOK))) {
			DPRINTF(("\tbus_space_map: "
			    "extent_alloc_regiion() failed\n"));
			return (err);
		}
	}
	*bshp = __hpcmips_cacheable(t, bpa, size, cacheable);

	return (0);
}

/* ARGSUSED */
void
__bs_unmap(bus_space_tag_t tx, bus_space_handle_t bsh, bus_size_t size)
{
	struct bus_space_tag_hpcmips *t = (struct bus_space_tag_hpcmips *)tx;
	int err;
	u_int32_t addr;

	if (!t->extent) {
		return; /* Before autoconfiguration, can't use extent */
	}

	if (t->base < MIPS_KSEG2_START) {
		addr = MIPS_KSEG1_TO_PHYS(bsh);
	} else {
		addr = bsh;
	}

	if ((err = extent_free(t->extent, addr, size, EX_NOWAIT))) {
		DPRINTF(("warning: %#lx-%#lx of %s space lost\n",
		    bsh, bsh+size, t->name));
	}
}

/* ARGSUSED */
int
__bs_subregion(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);
}

/* ARGSUSED */
int
__bs_alloc(bus_space_tag_t tx, bus_addr_t rstart, bus_addr_t rend,
    bus_size_t size, bus_size_t alignment, bus_size_t boundary, int flags,
    bus_addr_t *bpap, bus_space_handle_t *bshp)
{
	struct bus_space_tag_hpcmips *t = (struct bus_space_tag_hpcmips *)tx;
	int cacheable = flags & BUS_SPACE_MAP_CACHEABLE;
	u_long bpa;
	int err;

	if (!t->extent)
		panic("bus_space_alloc: no extent");

	DPRINTF(("\tbus_space_alloc:%#lx(%#lx)+%#lx\n", bpa,
	    bpa - t->base, size));

	rstart += t->base;
	rend += t->base;
	if ((err = extent_alloc_subregion(t->extent, rstart, rend, size,
	    alignment, boundary, EX_FAST|EX_NOWAIT|EX_MALLOCOK, &bpa))) {
		return (err);
	}

	*bshp = __hpcmips_cacheable(t, bpa, size, cacheable);

	if (bpap) {
		*bpap = bpa;
	}

	return (0);
}

/* ARGSUSED */
void
__bs_free(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t size)
{
	/* bus_space_unmap() does all that we need to do. */
	bus_space_unmap(t, bsh, size);
}

void
__bs_barrier(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    bus_size_t len, int flags)
{
	wbflush();
}

int
__bs_peek(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    size_t size, void *ptr)
{
	u_int32_t tmp;

	if (badaddr((void *)(bsh + offset), size))
		return (-1);

	if (ptr == NULL)
		ptr = &tmp;

	switch(size) {
	case 1:
		*((u_int8_t *)ptr) = bus_space_read_1(t, bsh, offset);
		break;
	case 2:
		*((u_int16_t *)ptr) = bus_space_read_2(t, bsh, offset);
		break;
	case 4:
		*((u_int32_t *)ptr) = bus_space_read_4(t, bsh, offset);
		break;
	default:
		panic("bus_space_peek: bad size, %d", size);
	}

	return (0);
}

int
__bs_poke(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    size_t size, u_int32_t val)
{

	if (badaddr((void *)(bsh + offset), size))
		return (-1);

	switch(size) {
	case 1:
		bus_space_write_1(t, bsh, offset, val);
		break;
	case 2:
		bus_space_write_2(t, bsh, offset, val);
		break;
	case 4:
		bus_space_write_4(t, bsh, offset, val);
		break;
	default:
		panic("bus_space_poke: bad size, %d", size);
	}

	return (0);
}

u_int8_t
__bs_r_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset)
{
	wbflush();
	return (*(volatile u_int8_t *)(bsh + offset));
}

u_int16_t
__bs_r_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset)
{
	wbflush();
	return (*(volatile u_int16_t *)(bsh + offset));
}

u_int32_t
__bs_r_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset)
{
	wbflush();
	return (*(volatile u_int32_t *)(bsh + offset));
}

void
__bs_rm_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int8_t *addr, bus_size_t count) {
	while (count--)
		*addr++ = bus_space_read_1(t, bsh, offset);
}

void
__bs_rm_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int16_t *addr, bus_size_t count)
{
	while (count--)
		*addr++ = bus_space_read_2(t, bsh, offset);
}

void
__bs_rm_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int32_t *addr, bus_size_t count)
{
	while (count--)
		*addr++ = bus_space_read_4(t, bsh, offset);
}

void
__bs_rr_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int8_t *addr, bus_size_t count)
{
	while (count--) {
		*addr++ = bus_space_read_1(t, bsh, offset);
		offset += sizeof(*addr);
	}
}

void
__bs_rr_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int16_t *addr, bus_size_t count)
{
	while (count--) {
		*addr++ = bus_space_read_2(t, bsh, offset);
		offset += sizeof(*addr);
	}
}

void
__bs_rr_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int32_t *addr, bus_size_t count)
{
	while (count--) {
		*addr++ = bus_space_read_4(t, bsh, offset);
		offset += sizeof(*addr);
	}
}

void
__bs_w_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int8_t value)
{
	*(volatile u_int8_t *)(bsh + offset) = value;
	wbflush();
}

void
__bs_w_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int16_t value)
{
	*(volatile u_int16_t *)(bsh + offset) = value;
	wbflush();
}

void
__bs_w_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    u_int32_t value)
{
	*(volatile u_int32_t *)(bsh + offset) = value;
	wbflush();
}

void
__bs_wm_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int8_t *addr, bus_size_t count)
{
	while (count--)
		bus_space_write_1(t, bsh, offset, *addr++);
}

void
__bs_wm_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int16_t *addr, bus_size_t count)
{
	while (count--)
		bus_space_write_2(t, bsh, offset, *addr++);
}

void
__bs_wm_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int32_t *addr, bus_size_t count)
{
	while (count--)
		bus_space_write_4(t, bsh, offset, *addr++);
}

void
__bs_wr_1(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int8_t *addr, bus_size_t count)
{
	while (count--) {
		bus_space_write_1(t, bsh, offset, *addr++);
		offset += sizeof(*addr);
	}
}

void
__bs_wr_2(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int16_t *addr, bus_size_t count)
{
	while (count--) {
		bus_space_write_2(t, bsh, offset, *addr++);
		offset += sizeof(*addr);
	}
}

void
__bs_wr_4(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t offset,
    const u_int32_t *addr, bus_size_t count)
{
	while (count--) {
		bus_space_write_4(t, bsh, offset, *addr++);
		offset += sizeof(*addr);
	}
}

void
__bs_sm_1(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int8_t value, bus_size_t count)
{
	while (count--)
		bus_space_write_1(t, bsh, offset, value);
}

void
__bs_sm_2(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int16_t value, bus_size_t count)
{
	while (count--)
		bus_space_write_2(t, bsh, offset, value);
}

void
__bs_sm_4(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int32_t value, bus_size_t count)
{
	while (count--)
		bus_space_write_4(t, bsh, offset, value);
}


void
__bs_sr_1(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int8_t value, bus_size_t count)
{
	while (count--) {
		bus_space_write_1(t, bsh, offset, value);
		offset += (value);
	}
}

void
__bs_sr_2(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int16_t value, bus_size_t count)
{
	while (count--) {
		bus_space_write_2(t, bsh, offset, value);
		offset += (value);
	}
}

void
__bs_sr_4(bus_space_tag_t t, bus_space_handle_t bsh,
    bus_size_t offset, u_int32_t value, bus_size_t count)
{
	while (count--) {
		bus_space_write_4(t, bsh, offset, value);
		offset += (value);
	}
}

#define __bs_c_n(n)							\
void __CONCAT(__bs_c_,n)(bus_space_tag_t t, bus_space_handle_t h1,	\
    bus_size_t o1, bus_space_handle_t h2, bus_size_t o2, bus_size_t c)	\
{									\
	bus_size_t o;							\
									\
	if ((h1 + o1) >= (h2 + o2)) {					\
		/* src after dest: copy forward */			\
		for (o = 0; c != 0; c--, o += n)			\
			__CONCAT(bus_space_write_,n)(t, h2, o2 + o,	\
			    __CONCAT(bus_space_read_,n)(t, h1, o1 + o));\
	} else {							\
		/* dest after src: copy backwards */			\
		for (o = (c - 1) * n; c != 0; c--, o -= n)		\
			__CONCAT(bus_space_write_,n)(t, h2, o2 + o,	\
			    __CONCAT(bus_space_read_,n)(t, h1, o1 + o));\
	}								\
}

__bs_c_n(1)
__bs_c_n(2)
__bs_c_n(4)
