//----------------------------------------------------------------------------------
// <copyright file="uart_isr.s" company="LiquidRobotics">
//	Copyright (c) Liquid Robotics Corporation.  All rights reserved.
// </copyright>
//
// <summary>
// 	 Interrupt-driven character receive routine for the ATMEL 2560 internal ports
//   1..4
// </summary>
//
// <owner>Mike Cookson</owner>
//---------------------------------------------------------------------------------

#include "avr/io.h"


	.set	SIZE_UART_DEVCTL,20				; size of a DEVCTL block

	.set	DEVCTL_FRAME_ERRORS,0			; incremented on framing error
	.set	DEVCTL_UART_OVERRUN_ERRORS,2	; incremented on uart overrun error
	.set	DEVCTL_FIFO_OVERRUN_ERRORS,4	; incremented when rx fifo overflows
	.set	DEVCTL_RXINPTR,6				; offset within the associated rxbuffer at which data are being added
	.set	DEVCTL_RXOUTPTR,7				; offset within the associated rxbuffer at which data can be removed
	.set	DEVCTL_TXINPTR,8				; offset within the associated txbuffer at which data are being added
	.set	DEVCTL_TXOUTPTR,9				; offset within the associated txbuffer at which data are being removed
	.set	DEVCTL_RXCNT,10					; offset within the associated txbuffer at which data are being removed
	.set	DEVCTL_UARTBASE,12				; points to the base of the registers used for this port
	.set	DEVCTL_PRXEVENT,14				; points to the event to trigger on receive and if a task is pending
	.set	DEVCTL_STALE,16					; bit 0 indicates that the receive buffer is empty; bit 1 indicates a intercharacter gap
	.set	DEVCTL_PTXEVENT,17				; points to the event to trigger on transmit buffer empty if a task is pending
	.set	DEVCTL_TXCNT,19					; count of bytes in the transmit buffer

	.set	EMPTY,1							; flag indicating that the queue is empty
	.set	STALE,2							; flag indicating intercharacter gap
	.set	MAILBOX_THRESHOLD,16			; the number of bytes at which the mailbox is notified

	.set	PORT0_DEVCTL,uart_devctl
	.set	PORT1_DEVCTL,uart_devctl+SIZE_UART_DEVCTL
	.set	PORT2_DEVCTL,uart_devctl+2*SIZE_UART_DEVCTL
	.set	PORT3_DEVCTL,uart_devctl+3*SIZE_UART_DEVCTL

	.set	OS_LOWEST_PRIO,63				; MUST MATCH that of 'os_cfg.h'!!!

#include "os_cpu.inc"

; Here we are defining assembler symbols equivalent to C preprocesser #defines
; so that they can be used with macro explansion

	.set	UCSR0A_asm,UCSR0A
	.set	UCSR1A_asm,UCSR1A
	.set	UCSR2A_asm,UCSR2A
	.set	UCSR3A_asm,UCSR3A
	.set	UCSR0B_asm,UCSR0B
	.set	UCSR1B_asm,UCSR1B
	.set	UCSR2B_asm,UCSR2B
	.set	UCSR3B_asm,UCSR3B
	.set	UDR0A_asm,UDR0
	.set	UDR1A_asm,UDR1
	.set	UDR2A_asm,UDR2
	.set	UDR3A_asm,UDR3

; Here are definitions that enable the ISR entry points

	.global USART0_RX_vect
	.set	USART0_RX_vect, RXISRPORT0
	.global USART1_RX_vect
	.set	USART1_RX_vect, RXISRPORT1
	.global USART2_RX_vect
	.set	USART2_RX_vect, RXISRPORT2
	.global USART3_RX_vect
	.set	USART3_RX_vect, RXISRPORT3

	.global USART0_UDRE_vect
	.set	USART0_UDRE_vect, TXISRPORT0
	.global USART1_UDRE_vect
	.set	USART1_UDRE_vect, TXISRPORT1
	.global USART2_UDRE_vect
	.set	USART2_UDRE_vect, TXISRPORT2
	.global USART3_UDRE_vect
	.set	USART3_UDRE_vect, TXISRPORT3

; macro to increment a word
    .macro  inc_wd, addr=0
    lds     r28, \addr
    lds     r29, \addr+1
    adiw    r28, 1
    breq    9f
; don't increment past zero
    sts     \addr, r28
    sts     \addr+1, r29
9:
    .endm

; macro to check if there is anything pending on a mailbox (assumes min registers saved)
; on return, r29|r28 = [mbaddr] (i.e. Y points to the actual mailbox)
	.macro	mbpend, mbaddr, yesjump
    lds     r28, \mbaddr
    lds     r29, \mbaddr + 1
    ldd     r16, Y+5
    .if OS_LOWEST_PRIO > 63
    ldd     r0, Y+6
    or      r16, r0
    .else
    tst     r16
    .endif
    brne    \yesjump

; if the event pointer is already set, don't bother setting it again
	ldd     r16, Y+1
    tst     r16
    breq    \yesjump

	.endm

    .irp port, 0, 1, 2, 3

	.global RXISRPORT\port\()
	.func	RXISRPORT\port\()
RXISRPORT\port\():

; save mini context
    PUSH_MIN_ISR

; the receiver data isn't stale and not empty
    lds     r16, PORT\port\()_DEVCTL + DEVCTL_STALE
    cbr     r16, EMPTY | STALE
    sts     PORT\port\()_DEVCTL + DEVCTL_STALE, r16

; examine status register
	lds		r16,UCSR\port\()A_asm

; increment framing error count
    bst     r16, FE0
    brtc    RXISRPORT\port\()noframeerror
    inc_wd  PORT\port\()_DEVCTL + DEVCTL_FRAME_ERRORS

RXISRPORT\port\()noframeerror:

; increment data overrun error count
    bst     r16, DOR0
    brtc    RXISRPORT\port\()nodataovr
    inc_wd  PORT\port\()_DEVCTL + DEVCTL_UART_OVERRUN_ERRORS
RXISRPORT\port\()nodataovr:

; store the character in the buffer
    lds     r28, PORT\port\()_DEVCTL + DEVCTL_RXINPTR
    ldi     r29, hi8(rxbuffer+256*\port)
	lds		r16,UDR\port\()A_asm
    st      Y+, r16

; save new input buffer pointer
    sts     PORT\port\()_DEVCTL + DEVCTL_RXINPTR, r28

; if the output pointer is the same as the input pointer, then
; there was an overrun error in the FIFO
    lds     r16, PORT\port\()_DEVCTL + DEVCTL_RXOUTPTR
    cp      r16, r28
    brne    RXISRPORT\port\()nofifoovr

; overrun error. Increment the output pointer and increment the
; fifo overrun error count
    inc     r16
    sts     PORT\port\()_DEVCTL + DEVCTL_RXOUTPTR, r16
    inc_wd  PORT\port\()_DEVCTL + DEVCTL_FIFO_OVERRUN_ERRORS
    rjmp    RXISRPORT\port\()chkpending

RXISRPORT\port\()nofifoovr:

; increment count
    inc_wd  PORT\port\()_DEVCTL + DEVCTL_RXCNT

; check and see if an event is pending on this channel. Note that
; the event pointer MUST not be NULL!!!!!
RXISRPORT\port\()chkpending:

	mbpend	PORT\port\()_DEVCTL + DEVCTL_PRXEVENT, RXISRPORT\port\()pend

RXISRPORT\port\()eoi:
    
	POP_MIN_ISR
    reti

; if we are pending on the receiver, the mailbox is clear and if the count is more than 16,
; then save the rest of the context and set the mailbox
RXISRPORT\port\()pend:

    lds     r16, PORT\port\()_DEVCTL + DEVCTL_RXCNT + 1
    tst     r16
    brne    RXISRPORT\port\()postmb
    lds     r16, PORT\port\()_DEVCTL + DEVCTL_RXCNT
    cpi     r16, MAILBOX_THRESHOLD
    brlt    RXISRPORT\port\()eoi


; at this point, Y = mailbox, just save all registers, call OSIntEnter (or implement the
; equivalent); call OSMboxPost, call OSIntExit, restore all registers and exit the interrupt
;
; all interrupt service routines use this same code instance
RXISRPORT\port\()postmb:

	.ifc \port,0
post_mb:				; Common code for all UARTs' mailbox post paths.

   	PUSH_REST

; note R1 is cleared by PUSH_REST in preparation for execution of C functions: do not set R1
; to a non-zero value, or be sure to clear it again before calling C functions

; increment the OS nesting level

    lds     r16, OSIntNesting
    tst     r16
    brne    RXISRPORT\port\()nznesting

; if nesting level is zero, save current stack pointer: in the event of a context switch,
; control will not return to the ISR: the context switch routines will handle the return
    lds     r30, OSTCBCur
    lds     r31, OSTCBCur+1
    in      r17, _SFR_IO_ADDR(SPL)
    st      Z+, r17
    in      r17, _SFR_IO_ADDR(SPH)
    st      Z+, r17

; increment nesting level, but don't save if incremented to 0 -- check for overflow OPTIMIZED OUT
RXISRPORT\port\()nznesting:

    inc     r16
    sts     OSIntNesting, r16

; It is now OK to re-allow interrupts. (CO)
	sei

; ok so call OSMboxPost to make the waiting task ready and OSIntExit to re-evaluate task priorities
; and schedule the highest-priority task
    
    movw    r24, r28
    ldi     r22, 1
    clr     r23
    call    OSMboxPost
    call    OSIntExit
	;
	; control will only return here if NO context switch occurs
	;
    POP_REST
    POP_MIN_ISR
    reti

	.else

	jmp		post_mb

	.endif

   .endfunc


	.global TXISRPORT\port\()
	.func	TXISRPORT\port\()
TXISRPORT\port\():

; save mini context
    PUSH_MIN_ISR

	; are there any characters left to transmit?

	lds		r28, PORT\port\()_DEVCTL + DEVCTL_TXCNT
;	lds		r29, PORT\port\()_DEVCTL + DEVCTL_TXCNT + 1
	subi	r28, 1
;	sbci	r29, 0
	brcs	TXISRPORT\port\()fifoempty
	sts		PORT\port\()_DEVCTL + DEVCTL_TXCNT, r28
;	sts		PORT\port\()_DEVCTL + DEVCTL_TXCNT + 1, r29

	; there are characters left, so feed the next
	; character

    lds     r28, PORT\port\()_DEVCTL + DEVCTL_TXOUTPTR
    ldi     r29, hi8(txbuffer+256*\port\())
	ld		r16, Y+
	sts		UDR\port\()A_asm, r16
	sts		PORT\port\()_DEVCTL + DEVCTL_TXOUTPTR, r28

	; if nobody is pending on the event, just return
TXISRPORT\port\()chkpending:
	mbpend	PORT\port\()_DEVCTL + DEVCTL_PTXEVENT, TXISRPORT\port\()pending
TXISRPORT\port\()eoi:
	POP_MIN_ISR
	reti

	; somebody is waiting, but if there's not room for at least MAILBOX_THRESHOLD
	; characters, ignore them

TXISRPORT\port\()pending:

	lds		r16, PORT\port\()_DEVCTL + DEVCTL_TXCNT
	cpi		r16, -MAILBOX_THRESHOLD
	brcc	TXISRPORT\port\()eoi

	; OK I want to wake the pending caller up (reusing routine from receiver interrupt)
	; Y=mailbox on return from mbpend macro
	jmp		post_mb

	; the FIFO is now empty, so disable the ISR

TXISRPORT\port\()fifoempty:

	lds		r16, UCSR\port\()B_asm
	andi	r16, ~(1 << UDRE0)
	sts		UCSR\port\()B_asm, r16
	jmp		TXISRPORT\port\()chkpending	; Check for pending task(s) and set up pointers.

 	.endfunc



    .endr
