/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
 */
#include <stdarg.h>
#include <stddef.h>
#include <asm/pdc.h>
#include "bootloader.h"
#undef PAGE0
#define	PAGE0	((struct zeropage *)0x00000000)


#define HPHW_A_DIRECT 5
#define MUX_SVERSION 0x0d

#define F_EXTEND(x) ((unsigned long)((x) | (0xffffffff00000000ULL)))
#define COMMAND_GLOBAL  F_EXTEND(0xfffe0030)
#define CMD_RESET       5	/* reset any module */

void die(const char *s)
{
    puts(s);
}

static int firmware_is_wide;
static long long mem_pdc;

void
firmware_init(int started_wide)
{
    mem_pdc = PAGE0->mem_pdc;
    mem_pdc |= (unsigned long long) PAGE0->mem_pdc_hi << 32;

    firmware_is_wide = started_wide;
}

/* pdc_result[] is big enough for either narrow or wide calls */
static unsigned pdc_result[64] __attribute__ ((aligned (8)));
static char iodc_string[512]   __attribute__ ((aligned (64)));

struct wide_stack {
	unsigned long long arg0;
	unsigned long long arg1;
	unsigned long long arg2;
	unsigned long long arg3;
	unsigned long long arg4;
	unsigned long long arg5;
	unsigned long long arg6;
	unsigned long long arg7;
	unsigned long long arg8;
	unsigned long long arg9;
	unsigned long long arg10;
	unsigned long long arg11;
	unsigned long long arg12;
	unsigned long long arg13;
	unsigned long long frame_marker[2];	/* rp, previous sp */
	unsigned long long sp;
	/* in reality, there's nearly 8k of stack after this */
};

static int
firmware_call(unsigned long long fn, ...)
{
    va_list args;
    int r;

    if (firmware_is_wide)
    {
	extern struct wide_stack real_stack;
	extern unsigned int real64_call_asm(unsigned long long *,
					    unsigned long long *, 
					    unsigned long long);

	va_start(args, fn);
	real_stack.arg0 = va_arg(args, unsigned long);
	real_stack.arg1 = va_arg(args, unsigned long);
	real_stack.arg2 = va_arg(args, unsigned long);
	real_stack.arg3 = va_arg(args, unsigned long);
	real_stack.arg4 = va_arg(args, unsigned long);
	real_stack.arg5 = va_arg(args, unsigned long);
	real_stack.arg6 = va_arg(args, unsigned long);
	real_stack.arg7 = va_arg(args, unsigned long);
	real_stack.arg8 = va_arg(args, unsigned long);
	real_stack.arg9 = va_arg(args, unsigned long);
	real_stack.arg10 = va_arg(args, unsigned long);
	real_stack.arg11 = va_arg(args, unsigned long);
	real_stack.arg12 = va_arg(args, unsigned long);
	real_stack.arg13 = va_arg(args, unsigned long);
	va_end(args);

	r = real64_call_asm(&real_stack.sp, &real_stack.arg0, fn);
    }
    else
    {
	typedef int (*firmware_entry)();
	unsigned long arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
	    arg9, arg10, arg11, arg12, arg13;

	va_start(args, fn);
	arg0 = va_arg(args, unsigned long);
	arg1 = va_arg(args, unsigned long);
	arg2 = va_arg(args, unsigned long);
	arg3 = va_arg(args, unsigned long);
	arg4 = va_arg(args, unsigned long);
	arg5 = va_arg(args, unsigned long);
	arg6 = va_arg(args, unsigned long);
	arg7 = va_arg(args, unsigned long);
	arg8 = va_arg(args, unsigned long);
	arg9 = va_arg(args, unsigned long);
	arg10 = va_arg(args, unsigned long);
	arg11 = va_arg(args, unsigned long);
	arg12 = va_arg(args, unsigned long);
	arg13 = va_arg(args, unsigned long);
	va_end(args);

	r = (*(firmware_entry) (unsigned int) fn) (arg0, arg1, arg2, arg3, arg4,
	    arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13);
    }

    return r;
}

void
convert_from_wide(unsigned *retbuf)
{
    if (firmware_is_wide)
    {
	*retbuf = *(unsigned long long *)retbuf;
    }
}

/* flag=true means enable default wide mode.
 */
int
pdc_default_width(int wide)
{
    int r;
    int mask;

    /* Ask firmware which default PSW bits we're allowed to set */

    r = firmware_call(mem_pdc, PDC_PSW, PDC_PSW_MASK, pdc_result);
    convert_from_wide(pdc_result);
    switch(r)
    {
    case 0:		/* PDC call worked */
	mask = pdc_result[0];
	if (wide && ((mask & PDC_PSW_WIDE_BIT) == 0))
	{
	    die("Firmware does not allow selection of default wide mode.\n"
	    	"Are you trying to boot a 64-bit kernel on a 32-bit box?");
	    return 1;
	}
	/* get the current default bit settings */
	firmware_call(mem_pdc, PDC_PSW, PDC_PSW_GET_DEFAULTS, pdc_result);
	convert_from_wide(pdc_result);
	if (wide)
	{
	    if ((mask & PDC_PSW_WIDE_BIT) == 0) {
		die("Firmware does not allow selection of default wide mode.\n"
		    "Are you trying to boot a 64-bit kernel on a 32-bit box?");
		return 1;
	    }
	    pdc_result[0] |= PDC_PSW_WIDE_BIT;
	}
	else
	{
	    pdc_result[0] &= ~PDC_PSW_WIDE_BIT;
	}
	pdc_result[0] &= ~PDC_PSW_ENDIAN_BIT;
	/* Ask firmware to set the W bit appropriately */
	r = firmware_call(mem_pdc, PDC_PSW, PDC_PSW_SET_DEFAULTS, pdc_result[0]);
	if (r < 0)
	{
	    printf("PDC_PSW_SET_DEFAULTS returns error %d\n", r);
	    die("Requested default wide/narrow mode not set");
	    return 1;
	}
	if (0) printf("Set default PSW W bit to %d\n", wide);
	break;
    case -2:	/* unsupported PDC call */
    default:
	/* assume that when this fails, it's an older machine which */
	/* doesn't support wide mode */
	if (wide)
	{
	    die("Can't select default wide mode, PDC_PSW call does not work");
	    return 1;
	}
	else /* narrow */
	{
	    if (0) printf("Warning: narrow mode requested, PDC_PSW call fails\n");
	}
	break;
    }
    return 0;
}

int
pdc_os_bits()
{
    int r;
    int osbits = 0x2;	/* default to 32-bit OS */

    r = firmware_call(mem_pdc, PDC_MODEL, PDC_MODEL_CAPABILITIES, pdc_result);
    convert_from_wide(pdc_result);
    if (r < 0)
    {
	if (0) printf("Annoyance: Firmware does not support PDC_MODEL_CAPABILITIES call\n");
    }
    else
    {
	osbits = pdc_result[0];
	/* printf("Firmware OS bits = %d\n", osbits); */
    }

    return osbits;
}

int
pdc_coproc_cfg(void)
{
    int r;

    r = firmware_call(mem_pdc, PDC_COPROC, PDC_COPROC_CFG, pdc_result);
    convert_from_wide(pdc_result);
    if (r == PDC_OK) {
		return pdc_result[0]; /* ccr_functional */
        } else {
		return 0;
        }
}

int
pdc_model_sysmodel(char *name)
{
    int r;

    r = firmware_call(mem_pdc, PDC_MODEL, PDC_MODEL_SYSMODEL, pdc_result,
				OS_ID_HPUX, name);
    convert_from_wide(pdc_result);
    if (r == PDC_OK) {
		name[pdc_result[0]] = '\0'; /* add trailing '\0' */
        } else {
		name[0] = 0;
        }

    return r;
}

/*
  See Pdc11-v0.96-Ch3-IODC.pdf at https://parisc.docs.kernel.org/en/latest/technical_documentation.html
  The IODC_FEATURES (byte 10) byte specifies which optional IODC feature are
  supported by this module.  The rightmost bit (block field) specifies whether
  ENTRY_IO support block input (ARG1=16) and Boot block output (ARG1=17) are
  supported.
 */
int
pdc_bootdisk_2GB_limit(void)
{
    int r;
    struct pdc_iodc iodc __attribute__ ((aligned (8)));

    iodc.features = 0;
    r = firmware_call(mem_pdc, PDC_IODC, PDC_IODC_READ,
                pdc_result, PAGE0->mem_boot.hpa,
		PDC_IODC_INDEX_DATA, &iodc, sizeof(iodc));

    /* check boot block feature */
    if (r >= 0)
	return (iodc.features & 1) == 0;

    return 1;
}

int
pdc_cons_duplex()
{
    return (PAGE0->mem_cons.cl_class == CL_DUPLEX);
}

int
pdc_cons_mux(int *is_mux)
{
    int r;
    unsigned char hw_type;    /* 5  bits used */
    unsigned int sversion;    /* 20 bits used */
    unsigned long pdc_result2[32] __attribute__ ((aligned (8)));

    *is_mux = 0;

    r = firmware_call(mem_pdc, PDC_IODC, PDC_IODC_READ,
                pdc_result, PAGE0->mem_cons.hpa,
		PDC_IODC_INDEX_DATA, pdc_result2, 32);
                
    if (r >= 0)
    {
        unsigned char iodc_data[8];
        memcpy(&iodc_data, pdc_result2, 8);

        hw_type = iodc_data[3] & 0x1f;
        sversion = ((iodc_data[4] & 0x0f) << 16) | (iodc_data[5] << 8) | iodc_data[6];

        if(hw_type == HPHW_A_DIRECT && sversion == MUX_SVERSION)
            *is_mux = 1;

	return PDC_OK;
    }

    return r;				/* r < 0; error */
}

int
pdc_iodc_cin(char *buf, int size)
{
    int r;

    struct pz_device *in = pdc_cons_duplex() ?
				&PAGE0->mem_cons : &PAGE0->mem_kbd;

    if (size >= sizeof iodc_string)
	asm("\npdc_iodc_cin_test1fail: b,n .");

    r = firmware_call(in->iodc_io,
		in->hpa, ENTRY_IO_CIN,
		in->spa, in->dp.layers,
		pdc_result, 0, iodc_string, size, 0);

    if (r >= 0)
    {
	convert_from_wide(pdc_result);
	memcpy(buf, iodc_string, pdc_result[0]);
	return pdc_result[0];		/* count */
    }

    return r;				/* r < 0; error */
}

void
pdc_iodc_cout(const char *s, int size)
{
    int r, len;

    if (s[0] == 0)
	/* this test is usually the one to catch overwriting palo with kernel */
	while (1) { /* endless loop */ };

    while (size) {
	    if (size >= sizeof iodc_string)
		len = sizeof iodc_string;
	    else
		len = size;

	    memcpy(iodc_string, s, len);

	    size -= len;
	    s += len;

	    r = firmware_call(PAGE0->mem_cons.iodc_io,
			PAGE0->mem_cons.hpa, ENTRY_IO_COUT,
			PAGE0->mem_cons.spa, PAGE0->mem_cons.dp.layers,
			pdc_result, 0, iodc_string, len, 0);

	    if (r != 0)
		while (1) { /* endless loop */ };
    }
}

int
pdc_iodc_bootin(__u64 devaddr, char *memaddr, unsigned size)
{
    int r = -1;

    /* limit transfers per PDC call to this size. PDC on C8000 had issues. */
    if (size > MAX_IO_LIMIT)
	size = MAX_IO_LIMIT;

    if (!disk_2gb_limit) {
       unsigned long a = devaddr / FW_BLOCKSIZE;
       unsigned long s = (size + FW_BLOCKSIZE - 1) / FW_BLOCKSIZE;
       r = firmware_call(PAGE0->mem_boot.iodc_io,
		    PAGE0->mem_boot.hpa, ENTRY_IO_BBLOCK_IN,
		    PAGE0->mem_boot.spa, PAGE0->mem_boot.dp.layers,
		    pdc_result, a, memaddr, s, s);
	if (r >= 0)
	{
		convert_from_wide(pdc_result);
		/* ENTRY_IO_BBLOCK_IN returns blocks, not bytes */
		return pdc_result[0] * FW_BLOCKSIZE;	/* return count in bytes */
	}
    }

    if (r < 0) {
        /* check for overflow - ENTRY_IO_BOOTIN allows up to 2GB only */
        if (devaddr >> 31)
                r = PDC_INVALID_ARG;
        else
                r = firmware_call(PAGE0->mem_boot.iodc_io,
		    PAGE0->mem_boot.hpa, ENTRY_IO_BOOTIN,
		    PAGE0->mem_boot.spa, PAGE0->mem_boot.dp.layers,
		    pdc_result, (unsigned long)devaddr, memaddr, size, size);
    }

    if (r == 3)	/* EOF */
    {
	return 0;			/* count = 0 at EOF */
    }
    else if (r >= 0)
    {
	convert_from_wide(pdc_result);
	return pdc_result[0];		/* count */
    }
    return r;				/* r < 0; error */
}

int
pdc_read_conspath(unsigned char *memaddr)
{
    int r;
    
    r = firmware_call(mem_pdc, PDC_STABLE, 0, 96, memaddr, 8);

    if (r >= 0)
    {
	convert_from_wide(pdc_result);
	return pdc_result[0];		/* count */
    }
    return r;				/* r < 0; error */
}

static inline void gsc_writel(unsigned int val, unsigned long addr)
{
        __asm__ __volatile__(
        "       stwas   %0,0(%1)\n"
        : :  "r" (val), "r" (addr) );
}

int
pdc_do_reset(void)
{
    printf("Resetting machine.\n");

    /* reset the machine - this call will most likely not return. */
    firmware_call(mem_pdc, PDC_BROADCAST_RESET, PDC_DO_RESET);

    /* Nope...box should reset with just CMD_RESET now */
    gsc_writel(CMD_RESET, COMMAND_GLOBAL);

    /* Wait for RESET to lay us to rest. */
    while (1) /* wait */;

    return -1;
}
