/*	$NetBSD: cats_machdep.c,v 1.96 2024/10/21 13:37:07 skrll Exp $	*/

/*
 * Copyright (c) 1997,1998 Mark Brinicombe.
 * Copyright (c) 1997,1998 Causality Limited.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Mark Brinicombe
 *	for the NetBSD Project.
 * 4. The name of the company nor the name of the author may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * 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 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.
 *
 * Machine dependent functions for kernel setup for EBSA285 core architecture
 * using cyclone firmware
 *
 * Created      : 24/11/97
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: cats_machdep.c,v 1.96 2024/10/21 13:37:07 skrll Exp $");

#include "opt_arm_debug.h"
#include "opt_cats.h"
#include "opt_ddb.h"
#include "opt_modular.h"

#include "isadma.h"

#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/exec.h>
#include <sys/proc.h>
#include <sys/msgbuf.h>
#include <sys/reboot.h>
#include <sys/termios.h>
#include <sys/ksyms.h>
#include <sys/cpu.h>
#include <sys/intr.h>

#include <dev/cons.h>

#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
#include <ddb/db_extern.h>

#include <machine/bootconfig.h>
#define	_ARM32_BUS_DMA_PRIVATE
#include <sys/bus.h>
#include <arm/locore.h>
#include <arm/undefined.h>
#include <arm/arm32/machdep.h>

#include <machine/cyclone_boot.h>
#include <arm/footbridge/dc21285mem.h>
#include <arm/footbridge/dc21285reg.h>

#include "ksyms.h"
#include "opt_ableelf.h"

#include "isa.h"
#if NISA > 0
#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>
#endif

#ifdef VERBOSE_INIT_ARM
#define VPRINTF(...)	printf(__VA_ARGS__)
#else
#define VPRINTF(...)	__nothing
#endif

/* Kernel text starts at the base of the kernel address space. */
#define	KERNEL_TEXT_BASE	(KERNEL_BASE + 0x00000000)
#define	KERNEL_VM_BASE		(KERNEL_BASE + 0x01000000)

/*
 * The range 0xf1000000 - 0xfcffffff is available for kernel VM space
 * Footbridge registers and I/O mappings occupy 0xfd000000 - 0xffffffff
 */

/*
 * Size of available KVM space, note that growkernel will grow into this.
 */
#define KERNEL_VM_SIZE	0x0c000000

/*
 * Address to call from cpu_reset() to reset the machine.
 * This is machine architecture dependent as it varies depending
 * on where the ROM appears when you turn the MMU off.
 */

u_int dc21285_fclk = FCLK;

struct ebsaboot ebsabootinfo;
BootConfig bootconfig;		/* Boot config storage */
static char bootargs[MAX_BOOT_STRING + 1];
char *boot_args = NULL;
char *boot_file = NULL;

/* Prototypes */

void consinit(void);

int fcomcnattach(u_int iobase, int rate, tcflag_t cflag);
int fcomcndetach(void);

static void process_kernel_args(const char *);
extern void configure(void);

/* A load of console goo. */
#include "vga.h"
#if (NVGA > 0)
#include <dev/ic/mc6845reg.h>
#include <dev/ic/pcdisplayvar.h>
#include <dev/ic/vgareg.h>
#include <dev/ic/vgavar.h>
#endif

#include "pckbc.h"
#if (NPCKBC > 0)
#include <dev/ic/i8042reg.h>
#include <dev/ic/pckbcvar.h>
#endif

#include "com.h"
#if (NCOM > 0)
#include <dev/ic/comreg.h>
#include <dev/ic/comvar.h>
#ifndef CONCOMADDR
#define CONCOMADDR 0x3f8
#endif
#endif

#ifndef CONSDEVNAME
#define CONSDEVNAME "vga"
#endif

#define CONSPEED B38400
#ifndef CONSPEED
#define CONSPEED B9600	/* TTYDEF_SPEED */
#endif
#ifndef CONMODE
#define CONMODE ((TTYDEF_CFLAG & ~(CSIZE | CSTOPB | PARENB)) | CS8) /* 8N1 */
#endif

int comcnspeed = CONSPEED;
int comcnmode = CONMODE;

static const struct pmap_devmap cats_devmap[] = {
	/* Map 1MB for CSR space */
	DEVMAP_ENTRY(DC21285_ARMCSR_VBASE,
		     DC21285_ARMCSR_BASE,
		     DC21285_ARMCSR_VSIZE),

	/* Map 1MB for fast cache cleaning space */
	DEVMAP_ENTRY(DC21285_CACHE_FLUSH_VBASE,
		     DC21285_SA_CACHE_FLUSH_BASE,
		     DC21285_CACHE_FLUSH_VSIZE),

	/* Map 1MB for PCI IO space */
	DEVMAP_ENTRY(DC21285_PCI_IO_VBASE,
		     DC21285_PCI_IO_BASE,
		     DC21285_PCI_IO_VSIZE),

	/* Map 1MB for PCI IACK space */
	DEVMAP_ENTRY(DC21285_PCI_IACK_VBASE,
		     DC21285_PCI_IACK_SPECIAL,
		     DC21285_PCI_IACK_VSIZE),

	/* Map 16MB of type 1 PCI config access */
	DEVMAP_ENTRY(DC21285_PCI_TYPE_1_CONFIG_VBASE,
		     DC21285_PCI_TYPE_1_CONFIG,
		     DC21285_PCI_TYPE_1_CONFIG_VSIZE),

	/* Map 16MB of type 0 PCI config access */
	DEVMAP_ENTRY(DC21285_PCI_TYPE_0_CONFIG_VBASE,
		     DC21285_PCI_TYPE_0_CONFIG,
		     DC21285_PCI_TYPE_0_CONFIG_VSIZE),

	/* Map 1MB of 32 bit PCI address space for ISA MEM accesses via PCI */
	DEVMAP_ENTRY(DC21285_PCI_ISA_MEM_VBASE,
		     DC21285_PCI_MEM_BASE,
		     DC21285_PCI_ISA_MEM_VSIZE),

	DEVMAP_ENTRY_END
};

#define MAX_PHYSMEM 4
static struct boot_physmem cats_physmem[MAX_PHYSMEM];
int ncats_physmem = 0;

extern struct bus_space footbridge_pci_io_bs_tag;
extern struct bus_space footbridge_pci_mem_bs_tag;
void footbridge_pci_bs_tag_init(void);

/*
 * vaddr_t initarm(struct ebsaboot *bootinfo)
 *
 * Initial entry point on startup. This gets called before main() is
 * entered.
 * It should be responsible for setting up everything that must be
 * in place when main is called.
 * This includes
 *   Taking a copy of the boot configuration structure.
 *   Initialising the physical console so characters can be printed.
 *   Setting up page tables for the kernel
 *   Relocating the kernel to the bottom of physical memory
 */

vaddr_t
initarm(void *arm_bootargs)
{
	struct ebsaboot *bootinfo = arm_bootargs;
	extern u_int cpu_get_control(void);

	/*
	 * Heads up ... Setup the CPU / MMU / TLB functions
	 */
	set_cpufuncs();

	/* Copy the boot configuration structure */
	ebsabootinfo = *bootinfo;

	if (ebsabootinfo.bt_fclk >= 50000000
	    && ebsabootinfo.bt_fclk <= 66000000)
		dc21285_fclk = ebsabootinfo.bt_fclk;

	/* Fake bootconfig structure for the benefit of pmap.c */
	/* XXX must make the memory description h/w independent */
	bootconfig.dramblocks = 1;
	bootconfig.dram[0].address = ebsabootinfo.bt_memstart;
	bootconfig.dram[0].pages = (ebsabootinfo.bt_memend
	    - ebsabootinfo.bt_memstart) / PAGE_SIZE;

	/*
	 * Initialise the diagnostic serial console
	 * This allows a means of generating output during initarm().
	 * Once all the memory map changes are complete we can call consinit()
	 * and not have to worry about things moving.
	 */
	pmap_devmap_bootstrap((vaddr_t)ebsabootinfo.bt_l1, cats_devmap);

#ifdef FCOM_INIT_ARM
	fcomcnattach(DC21285_ARMCSR_VBASE, comcnspeed, comcnmode);
#endif

	/* Talk to the user */
	printf("NetBSD/cats booting ...\n");

	if (ebsabootinfo.bt_magic != BT_MAGIC_NUMBER_EBSA
	    && ebsabootinfo.bt_magic != BT_MAGIC_NUMBER_CATS)
		panic("Incompatible magic number %#x passed in boot args",
		    ebsabootinfo.bt_magic);

	/* output the incoming bootinfo */
	VPRINTF("bootinfo @ %p\n", arm_bootargs);
	VPRINTF("bt_magic    = 0x%08x\n", ebsabootinfo.bt_magic);
	VPRINTF("bt_vargp    = 0x%08x\n", ebsabootinfo.bt_vargp);
	VPRINTF("bt_pargp    = 0x%08x\n", ebsabootinfo.bt_pargp);
	VPRINTF("bt_args @ %p, contents = \"%s\"\n", ebsabootinfo.bt_args, ebsabootinfo.bt_args);
	VPRINTF("bt_l1       = %p\n", ebsabootinfo.bt_l1);

	VPRINTF("bt_memstart = 0x%08x\n", ebsabootinfo.bt_memstart);
	VPRINTF("bt_memend   = 0x%08x\n", ebsabootinfo.bt_memend);
	VPRINTF("bt_memavail = 0x%08x\n", ebsabootinfo.bt_memavail);
	VPRINTF("bt_fclk     = 0x%08x\n", ebsabootinfo.bt_fclk);
	VPRINTF("bt_pciclk   = 0x%08x\n", ebsabootinfo.bt_pciclk);
	VPRINTF("bt_vers     = 0x%08x\n", ebsabootinfo.bt_vers);
	VPRINTF("bt_features = 0x%08x\n", ebsabootinfo.bt_features);

	/*
	 * Examine the boot args string for options we need to know about
	 * now.
	 */
	process_kernel_args(ebsabootinfo.bt_args);

	psize_t ram_size = ebsabootinfo.bt_memend - ebsabootinfo.bt_memstart;
	/*
	 * If MEMSIZE specified less than what we really have, limit ourselves
	 * to that.
	*/
#ifdef MEMSIZE
	if (ram_size == 0 || ram_size > (unsigned)MEMSIZE * 1024 * 1024)
		ram_size = (unsigned)MEMSIZE * 1024 * 1024;
	VPRINTF("ram_size = 0x%x\n", (int)ram_size);
#else
	KASSERTMSG(ram_size > 0, "RAM size unknown and MEMSIZE undefined");
#endif

#ifdef __HAVE_MM_MD_DIRECT_MAPPED_PHYS
	const bool mapallmem_p = true;

	if (ram_size > KERNEL_VM_BASE - KERNEL_BASE) {
		printf("%s: dropping RAM size from %luMB to %uMB\n",
		    __func__, (unsigned long) (ram_size >> 20),
		    (KERNEL_VM_BASE - KERNEL_BASE) >> 20);
		ram_size = KERNEL_VM_BASE - KERNEL_BASE;
        }
#else
        const bool mapallmem_p = false;
#endif

	printf("ram_size = 0x%08lx\n", ram_size);

	arm32_bootmem_init(ebsabootinfo.bt_memstart, ram_size,
	    ebsabootinfo.bt_memstart);

	/*
	 * The free block after the kernel from arm32_bootmem_init doesn't
	 * account for bt_memavail.  Adjust for this.
	 */
	extern struct bootmem_info bootmem_info;
	struct bootmem_info * const bmi = &bootmem_info;

	pv_addr_t *pv0 = &bmi->bmi_freeblocks[0];
	KASSERTMSG(pv0->pv_pa == bmi->bmi_kernelend,
	    "pv_pa %#lx kernelend %#lx", pv0->pv_pa, bmi->bmi_kernelend);

	pv0->pv_pa = ebsabootinfo.bt_memavail;
	pv0->pv_va = KERN_PHYSTOV(pv0->pv_pa);
	pv0->pv_size = bmi->bmi_end - pv0->pv_pa;

	printf("First freeblock adjusted to: %lx -> %lx\n", pv0->pv_pa,
	    pv0->pv_pa + pv0->pv_size - 1);

	arm32_kernel_vm_init(KERNEL_VM_BASE, ARM_VECTORS_LOW, 0, cats_devmap,
	    mapallmem_p);

	printf("init subsystems: patch ");

	/*
	 * PATCH PATCH ...
	 *
	 * Fixup the first word of the kernel to be the instruction
	 * add pc, pc, #0x41000000
	 *
	 * This traps the case where the CPU core resets due to bus contention
	 * on a prototype CATS system and will reboot into the firmware.
	 */
	*((u_int *)KERNEL_TEXT_BASE) = 0xe28ff441;

#if NISA > 0
	/* Initialise the ISA subsystem early ... */
	isa_footbridge_init(DC21285_PCI_IO_VBASE, DC21285_PCI_ISA_MEM_VBASE);
#endif

	footbridge_pci_bs_tag_init();

	printf("busses done.\n");

	/* Map the boot arguments page */
	if (ebsabootinfo.bt_vargp != vector_page) {
		pmap_map_entry(kernel_l1pt.pv_va, ebsabootinfo.bt_vargp,
		    ebsabootinfo.bt_pargp, VM_PROT_READ, PTE_CACHE);
	}

	printf("Doing freeblocks: %d\n", bmi->bmi_nfreeblocks);

	for (size_t i = 0; i < bmi->bmi_nfreeblocks; i++) {
		pv_addr_t * const pv = &bmi->bmi_freeblocks[i];
		paddr_t start = pv->pv_pa;
		paddr_t end = pv->pv_pa + pv->pv_size;
		struct boot_physmem *bp;
#if NISADMA > 0
		paddr_t istart, isize;
		extern struct arm32_dma_range *footbridge_isa_dma_ranges;
		extern int footbridge_isa_dma_nranges;
#endif

		VPRINTF("%zu: 0x08%lx -> 0x08%lx\n", i, start, end - 1);

#if NISADMA > 0
		if (arm32_dma_range_intersect(footbridge_isa_dma_ranges,
		   footbridge_isa_dma_nranges, start, end - start,
		   &istart, &isize)) {
			/*
			 * Place the pages that intersect with the
			 * ISA DMA range onto the ISA DMA free list.
			 */
			VPRINTF("    ISADMA 0x08%lx -> 0x%08lx\n", istart,
			    istart + isize - 1);
			bp = &cats_physmem[ncats_physmem++];
			KASSERT(ncats_physmem < MAX_PHYSMEM);
			bp->bp_start = atop(istart);
			bp->bp_pages = atop(isize);
			bp->bp_freelist = VM_FREELIST_ISADMA;

			/*
			 * Load the pieces that come before the
			 * intersection onto the default free list.
			 */
			if (start < istart) {
				VPRINTF("    BEFORE 0x08%lx -> 0x08%lx\n",
				    start, istart - 1);
				bp = &cats_physmem[ncats_physmem++];
				KASSERT(ncats_physmem < MAX_PHYSMEM);
				bp->bp_start = atop(start);
				bp->bp_pages = atop(istart - start);
				bp->bp_freelist = VM_FREELIST_DEFAULT;
			}

			/*
			 * Load the pieces that come after the
			 * intersection onto the default free list.
			 */
			if ((istart + isize) < end) {
				VPRINTF("     AFTER 0x%08lx -> 0x%08lx\n",
				    (istart + isize), end - 1);
				bp = &cats_physmem[ncats_physmem++];
				KASSERT(ncats_physmem < MAX_PHYSMEM);
				bp->bp_start = atop(istart + isize);
				bp->bp_pages = atop(end - (istart + isize));
				bp->bp_freelist = VM_FREELIST_DEFAULT;
			}
		} else {
			bp = &cats_physmem[ncats_physmem++];
			KASSERT(ncats_physmem < MAX_PHYSMEM);
			bp->bp_start = atop(start);
			bp->bp_pages = atop(end - start);
			bp->bp_freelist = VM_FREELIST_DEFAULT;
		}
#else /* NISADMA > 0 */
		bp = &cats_physmem[ncats_physmem++];
		KASSERT(ncats_physmem < MAX_PHYSMEM);
		bp->bp_start = atop(start);
		bp->bp_pages = atop(end - start);
		bp->bp_freelist = VM_FREELIST_DEFAULT;
#endif /* NISADMA > 0 */
	}

	cpu_reset_address_paddr = DC21285_ROM_BASE;

	/* initarm_common returns the new stack pointer address */
	vaddr_t sp;
	sp = initarm_common(KERNEL_VM_BASE, KERNEL_VM_SIZE, cats_physmem,
	    ncats_physmem);

	/* Setup the IRQ system */
	printf("init subsystems: irq ");
	footbridge_intr_init();

	printf("done.\n");

#ifdef FCOM_INIT_ARM
	fcomcndetach();
#endif

#ifdef DDB
	if (boothowto & RB_KDB)
		Debugger();
#endif

	/*
	 * XXX this should only be done in main() but it useful to
	 * have output earlier ...
	 */
	consinit();

	return sp;
}

static void
process_kernel_args(const char *loader_args)
{
	char *args;
	boothowto = 0;

	/* Make a local copy of the bootargs */
	strncpy(bootargs, loader_args, MAX_BOOT_STRING);

	args = bootargs;
	boot_file = bootargs;

	/* Skip the kernel image filename */
	while (*args != ' ' && *args != 0)
		++args;

	if (*args != 0)
		*args++ = 0;

	while (*args == ' ')
		++args;

	boot_args = args;

	printf("bootfile: %s\n", boot_file);
	printf("bootargs: %s\n", boot_args);

	parse_mi_bootargs(boot_args);
}

void
consinit(void)
{
	static int consinit_called = 0;
	const char *console = CONSDEVNAME;

	if (consinit_called != 0)
		return;

	consinit_called = 1;

	get_bootconf_option(boot_args, "console", BOOTOPT_TYPE_STRING,
	    &console);

	if (strncmp(console, "fcom", 4) == 0
	    || strncmp(console, "diag", 4) == 0)
		fcomcnattach(DC21285_ARMCSR_VBASE, comcnspeed, comcnmode);
#if (NVGA > 0)
	else if (strncmp(console, "vga", 3) == 0) {
		vga_cnattach(&footbridge_pci_io_bs_tag,
		    &footbridge_pci_mem_bs_tag, - 1, 0);
#if (NPCKBC > 0)
		pckbc_cnattach(&isa_io_bs_tag, IO_KBD, KBCMDP, PCKBC_KBD_SLOT,
		    0);
#endif	/* NPCKBC */
	}
#endif	/* NVGA */
#if (NCOM > 0)
	else if (strncmp(console, "com", 3) == 0) {
		if (comcnattach(&isa_io_bs_tag, CONCOMADDR, comcnspeed,
		    COM_FREQ, COM_TYPE_NORMAL, comcnmode))
			panic("can't init serial console @%x", CONCOMADDR);
	}
#endif
	/* Don't know what console was requested so use the fall back. */
	else
		fcomcnattach(DC21285_ARMCSR_VBASE, comcnspeed, comcnmode);
}

/* End of cats_machdep.c */
