/* interrupts-asm.S -- interrupt handling for OpenRISC 1000.
 *
 * Copyright (c) 2011, 2012, 2014 Authors
 *
 * Contributor Julius Baxter <juliusbaxter@gmail.com>
 * Contributor Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>
 * Contributor Stefan Wallentowitz <stefan.wallentowitz@tum.de>
 *
 * The authors hereby grant permission to use, copy, modify, distribute,
 * and license this software and its documentation for any purpose, provided
 * that existing copyright notices are retained in all copies and that this
 * notice is included verbatim in any distributions. No written agreement,
 * license, or royalty fee is required for any of the authorized uses.
 * Modifications to this software may be copyrighted by their authors
 * and need not follow the licensing terms described here, provided that
 * the new terms are clearly indicated on the first page of each file where
 * they apply.
 */

/* -------------------------------------------------------------------------- */
/*!Generic interrupt handler function for or1k
                                                                              */
/* -------------------------------------------------------------------------- */

#include "include/or1k-asm.h"
#include "include/or1k-sprs.h"

	.extern _or1k_interrupt_handler_table
	.extern _or1k_interrupt_handler_data_ptr_table

/* -------------------------------------------------------------------------- */
/*!Function to call appropriate interrupt handler
                                                                              */
/* -------------------------------------------------------------------------- */

	.section .text
	.global	_or1k_interrupt_handler
	.type	_or1k_interrupt_handler,@function

_or1k_interrupt_handler:
	/* Make room on stack, save link register */
	l.addi	r1,r1,-12
	l.sw	0(r1),r9

	/* Read PICSR */
	l.mfspr	r3,r0,OR1K_SPR_PIC_PICSR_ADDR

	/* Load handler table base address */
	l.movhi r7,hi(_or1k_interrupt_handler_table)
	l.ori	r7,r7,lo(_or1k_interrupt_handler_table)
	/* Load data pointer table base address */
	l.movhi r12,hi(_or1k_interrupt_handler_data_ptr_table)
	l.ori	r12,r12,lo(_or1k_interrupt_handler_data_ptr_table)
#ifdef __OR1K_MULTICORE__
	/* Read the addresses of the arrays of cores */
	/* r7 = (*or1k_interrupt_handler_table)  */
	l.lwz	r7,0(r7)
	/* r12 = (*or1k_interrupt_handler_data_ptr_table)  */
	l.lwz	r12,0(r12)
	/* Generate offset in arrays */
	/* r14 = coreid */
	l.mfspr	r14,r0,OR1K_SPR_SYS_COREID_ADDR
	/* r14 = coreid*32*4 = off */
	l.slli	r14,r14,7
	/* r7 = (*or1k_exception_handler_table)[coreid] */
	l.add	r7,r7,r14
	/* r12 = (*or1k_exception_handler_table)[coreid] */
	l.add	r12,r12,r14
#endif

.L0:
	/* Find first set bit in PICSR */
	l.ff1	r4,r3
	/* Any bits set? */
	l.sfne	r4,r0
	/* If none, finish */
	OR1K_DELAYED_NOP(OR1K_INST(l.bnf .L2))
	/* What is IRQ function table offset? */
	l.addi	r5,r4,-1
	l.slli	r6,r5,2
	/* Add this to table bases */
	l.add	r14,r6,r7
	l.add	r13,r6,r12

	/* Fetch handler function address */
	l.lwz	r14,0(r14)

	/* Double check it's valid, compare against INTERRUPT_HANDLER_NOT_SET */
	l.sfne	r14,r0
	/* Skip if no handler: TODO: Indicate interrupt fired but no handler*/
	OR1K_DELAYED_NOP(OR1K_INST(l.bnf .L1))

	/* Pull out data pointer from table, save r3, we'll write over it */
	l.sw	4(r1),r3
	l.lwz	r3,0(r13)
	/* Call handler, save r5 in delay slot */
	OR1K_DELAYED(
		OR1K_INST(l.sw   8(r1),r5),
		OR1K_INST(l.jalr r14)
	)

	/* Reload r3,r5 */
	l.lwz	r3,4(r1)
	l.lwz	r5,8(r1)
.L1:
	/* Clear bit from PICSR, return to start of checking loop */
	l.ori	r6,r0,1
	l.sll	r6,r6,r5
	OR1K_DELAYED(
		OR1K_INST(l.xor r3,r3,r6),
		OR1K_INST(l.j   .L0)
	)

.L2:
	/* Finish up - write PICSR back, restore r9*/
	l.lwz	r9,0(r1)
	l.mtspr	r0,r3,OR1K_SPR_PIC_PICSR_ADDR
	OR1K_DELAYED(
		OR1K_INST(l.addi r1,r1,12),
		OR1K_INST(l.jr   r9)
	)

/* -------------------------------------------------------------------------- */
/*!Function to enable an interrupt handler in the PICMR
                                                                              */
/* -------------------------------------------------------------------------- */
	.global	or1k_interrupt_enable
	.type	or1k_interrupt_enable,@function

	/* r3 should have IRQ line for peripheral */
or1k_interrupt_enable:
	l.addi 	r1,r1,-4
	l.sw	0(r1),r4
	l.ori	r4,r0,0x1
	l.sll	r4,r4,r3
	l.mfspr	r3,r0,OR1K_SPR_PIC_PICMR_ADDR
	l.or	r3,r3,r4
	l.mtspr	r0,r3,OR1K_SPR_PIC_PICMR_ADDR
	l.lwz	r4,0(r1)
	OR1K_DELAYED(
		OR1K_INST(l.addi	r1,r1,4),
		OR1K_INST(l.jr	r9)
	)

/* -------------------------------------------------------------------------- */
/*!Function to disable an interrupt handler in the PICMR
                                                                              */
/* -------------------------------------------------------------------------- */
	.global	or1k_interrupt_disable
	.type	or1k_interrupt_disable,@function

	/* r3 should have IRQ line for peripheral */
or1k_interrupt_disable:
	l.addi 	r1,r1,-4
	l.sw	0(r1),r4
	l.ori	r4,r0,0x1
	l.sll	r4,r4,r3
	l.xori	r4,r4,0xffff
	l.mfspr	r3,r0,OR1K_SPR_PIC_PICMR_ADDR
	l.and	r3,r3,r4
	l.mtspr	r0,r3,OR1K_SPR_PIC_PICMR_ADDR
	l.lwz	r4,0(r1)
	OR1K_DELAYED(
		OR1K_INST(l.addi r1,r1,4),
		OR1K_INST(l.jr   r9)
	)