/* $NetBSD: machdep.c,v 1.53 2018/07/15 05:16:41 maxv Exp $ */
/*
* Copyright (C) 1995, 1996 Wolfgang Solfrank.
* Copyright (C) 1995, 1996 TooLs GmbH.
* 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 TooLs GmbH.
* 4. The name of TooLs GmbH may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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: machdep.c,v 1.53 2018/07/15 05:16:41 maxv Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/mount.h>
#include <sys/msgbuf.h>
#include <sys/kernel.h>
#include <sys/reboot.h>
#include <sys/ksyms.h>
#include <uvm/uvm_extern.h>
#include <dev/cons.h>
#include <machine/autoconf.h>
#include <machine/powerpc.h>
#include <powerpc/oea/bat.h>
#include <powerpc/pic/picvar.h>
#include <amiga/amiga/cc.h>
#include <amiga/amiga/cia.h>
#include <amiga/amiga/custom.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/isr.h>
#include <amiga/amiga/memlist.h>
#include <amigappc/amigappc/p5reg.h>
#include "opt_ddb.h"
#include "fd.h"
#include "ser.h"
extern void setup_amiga_intr(void);
#if NSER > 0
extern void ser_outintr(void);
extern void ser_fastint(void);
#endif
#if NFD > 0
extern void fdintr(int);
#endif
#define AMIGAMEMREGIONS 8
static struct mem_region physmemr[AMIGAMEMREGIONS], availmemr[AMIGAMEMREGIONS];
static char model[80];
/*
* patched by some devices at attach time (currently, only the coms)
*/
int amiga_serialspl = 4;
/*
* current open serial device speed; used by some SCSI drivers to reduce
* DMA transfer lengths.
*/
int ser_open_speed;
/* interrupt handler chains for level2 and level6 interrupt */
struct isr *isr_ports;
struct isr *isr_exter;
extern void *startsym, *endsym;
void
add_isr(struct isr *isr)
{
struct isr **p, *q;
p = isr->isr_ipl == 2 ? &isr_ports : &isr_exter;
while ((q = *p) != NULL)
p = &q->isr_forw;
isr->isr_forw = NULL;
*p = isr;
/* enable interrupt */
custom.intena = isr->isr_ipl == 2 ?
INTF_SETCLR | INTF_PORTS :
INTF_SETCLR | INTF_EXTER;
}
void
remove_isr(struct isr *isr)
{
struct isr **p, *q;
p = isr->isr_ipl == 6 ? &isr_exter : &isr_ports;
while ((q = *p) != NULL && q != isr)
p = &q->isr_forw;
if (q)
*p = q->isr_forw;
else
panic("remove_isr: handler not registered");
/* disable interrupt if no more handlers */
p = isr->isr_ipl == 6 ? &isr_exter : &isr_ports;
if (*p == NULL) {
custom.intena = isr->isr_ipl == 6 ?
INTF_EXTER : INTF_PORTS;
}
}
static int
ports_intr(void *arg)
{
struct isr **p;
struct isr *q;
p = (struct isr **)arg;
while ((q = *p) != NULL) {
if ((q->isr_intr)(q->isr_arg))
break;
p = &q->isr_forw;
}
if (q == NULL)
ciaa_intr(); /* ciaa handles keyboard and parallel port */
custom.intreq = INTF_PORTS;
return 0;
}
static int
exter_intr(void *arg)
{
struct isr **p;
struct isr *q;
p = (struct isr **)arg;
while ((q = *p) != NULL) {
if ((q->isr_intr)(q->isr_arg))
break;
p = &q->isr_forw;
}
if (q == NULL)
ciab_intr(); /* clear ciab icr */
custom.intreq = INTF_EXTER;
return 0;
}
static int
lev1_intr(void *arg)
{
unsigned short ireq;
ireq = custom.intreqr;
if (ireq & INTF_TBE) {
#if NSER > 0
ser_outintr();
#else
custom.intreq = INTF_TBE;
#endif
}
if (ireq & INTF_DSKBLK) {
#if NFD > 0
fdintr(0);
#endif
custom.intreq = INTF_DSKBLK;
}
if (ireq & INTF_SOFTINT) {
#ifdef DEBUG
printf("intrhand: SOFTINT ignored\n");
#endif
custom.intreq = INTF_SOFTINT;
}
return 0;
}
static int
lev3_intr(void *arg)
{
unsigned short ireq;
ireq = custom.intreqr;
if (ireq & INTF_BLIT)
blitter_handler();
if (ireq & INTF_COPER)
copper_handler();
if (ireq & INTF_VERTB)
vbl_handler();
return 0;
}
static int
lev4_intr(void *arg)
{
audio_handler();
return 0;
}
static int
lev5_intr(void *arg)
{
ser_fastint();
if (custom.intreqr & INTF_DSKSYNC)
custom.intreq = INTF_DSKSYNC;
return 0;
}
static void
amigappc_install_handlers(void)
{
/* handlers for all 6 Amiga interrupt levels */
intr_establish(1, IST_LEVEL, IPL_BIO, lev1_intr, NULL);
intr_establish(2, IST_LEVEL, IPL_BIO, ports_intr, &isr_ports);
intr_establish(3, IST_LEVEL, IPL_TTY, lev3_intr, NULL);
intr_establish(4, IST_LEVEL, IPL_AUDIO, lev4_intr, NULL);
intr_establish(5, IST_LEVEL, IPL_SERIAL, lev5_intr, NULL);
intr_establish(6, IST_LEVEL, IPL_SERIAL, exter_intr, &isr_exter);
}
static void
amigappc_identify(void)
{
extern u_long ns_per_tick, ticks_per_sec;
static const unsigned char pll[] = {
10, 10, 70, 10, 20, 65, 25, 45,
30, 55, 40, 50, 15, 60, 35, 00
};
const char *cpuname, *mach, *p5type_p, *pup;
u_long busclock, cpuclock;
register int pvr, hid1;
/* PowerUp ROM id location */
p5type_p = (const char *)0xf00010;
/*
* PVR holds the CPU-type and version while
* HID1 holds the PLL configuration
*/
__asm ("mfpvr %0; mfspr %1,1009" : "=r"(pvr), "=r"(hid1));
/* Amiga types which can run a PPC board */
if (is_a4000())
mach = "Amiga 4000";
else if (is_a3000())
mach = "Amiga 3000";
else
mach = "Amiga 1200";
/* find CPU type - BlizzardPPC has 603e/603ev, CyberstormPPC has 604e */
switch (pvr >> 16) {
case 3:
cpuname = "603";
break;
case 4:
cpuname = "604";
break;
case 6:
cpuname = "603e";
break;
case 7:
cpuname = "603ev";
break;
case 9:
case 10:
cpuname = "604e";
break;
default:
cpuname = "unknown";
break;
}
switch (p5type_p[0]) {
case 'D':
pup = "[PowerUP]";
break;
case 'E':
pup = "[CSPPC]";
break;
case 'F':
pup = "[CS Mk.III]";
break;
case 'H':
case 'I':
pup = "[BlizzardPPC]";
break;
default:
pup = "";
break;
}
/* XXX busclock can be measured with CIA-timers */
switch (p5type_p[1]) {
case 'A':
busclock = 60000000;
break;
/* case B, C, D */
default:
busclock = 66666667;
break;
}
/* compute cpuclock based on PLL configuration */
cpuclock = busclock * pll[hid1>>28 & 0xf] / 10;
snprintf(model, sizeof(model),
"%s %s (%s v%d.%d %lu MHz, busclk %lu MHz)",
mach, pup, cpuname, (pvr>>8) & 0xf, (pvr >> 0) & 0xf,
cpuclock / 1000000, busclock / 1000000);
/* set timebase */
ticks_per_sec = busclock / 4;
ns_per_tick = 1000000000 / ticks_per_sec;
}
static void
amigappc_reboot(void)
{
/* reboot CSPPC/BPPC */
if (!is_a1200()) {
P5write(P5_REG_LOCK,0x60);
P5write(P5_REG_LOCK,0x50);
P5write(P5_REG_LOCK,0x30);
P5write(P5_REG_SHADOW,P5_SET_CLEAR|P5_SHADOW);
P5write(P5_REG_LOCK,0x00);
} else
P5write(P5_BPPC_MAGIC,0x00);
P5write(P5_REG_LOCK,0x60);
P5write(P5_REG_LOCK,0x50);
P5write(P5_REG_LOCK,0x30);
P5write(P5_REG_SHADOW,P5_SELF_RESET);
P5write(P5_REG_RESET,P5_AMIGA_RESET);
}
static void
amigappc_bat_add(paddr_t pa, register_t len, register_t prot)
{
static int nd = 0, ni = 0;
const uint32_t i = pa >> 28;
battable[i].batl = BATL(pa, prot, BAT_PP_RW);
battable[i].batu = BATU(pa, len, BAT_Vs);
/*
* Let's start loading the BAT registers.
*/
if (!(prot & (BAT_I | BAT_G))) {
switch (ni) {
case 0:
__asm volatile ("isync");
__asm volatile ("mtibatl 0,%0; mtibatu 0,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
ni = 1;
break;
case 1:
__asm volatile ("isync");
__asm volatile ("mtibatl 1,%0; mtibatu 1,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
ni = 2;
break;
case 2:
__asm volatile ("isync");
__asm volatile ("mtibatl 2,%0; mtibatu 2,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
ni = 3;
break;
case 3:
__asm volatile ("isync");
__asm volatile ("mtibatl 3,%0; mtibatu 3,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
ni = 4;
break;
default:
break;
}
}
switch (nd) {
case 0:
__asm volatile ("isync");
__asm volatile ("mtdbatl 0,%0; mtdbatu 0,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
nd = 1;
break;
case 1:
__asm volatile ("isync");
__asm volatile ("mtdbatl 1,%0; mtdbatu 1,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
nd = 2;
break;
case 2:
__asm volatile ("isync");
__asm volatile ("mtdbatl 2,%0; mtdbatu 2,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
nd = 3;
break;
case 3:
__asm volatile ("isync");
__asm volatile ("mtdbatl 3,%0; mtdbatu 3,%1;"
:: "r"(battable[i].batl),
"r"(battable[i].batu));
__asm volatile ("isync");
nd = 4;
break;
default:
break;
}
}
static void
amigappc_batinit(paddr_t pa, ...)
{
va_list ap;
register_t msr;
msr = mfmsr();
/*
* Set BAT registers to unmapped to avoid overlapping mappings below.
*/
if ((msr & (PSL_IR|PSL_DR)) == 0) {
__asm volatile ("isync");
__asm volatile ("mtibatu 0,%0" :: "r"(0));
__asm volatile ("mtibatu 1,%0" :: "r"(0));
__asm volatile ("mtibatu 2,%0" :: "r"(0));
__asm volatile ("mtibatu 3,%0" :: "r"(0));
__asm volatile ("mtdbatu 0,%0" :: "r"(0));
__asm volatile ("mtdbatu 1,%0" :: "r"(0));
__asm volatile ("mtdbatu 2,%0" :: "r"(0));
__asm volatile ("mtdbatu 3,%0" :: "r"(0));
__asm volatile ("isync");
}
/*
* Setup BATs
*/
va_start(ap, pa);
while (pa != ~0) {
register_t len, prot;
len = va_arg(ap, register_t);
prot = va_arg(ap, register_t);
amigappc_bat_add(pa, len, prot);
pa = va_arg(ap, paddr_t);
}
va_end(ap);
}
void
initppc(u_int startkernel, u_int endkernel)
{
struct boot_memseg *ms;
u_int i, r;
/*
* amigappc memory region set
*/
ms = &memlist->m_seg[0];
for (i = 0, r = 0; i < memlist->m_nseg; i++, ms++) {
if (ms->ms_attrib & MEMF_FAST) {
/*
* XXX Only recognize the memory segment in which
* the kernel resides, for now
*/
if (ms->ms_start <= startkernel &&
ms->ms_start + ms->ms_size > endkernel) {
physmemr[r].start = (paddr_t)ms->ms_start;
physmemr[r].size = (psize_t)ms->ms_size & ~PGOFSET;
availmemr[r].start = (endkernel + PGOFSET) & ~PGOFSET;
availmemr[r].size = (physmemr[0].start +
physmemr[0].size)
- availmemr[0].start;
}
r++;
}
}
physmemr[r].start = 0;
physmemr[r].size = 0;
availmemr[r].start = 0;
availmemr[r].size = 0;
if (r == 0)
panic("initppc: no suitable memory segment found");
/*
* Initialize BAT tables.
* The CSPPC RAM (A3000/A4000) always starts at 0x08000000 and is
* up to 128MB big.
* The BPPC RAM (A1200) can be up to 256MB and may start at nearly
* any address between 0x40000000 and 0x80000000 depending on which
* RAM module of which size was inserted into which bank:
* The RAM module in bank 1 is located from 0x?8000000 downwards.
* The RAM module in bank 2 is located from 0x?8000000 upwards.
* Whether '?' is 4, 5, 6 or 7 probably depends on the size.
* So we have to use the 'startkernel' symbol for BAT-mapping
* our RAM.
*/
if (is_a1200()) {
amigappc_batinit(0x00000000, BAT_BL_16M, BAT_I|BAT_G,
(startkernel & 0xf0000000), BAT_BL_256M, 0,
0xfff00000, BAT_BL_512K, 0,
~0);
} else {
/* A3000 or A4000 */
amigappc_batinit(0x00000000, BAT_BL_16M, BAT_I|BAT_G,
(startkernel & 0xf8000000), BAT_BL_128M, 0,
0xfff00000, BAT_BL_512K, 0,
0x40000000, BAT_BL_256M, BAT_I|BAT_G,
~0);
}
/*
* Set up trap vectors and interrupt handler
*/
oea_init(NULL);
/* XXX bus_space_init() not needed here */
uvm_md_init();
/*
* Initialize pmap module
*/
pmap_bootstrap(startkernel, endkernel);
#if NKSYMS || defined(DDB) || defined(MODULAR)
ksyms_addsyms_elf((int)((u_int)endsym - (u_int)startsym), startsym, endsym);
#endif
/*
* CPU model, bus clock, timebase
*/
amigappc_identify();
#ifdef DDB
if (boothowto & RB_KDB)
Debugger();
#endif
}
/*
* This is called during initppc, before the system is really initialized.
* It shall provide the total and the available regions of RAM.
* Both lists must have a zero-size entry as terminator.
* The available regions need not take the kernel into account, but needs
* to provide space for two additional entry beyond the terminating one.
*/
void
mem_regions(struct mem_region **memp, struct mem_region **availp)
{
*memp = physmemr;
*availp = availmemr;
}
/*
* Machine dependent startup code
*/
void
cpu_startup(void)
{
int msr;
/*
* hello world
*/
oea_startup(model);
/* Setup interrupts */
pic_init();
setup_amiga_intr();
amigappc_install_handlers();
oea_install_extint(pic_ext_intr);
/*
* Now allow hardware interrupts.
*/
splhigh();
__asm volatile ("mfmsr %0; ori %0,%0,%1; mtmsr %0"
: "=r"(msr)
: "K"(PSL_EE));
#if 0 /* XXX Not in amiga bus.h - fix it? */
/*
* Now that we have VM, malloc's are OK in bus_space.
*/
bus_space_mallocok();
#endif
}
/*
* consinit
* Initialize system console.
*/
void
consinit(void)
{
/* preconfigure graphics cards */
custom_chips_init();
config_console();
/* Initialize the console before we print anything out. */
cninit();
}
/*
* Halt or reboot the machine after syncing/dumping according to howto
*/
void
cpu_reboot(int howto, char *what)
{
static int syncing;
if (cold) {
howto |= RB_HALT;
goto halt_sys;
}
boothowto = howto;
if ((howto & RB_NOSYNC) == 0 && syncing == 0) {
syncing = 1;
vfs_shutdown(); /* sync */
resettodr(); /* set wall clock */
}
/* Disable intr */
splhigh();
/* Do dump if requested */
if ((howto & (RB_DUMP | RB_HALT)) == RB_DUMP) {
oea_dumpsys();
/* XXX dumpsys doesn't work, so give a chance to debug */
Debugger();
}
halt_sys:
doshutdownhooks();
if (howto & RB_HALT) {
printf("\n");
printf("The operating system has halted.\n");
printf("Please press any key to reboot.\n\n");
cnpollc(1); /* for proper keyboard command handling */
cngetc();
cnpollc(0);
}
printf("rebooting...\n\n");
delay(1000000);
amigappc_reboot();
for (;;);
/* NOTREACHED */
}
/*
* Try to emulate the functionality from m68k/m68k/sys_machdep.c
* used by several amiga scsi drivers.
*/
int
dma_cachectl(void *addr, int len)
{
paddr_t pa, end;
int inc;
if (addr == NULL || len == 0)
return 0;
pa = kvtop(addr);
inc = curcpu()->ci_ci.dcache_line_size;
for (end = pa + len; pa < end; pa += inc)
__asm volatile("dcbf 0,%0" :: "r"(pa));
__asm volatile("sync");
#if 0 /* XXX not needed, we don't have instructions in DMA buffers */
pa = kvtop(addr);
for (end = pa + len; pa < end; pa += inc)
__asm volatile("icbi 0,%0" :: "r"(pa));
__asm volatile("isync");
#endif
return 0;
}