/* pru1_main_qbus.c: main loop with mailbox cmd interface. QBUS devices with opt. phys. PDP-11 CPU.

 Copyright (c) 2018-2021, Joerg Hoppe
 j_hoppe@t-online.de, www.retrocmp.com

 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
 to deal in the Software without restriction, including without limitation
 the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


 28-mar-2019  JH      split off from "all-function" main
 12-nov-2018  JH      entered beta phase

 Master and slave functionality for QBUS devices.
 Assumes a physical PDP-11 CPU is working as Arbitrator for
 NPR/NG/SACK and BR/BG/SACK.
 Needed if QBone runs in a system running PDP-11 CPU
 and simulated or physical devices do DMA or INTR.

 Separated from "all-function" main() because of PRU code size limits.
 Application has to load this into PRU1 depending on system state.

 from d:\RetroCmp\dec\pdp11\UniBone\91_3rd_party\pru-c-compile\pru-software-support-package\examples\am335x\PRU_gpioToggle
 Test GPIO, shared mem and interrupt
 a) waits until ARM writes a value to mailbox.arm2pru_req
 b) ACKs with clear of arm2pru_req
 c) toggles 1 mio times GPIO, with delay as set by ARM
 d) signal EVENT0
 e) goto a
 */
 
#include <stdint.h>
#include <stdbool.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"

#include "pru1_utils.h"
#include "pru1_timeouts.h"

#include "pru_pru_mailbox.h"
#include "mailbox.h"
#include "ddrmem.h"
#include "iopageregister.h"

#include "pru1_buslatches.h"
#include "pru1_statemachine_initialization.h"
#include "pru1_statemachine_arbitration.h"
#include "pru1_statemachine_dma.h"
#include "pru1_statemachine_data_slave.h"
//#include "pru1_statemachine_intr_master.h"
//#include "pru1_statemachine_intr_slave.h"

// supress warnigns about using void * as function pointers
//	sm_slave_state = (statemachine_state_func)&sm_data_slave_start;
// while (sm_slave_state = sm_slave_state()) << usage
#pragma diag_push
#pragma diag_remark=515

/***
 3 major states executed in circular 1- 2- 3 order.

 1. "SLAVE":
 QBone monitoring BUS traffic as slave for DATI/DATO cycles
 Watch QBUS for CPU access to emulated memory/devices
 High speed not necessary: Bus master will wait with SYNCif QBone not responding.
 wathcin BG/BPG signals, catching requested GRANts and forwardinf
 other GRANTS
 - monitoring INIT and AC_LO/DC_LO
 - watching fpr AMR2PRU commands
 2. "BBSYWAIT": QBone got PRIORITY GRANT, has set SACK and released BR/NPR
 waits for current BUS master to relaeasy BBSY (ony DATI/DATO cycle max)
 - SACK active: no GRANT forward necessary, no arbitration necessary
 - INIT is monitored by DMA statemachine: no DC_LO/INIT monitoring necessary
 3. "MASTER": QBone is Bus master: transfering INTR vector or doing DMA
 - Own timing: transfer DMA data block or INTR vector with master cycles
 - SACK active: no GRANT forward necessary, no arbitration necessary
 - INIT is monitored by DMA statemachine: no DC_LO/INIT monitoring necessary
 */

void main(void) {

	// state function pointer for different state machines
	statemachine_state_func sm_data_master_state = NULL;
	// these are function pointers: could be 16bit on PRU?
	sm_arb.state = state_arbitration_grant_check ;
#ifdef TODO	
	statemachine_state_func sm_intr_slave_state = NULL;
#endif
	sm_data_slave.state = state_data_slave_stop ;

	bool emulate_cpu = false;

	/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
	CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

	timeout_init();

	// clear all tables, as backup if ARM fails todo
	iopageregisters_init();

	buslatches_reset(); // all signals negated, caches cleared

	sm_initialization_reset();

	/* ARM must init mailbox, especially:
	 mailbox.arm2pru_req = ARM2PRU_NONE;
	 mailbox.events.eventmask = 0;
	 mailbox.events.initialization_signals_prev = 0;
	 mailbox.events.initialization_signals_cur = 0;
	 */
	sm_arb_reset();

	while (true) {
		uint8_t arm2pru_req_cached;
		uint8_t cpu_grant_mask; // GRANts generated by physical or emulated CPU

		if (sm_data_master_state == NULL) {
//TODO: restart bus master operation

			// State 1 "SLAVE"

			// DATA or INTR for CPU?
//TODO: dummy slave: always NULL
			// fast: a complete slave data cycle
			// execute slave states until _stop or return because of signal to PRU
			while ((sm_data_slave.state = sm_data_slave_func(sm_data_slave.state)) /* ! stopped*/
					&& EVENT_IS_ACKED(mailbox, deviceregister)) {
				// throws signals to ARM,
				// Acess to internal registers may issue ARM2PRU opcode, so exit loop then
				;// execute complete slave cycle, then check NPR/INTR
				}
			// signal INT or PWR FAIL to ARM
			// before sm_arb_worker(), so INTR/DMR requests are canceled on INIT
			sm_initialization_func();
					
			if (!emulate_cpu) {
				// Fast forward device requests GRANTed by physical CPU.
				// Only one GRANT at a time may be active, else arbitrator/CPLD2 malfunction.
				// Arbitrator asserts SACK is inactive
				// Single QBUS IAKI is split onto IAKI4..7 by CPLD2
				cpu_grant_mask = buslatches_getbyte(7) & PRIORITY_ARBITRATION_BIT_MASK; // read GRANT IN
				// forward un-requested IOAKI/DMGI to IOAKO/DMGO for other cards on neighbor slots
				sm_arb.device_forwarded_grant_mask = cpu_grant_mask
						& ~sm_arb.device_request_signalled_mask;
				// Any write to IAKO4..7 setzs QBUS IAKO
				buslatches_setbits(7, PRIORITY_ARBITRATION_BIT_MASK, sm_arb.device_forwarded_grant_mask)
				;
				// "A device may not accept a grant (assert SACK) after it passes the grant"
			}
			// Priority Arbitration
			// Delay INTR or DMA while BUS halted via SSYN.
			// ARM may start DMA within deviceregister event!
			if (EVENT_IS_ACKED(mailbox, deviceregister)) {
				// execute one of the arbitration workers
//TODO: always false
				if (emulate_cpu) {
					// handle GRANT/SACK/BBSY for emulated devices
					cpu_grant_mask = sm_arb_worker_cpu(); // GRANT device requests
					// do not read GRANT signals from QBUS, IAKO/DMGO not visible for
					// emulated devices
//					sm_arb.device_forwarded_grant_mask = 0 ;
				} // else GRANTed by physical CPU, see above
				uint8_t granted_request = sm_arb_worker_device(cpu_grant_mask); 
				// devices process GRANT from CPU
//TODO: sm_device_arb_worker is "noop", returns always NP/DMA grant
				// sm_device_arb_worker()s include State 2 "RPLYSNYCWAIT".
				// So now SACK maybe set, even if grant_mask is still 0

				if (granted_request & PRIORITY_ARBITRATION_BIT_NP) { //DMA
					sm_data_master_state = (statemachine_state_func) &sm_dma_start;
					// can data_master_state be overwritten in the midst of a running data_master_state ?
					// no: when running, SACK is set, no new GRANTs
				}
				// else if (granted_request & PRIORITY_ARBITRATION_INTR_MASK) {
				// No separate state machone for device side of INTR, handled in sm_arb 
			}
			
		} else {
			// State 3 "MASTER"
			// we have been GRANTed bus mastership and are doing DMA or INTR
			// SACK held here -> no further arbitration
			// INTR is only 1 cycle, DMA has SACK set all the time, arbitration
			// prohibited then.

			sm_data_master_state = sm_data_master_state(); // execute only ONE state ,
			// else DMA blocks will block processing of other state machines
			// throws signals to ARM, causes may issue mailbox.arm2pru_req
		}
#ifdef TODO
		if (emulate_cpu) {
			// Receive INTR from physical or emulated devices, and signal ARM.
			if (!sm_intr_slave_state)
				sm_intr_slave_state = (statemachine_state_func) &sm_intr_slave_start;
			sm_intr_slave_state = sm_intr_slave_state();
			/*
			 while ((sm_intr_slave_state = sm_intr_slave_state())
			 && EVENT_IS_ACKED(mailbox, intr_slave))
			 ;
			 */
		}
#endif
		// process ARM commands in master and slave mode
		// standard operation may be interrupt by other requests
		if (arm2pru_req_cached = mailbox.arm2pru_req) {
			// not ARM2PRU_NONE
			switch (arm2pru_req_cached) {
			case ARM2PRU_DMA:
				// set QUNIBUS_IOPAGE_ADDR_BITMASK in addr for IOpage
				// different arbitration for device and CPU memory access.

				// request DMA, arbitrator must've been selected with ARM2PRU_ARB_MODE_*
				if (mailbox.dma.cpu_access) {
					// Emulated CPU: no NPR/NPG/SACK protocol
					sm_arb.cpu_request = 1;
//PRU_DEBUG_PIN0_PULSE(50) ; // CPU20 performace
				} else {
					// Emulated device: raise request for emulated or physical Arbitrator.
					sm_arb.device_request_mask |= PRIORITY_ARBITRATION_BIT_NP;
				}
				// request not put on bus for CPU memory access
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_INTR:
				// request INTR, arbitrator must've been selected with ARM2PRU_ARB_MODE_*
				// start one INTR cycle. May be raised in midst of slave cycle
				// by ARM, if access to "active" register triggers INTR.
				sm_arb.device_request_mask |= mailbox.intr.priority_arbitration_bit;
				// sm_arb evaluates this, extern Arbitrator raises Grant/performs INTR protocol,
				// vector of GRANted level is transfered to ARM

				// Atomically change state in a device's associates interrupt register.
				// The Interupt Register is set immediately. No wait for INTR GRANT,
				// INTR level may be blocked.
				if (mailbox.intr.iopage_register_handle)
					pru_iopage_registers.registers[mailbox.intr.iopage_register_handle].value =
							mailbox.intr.iopage_register_value;
				mailbox.arm2pru_req = ARM2PRU_NONE;  // done
				// end of INTR is signaled to ARM with signal
				break;
			case ARM2PRU_INTR_CANCEL:
				// cancels one or more INTR requests. If already Granted, the GRANT is forwarded,
				// and canceled by reaching a "SACK turnaround terminator" or "No SACK TIMEOUT" in the arbitrator.
				sm_arb.device_request_mask &= ~mailbox.intr.priority_arbitration_bit;
				// no completion event, could interfer with other INTRs?
				mailbox.arm2pru_req = ARM2PRU_NONE;  // done
				break;
			case ARM2PRU_ARB_GRANT_INTR_REQUESTS:
				if (emulate_cpu) {
					mailbox.arbitrator.ifs_intr_arbitration_pending = true;
					// also blocks ARM thread
				}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_INITALIZATIONSIGNAL_SET:
				switch (mailbox.initializationsignal.id) {
				case INITIALIZATIONSIGNAL_POK:
					// assert/negate POK
					buslatches_setbits(5, INITIALIZATIONSIGNAL_POK, mailbox.initializationsignal.val? INITIALIZATIONSIGNAL_POK:0);
					break;
				case INITIALIZATIONSIGNAL_DCOK:
					// assert/negate DCOK
					buslatches_setbits(5, INITIALIZATIONSIGNAL_DCOK, mailbox.initializationsignal.val? INITIALIZATIONSIGNAL_DCOK:0);
					break;
				case INITIALIZATIONSIGNAL_INIT:
					// assert/deassert INIT
					buslatches_setbits(5, INITIALIZATIONSIGNAL_INIT, mailbox.initializationsignal.val? INITIALIZATIONSIGNAL_INIT:0);
					break;
				case INITIALIZATIONSIGNAL_HALT:
					// assert/deassert HALT
					buslatches_setbits(5, INITIALIZATIONSIGNAL_HALT, mailbox.initializationsignal.val? INITIALIZATIONSIGNAL_HALT:0);
					break;
				}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
#ifdef TODO				
			case ARM2PRU_CPU_ENABLE:
				// bool flag much faster to access than shared mailbox member.
				if (emulate_cpu != mailbox.param) {
					emulate_cpu = mailbox.param;
					sm_arb_reset(); // new arbitration algorithms
				}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
#endif				
			case ARM2PRU_BUSLATCH_SET: { // set a mux register
				// and read back
				// don't feed "volatile" vars into buslatch_macros !!!
				uint8_t reg_sel = mailbox.buslatch.addr & 7;
				uint8_t bitmask = mailbox.buslatch.bitmask;
				uint8_t val = mailbox.buslatch.val;
				buslatches_setbits(reg_sel, bitmask, val);
				mailbox.buslatch.val = buslatches_getbyte(reg_sel);
			}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_BUSLATCH_GET: {
				// don't feed "volatile" vars into buslatch_macros !!!
				uint8_t reg_sel = mailbox.buslatch.addr & 7;
				mailbox.buslatch.val = buslatches_getbyte(reg_sel);
			}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_NOP: // needed to probe PRU run state
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_ARB_MODE_NONE: // ignore SACK condition
				// from now on, ignore INTR requests and allow DMA request immediately
				sm_arb.state = state_arbitration_noop;
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_ARB_MODE_CLIENT:
				// request DMA from external Arbitrator
				sm_arb.state = state_arbitration_grant_check; // now executes DMA/IRQ GRANT protocols
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break;
			case ARM2PRU_CPU_BUS_ACCESS:
				// Halted CPU does ODT polling for SLU, this causes bus traffic
				if (!mailbox.param) {
					// set DMR, this stops BUS activity
					sm_arb.cpu_bus_inhibit_dmr_mask |= ARB_CPU_BUS_INHIBIT_DMR_ARM ;
//					buslatches_setbits(7, BIT(4), BIT(4)) ;
				} else {// Clear DMR: generates SACK timeout and restart
					sm_arb.cpu_bus_inhibit_dmr_mask &=  ~ARB_CPU_BUS_INHIBIT_DMR_ARM ;
//					buslatches_setbits(7, BIT(4), 0) ;
				}
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				break ;
			case ARM2PRU_HALT:
				mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
				__halt(); // LA: trigger on timeout of REG_WRITE
				break;
			}
		}

	}
	// never reached
}

#pragma diag_pop

