/*	$NetBSD: sifbios.c,v 1.11 2014/03/31 11:25:49 martin Exp $	*/

/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by UCHIYAMA Yasushi.
 *
 * 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.
 */

/*
 * PlayStation 2 SIF BIOS Version 2.0 interface.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sifbios.c,v 1.11 2014/03/31 11:25:49 martin Exp $");

#include <sys/param.h>
#include <sys/systm.h>

#include <playstation2/playstation2/sifbios.h>
#include <playstation2/playstation2/interrupt.h>

#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif

#define SIFBIOS_ENTRY_PTR	MIPS_PHYS_TO_KSEG0(0x00001000)
#define SIFBIOS_SIGNATURE_PTR	MIPS_PHYS_TO_KSEG1(0x00001004)
#define SIFBIOS_SIGNATURE	(('P' << 0)|('S' << 8)|('2' << 16)|('b' << 24))

STATIC int (*__sifbios_call)(int, void *);
#define CALL(t, n, a)	((t)(*__sifbios_call)((n), (void *)(a)))

STATIC void sifbios_rpc_callback(void *, int);
STATIC int sifbios_rpc_call(int, void *, int *);

void
sifbios_init(void)
{
	/* check BIOS exits */
	if (*(u_int32_t *)SIFBIOS_SIGNATURE_PTR != SIFBIOS_SIGNATURE)
		panic("SIFBIOS not found");
	
	__sifbios_call = *((int (**)(int, void*))SIFBIOS_ENTRY_PTR);
}

int
sifbios_rpc_call(int callno, void *arg, int *result)
{
	volatile int done = 0;
	int retry;
	struct {
		int result;
		void *arg;
		void (*callback)(void *, int);
		volatile void *callback_arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		arg:		arg,
		callback:	sifbios_rpc_callback,
		callback_arg:	(volatile void *)&done,
	};
	
	/* call SIF BIOS */
	retry = 100;
	while (CALL(int, callno, &sifbios_arg) != 0 && --retry > 0)
		delay(20000);	/* .02 sec. for slow IOP */

	if (retry == 0) {
		printf("SIF BIOS call %d failed\n", callno);
		goto error;
	}

	/* wait IOP response (1 sec.) */
	_sif_call_start();
	retry = 10000;
	while (!done && --retry > 0)
		delay(100);
	_sif_call_end();

	if (retry == 0) {
		printf("IOP not respond (callno = %d)\n", callno);
		goto error;
	}
	
	*result = sifbios_arg.result;

	return (0);

 error:
	return (-1);
}

void
sifbios_rpc_callback(void *arg, int result)
{
	int *done = (int *)arg;
	
	*done = 1;
}

/*
 * System misc.
 */
int
sifbios_getver(void)
{

	return CALL(int, 0, 0);
}

void
sifbios_halt(int mode)
{
	int sifbios_arg = mode;

	CALL(void, 1, &sifbios_arg);
}

void
sifbios_setdve(int mode)
{
	int sifbios_arg = mode;

	CALL(void, 2, &sifbios_arg);
}

void
sifbios_putchar(int c)
{
	int sifbios_arg = c;

	CALL(void, 3, &sifbios_arg);
}

int
sifbios_getchar(void)
{

	return CALL(int, 4, 0);
}

/*
 * SIF DMA
 */
int
sifdma_init(void)
{

	return CALL(int, 16, 0);
}

void
sifdma_exit(void)
{

	CALL(void, 17, 0);
}

/* queue DMA request to SIFBIOS. returns queue identifier. */
sifdma_id_t
sifdma_queue(struct sifdma_transfer *arg, int n)
{
	struct {
		void *arg;	/* pointer to sifdma_transfer array */
		int n;		/* # of elements */
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		arg:	arg,
		n:	n
	};

	return CALL(sifdma_id_t, 18, &sifbios_arg);
}

/* 
 * status of DMA 
 *	>0 ... queued. not kicked.
 *	 0 ... DMA executing.
 *	<0 ... DMA done.
 */
int
sifdma_stat(sifdma_id_t id)
{
	u_int32_t sifbios_arg = id;

	return CALL(int, 19, &sifbios_arg);
}

/* reset DMA channel */
void
sifdma_reset(void)
{

	CALL(void, 20, 0);
}

/*
 * SIF CMD
 */
int
sifcmd_init(void)
{
	
	return CALL(int, 32, 0);
}

void
sifcmd_exit(void)
{

	CALL(void, 33, 0);
}

sifdma_id_t
sifcmd_queue(sifcmd_sw_t sw, vaddr_t cmd_pkt_addr, size_t cmd_pkt_sz,
    vaddr_t src_addr, vaddr_t dst_addr, vsize_t buf_sz)
{
	struct {
		sifcmd_sw_t sw;
		vaddr_t cmd_pkt_addr;	/* command buffer */
		size_t cmd_pkt_sz;
		vaddr_t src_addr;	/* data buffer */
		vaddr_t dst_addr;
		vsize_t buf_sz;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		sw:		sw,
		cmd_pkt_addr:	cmd_pkt_addr,
		cmd_pkt_sz:	cmd_pkt_sz,
		src_addr:	src_addr,
		dst_addr:	dst_addr,
		buf_sz:		buf_sz,
	};

	return CALL(sifdma_id_t, 34, &sifbios_arg);
}

/* interrupt handler of DMAC channel 5 (SIF0) */
int
sifcmd_intr(void *arg)
{

	CALL(void, 35, 0);

	return (1);
}

void
sifcmd_establish(sifcmd_sw_t sw, struct sifcmd_callback_holder *holder)
{
	struct {
		sifcmd_sw_t sw;
		sifcmd_callback_t func;
		void *arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		sw:	sw,
		func:	holder->func,
		arg:	holder->arg,
	};
	
	CALL(void, 36, &sifbios_arg);
}

void
sifcmd_disestablish(sifcmd_sw_t sw)
{
	u_int32_t sifbios_arg = sw;

	CALL(void, 37, &sifbios_arg);
}

struct sifcmd_callback_holder *
sifcmd_handler_init(struct sifcmd_callback_holder *holder, int n)
{
	struct {
		void *holder;
		int n;		/* # of slot */
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		holder:	holder,
		n:	n,
	};

	/* returns old holder */
	return CALL(struct sifcmd_callback_holder *, 38, &sifbios_arg);
}

/*
 * SIF RPC
 */
int
sifrpc_init(void)
{

	return CALL(int, 48, 0);
}

void
sifrpc_exit(void)
{

	CALL(void, 49, 0);
}

int
sifrpc_receive_buffer(struct sifrpc_receive *_cookie, void *src_iop,
    void *dst_ee, size_t sz, u_int32_t rpc_mode, void (*end_func)(void *),
    void *end_arg)
{
	struct {
		void *_cookie;
		void *src_iop;
		void *dst_ee;
		size_t sz;
		u_int32_t rpc_mode;
		sifrpc_endfunc_t end_func;
		void *end_arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		_cookie:	_cookie,
		src_iop:	src_iop,
		dst_ee:		dst_ee,
		sz:		sz,
		rpc_mode:	rpc_mode,
		end_func:	end_func,
		end_arg:	end_arg,
	};

	return CALL(int, 50, &sifbios_arg);
}

int
sifrpc_bind(struct sifrpc_client *_cookie, sifrpc_id_t rpc_id,
    u_int32_t rpc_mode, void (*end_func)(void *), void *end_arg)
{
	struct {
		void *_cookie;		/* filled by this call */
		sifrpc_id_t rpc_id;	/* specify server RPC id */
		u_int32_t rpc_mode;
		sifrpc_endfunc_t end_func;
		void *end_arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		_cookie:	_cookie,
		rpc_id:		rpc_id,
		rpc_mode:	rpc_mode,
		end_func:	end_func,
		end_arg:	end_arg,
	};

	return CALL(int, 51, &sifbios_arg);
}

int
sifrpc_call(struct sifrpc_client *_cookie, sifrpc_callno_t call_no,
    u_int32_t rpc_mode, void *sendbuf, size_t sendbuf_sz, void *recvbuf,
    size_t recvbuf_sz, void (*end_func)(void *), void *end_arg)
{
	struct {
		struct sifrpc_client *_cookie;	/* binded client cookie */
		sifrpc_callno_t call_no; /* passed to service function arg. */
		u_int32_t rpc_mode;
		void *sendbuf;
		size_t sendbuf_sz;
		void *recvbuf;
		size_t recvbuf_sz;
		sifrpc_endfunc_t end_func;
		void *end_arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		_cookie:	_cookie,
		call_no:	call_no,
		rpc_mode:	rpc_mode,
		sendbuf:	sendbuf,
		sendbuf_sz:	sendbuf_sz,
		recvbuf:	recvbuf,
		recvbuf_sz:	recvbuf_sz,
		end_func:	end_func,
		end_arg:	end_arg,
	};

	return CALL(int, 52, &sifbios_arg);
}

int
sifrpc_stat(struct sifrpc_header *_cookie)
{
	void *sifbios_arg = _cookie;

	return CALL(int, 53, &sifbios_arg);
}

void
sifrpc_establish(struct sifrpc_server_system *queue, void (*end_func)(void *),
    void *end_arg)
{
	struct {
		struct sifrpc_server_system *queue;
		sifrpc_endfunc_t end_func;
		void *end_arg;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		queue:		queue,
		end_func:	end_func,
		end_arg:	end_arg,
	};

	CALL(void, 54, &sifbios_arg);
}

void
sifrpc_register_service(struct sifrpc_server_system *queue,
    struct sifrpc_server *server, sifrpc_id_t rpc_id,
    void *(*service_func)(sifrpc_callno_t, void *, size_t), void *service_arg,
    void *(*cancel_func)(sifrpc_callno_t, void *, size_t), void *cancel_arg)
{
	struct {
		void *server;
		sifrpc_id_t rpc_id;
		sifrpc_rpcfunc_t service_func;
		void *service_arg;
		sifrpc_rpcfunc_t cancel_func;
		void *cancel_arg;
		void *receive_queue;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		server:		server,
		rpc_id:		rpc_id,
		service_func:	service_func,
		service_arg:	service_arg,
		cancel_func:	cancel_func,
		cancel_arg:	cancel_arg,
		receive_queue:	queue,
	};
	
	CALL(void, 55, &sifbios_arg);
}

void
sifrpc_unregister_service(struct sifrpc_server_system *queue,
    struct sifrpc_server *server)
{
	struct {
		void *server;
		void *receive_queue;
	} __attribute__((__packed__, __aligned__(4))) sifbios_arg = {
		server:		server,
		receive_queue:	queue,
	};

	CALL(void, 56, &sifbios_arg);
}

void
sifrpc_disestablish(struct sifrpc_server_system *queue)
{
	void *sifbios_arg = queue;

	CALL(void, 57, &sifbios_arg);
}

struct sifrpc_server *
sifrpc_dequeue(struct sifrpc_server_system *queue)
{
	void *sifbios_arg = queue;

	return CALL(struct sifrpc_server *, 58, &sifbios_arg);
}

void
sifrpc_dispatch_service(struct sifrpc_server *server)
{
	void *sifbios_arg = server;

	CALL(void, 59, &sifbios_arg);
}

/*
 * IOP memory
 */
int
iopmem_init(void)
{
	int result;

	sifbios_rpc_call(64, 0, &result);

	return (0);
}

paddr_t
iopmem_alloc(psize_t sz)
{
	int result, sifbios_arg = sz;

	if (sifbios_rpc_call(65, (void *)&sifbios_arg, &result) < 0)
		return (paddr_t)0;
	/* returns allocated IOP physical addr */
	return (paddr_t)result;
}

int
iopmem_free(paddr_t addr)
{
	int result, sifbios_arg = addr;

	sifbios_rpc_call(66, (void *)&sifbios_arg, &result);

	return (result);
}
