/***************************************************************************
 *   Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014                *
 *   by Terraneo Federico                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   As a special exception, if other files instantiate templates or use   *
 *   macros or inline functions from this file, or you compile this file   *
 *   and link it with other works to produce a work based on this file,    *
 *   this file does not by itself cause the resulting work to be covered   *
 *   by the GNU General Public License. However the source code for this   *
 *   file must still be made available in accordance with the GNU General  *
 *   Public License. This exception does not invalidate any other reasons  *
 *   why a work based on this file might be covered by the GNU General     *
 *   Public License.                                                       *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
 ***************************************************************************/ 

#include <cstring>
#include <errno.h>
#include <termios.h>
#include "serial_lpc2000.h"
#include "kernel/sync.h"
#include "kernel/scheduler/scheduler.h"
#include "interfaces/portability.h"
#include "filesystem/ioctl.h"
#include "LPC213x.h"

namespace miosix {

/// Pointer to serial port classes to let interrupts access the classes
static LPC2000Serial *ports[2]={0};

/**
 * \internal interrupt routine for usart0 actual implementation
 */
void __attribute__((noinline)) usart0irqImpl()
{
    if(ports[0]) ports[0]->IRQhandleInterrupt();
    VICVectAddr=0xff;//Restart VIC
}

/**
 * \internal interrupt routine for usart0
 */
void __attribute__((interrupt("IRQ"),naked)) usart0irq()
{
    saveContextFromIrq();
	asm volatile("bl _ZN6miosix13usart0irqImplEv");
    restoreContext();
}

/**
 * \internal interrupt routine for usart1 actual implementation
 */
void __attribute__((noinline)) usart1irqImpl()
{
    if(ports[1]) ports[1]->IRQhandleInterrupt();
    VICVectAddr=0xff;//Restart VIC
}

/**
 * \internal interrupt routine for usart1
 */
void __attribute__ ((interrupt("IRQ"),naked)) usart1irq()
{
    saveContextFromIrq();
	asm volatile("bl _ZN6miosix13usart1irqImplEv");
    restoreContext();
}

//
// class LPC2000Serial
//

// A note on the baudrate/500: the buffer is selected so as to withstand
// 20ms of full data rate. In the 8N1 format one char is made of 10 bits.
// So (baudrate/10)*0.02=baudrate/500
LPC2000Serial::LPC2000Serial(int id, int baudrate) : Device(Device::TTY),
        rxQueue(hwRxQueueLen+baudrate/500), rxWaiting(0), idle(true)
{
    InterruptDisableLock dLock;
    if(id<0 || id>1 || ports[id]!=0) errorHandler(UNEXPECTED);
    ports[id]=this;
    if(id==0)
    {
        serial=reinterpret_cast<Usart16550*>(0xe000c000);
        PCONP|=(1<<3);//Enable UART0 peripheral
        PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0
        PINSEL0|=0x5;//Set p0.0 as TXD and p0.1 as RXD
        //Init VIC
        VICSoftIntClr=(1<<6);//Clear uart0 interrupt flag (if previously set)
        VICIntSelect&=~(1<<6);//uart0=IRQ
        VICIntEnable=(1<<6);//uart0 interrupt ON
        VICVectCntl2=0x20 | 0x6;//Slot 2 of VIC used by uart0
        VICVectAddr2=reinterpret_cast<unsigned int>(&usart0irq);
    } else {
        serial=reinterpret_cast<Usart16550*>(0xe0010000);
        PCONP|=(1<<4);//Enable UART1 peripheral
        PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0
        PINSEL0|=0x50000;//Set p0.8 as TXD and p0.9 as RXD
        //Init VIC
        VICSoftIntClr=(1<<7);//Clear uart1 interrupt flag (if previously set)
        VICIntSelect&=~(1<<7);//uart1=IRQ
        VICIntEnable=(1<<7);//uart1 interrupt ON
        VICVectCntl3=0x20 | 0x7;//Slot 3 of VIC used by uart1
        VICVectAddr3=reinterpret_cast<unsigned int>(&usart1irq);
    }
    serial->LCR=0x3;//DLAB disabled
    //0x07= fifo enabled, reset tx and rx hardware fifos
    //0x80= uart rx fifo trigger level 8 characters
    serial->FCR=0x07 | 0x80;
    serial->LCR=0x83;//8data bit, 1stop, no parity, DLAB enabled
    int div=TIMER_CLOCK/16/baudrate;
    serial->DLL=div & 0xff;
    serial->DLM=(div>>8) & 0xff;
    serial->LCR=0x3;//DLAB disabled
    serial->IER=0x7;//Enable RLS, RDA, CTI and THRE interrupts
}

ssize_t LPC2000Serial::readBlock(void *buffer, size_t size, off_t where)
{
    Lock<FastMutex> l(rxMutex);
    char *buf=reinterpret_cast<char*>(buffer);
    size_t result=0;
    FastInterruptDisableLock dLock;
    for(;;)
    {
        //Try to get data from the queue
        for(;result<size;result++)
        {
            if(rxQueue.tryGet(buf[result])==false) break;
            //This is here just not to keep IRQ disabled for the whole loop
            FastInterruptEnableLock eLock(dLock);
        }
        if(idle && result>0) break;
        if(result==size) break;
        //Wait for data in the queue
        do {
            rxWaiting=Thread::IRQgetCurrentThread();
            Thread::IRQwait();
            {
                FastInterruptEnableLock eLock(dLock);
                Thread::yield();
            }
        } while(rxWaiting);
    }
    return result;
}

ssize_t LPC2000Serial::writeBlock(const void *buffer, size_t size, off_t where)
{
    Lock<FastMutex> l(txMutex);
    FastInterruptDisableLock dLock;
    size_t len=size;
    const char *buf=reinterpret_cast<const char*>(buffer);
    while(len>0)
    {
        //If no data in software and hardware queue
        if((serial->LSR & (1<<5)) && (txQueue.isEmpty()))
        {
            //Fill hardware queue first
            for(int i=0;i<hwTxQueueLen;i++)
            {
                serial->THR=*buf++;
                len--;
                if(len==0) break;
            }
        } else {
            if(txQueue.IRQput(*buf)==true)
            {
                buf++;
                len--;
            } else {
                FastInterruptEnableLock eLock(dLock);
                txQueue.waitUntilNotFull();
            }
        }
    }
    return size;
}

void LPC2000Serial::IRQwrite(const char *str)
{
    while((*str)!='\0')
    {
        //Wait until the hardware fifo is ready to accept one char
        while(!(serial->LSR & (1<<5))) ; //wait
        serial->THR=*str++;
    }
    waitSerialTxFifoEmpty();
}

int LPC2000Serial::ioctl(int cmd, void* arg)
{
    if(reinterpret_cast<unsigned>(arg) & 0b11) return -EFAULT; //Unaligned
    termios *t=reinterpret_cast<termios*>(arg);
    switch(cmd)
    {
        case IOCTL_SYNC:
            waitSerialTxFifoEmpty();
            return 0;
        case IOCTL_TCGETATTR:
            t->c_iflag=IGNBRK | IGNPAR;
            t->c_oflag=0;
            t->c_cflag=CS8;
            t->c_lflag=0;
            return 0;
        case IOCTL_TCSETATTR_NOW:
        case IOCTL_TCSETATTR_DRAIN:
        case IOCTL_TCSETATTR_FLUSH:
            //Changing things at runtime unsupported, so do nothing, but don't
            //return error as console_device.h implements some attribute changes
            return 0;
        default:
            return -ENOTTY; //Means the operation does not apply to this descriptor
    }
}

void LPC2000Serial::IRQhandleInterrupt()
{
    char c;
    bool hppw=false;
    bool wakeup=false;
    switch(serial->IIR & 0xf)
    {
        case 0x6: //RLS
            c=serial->LSR;//Read LSR to clear interrupt
            c=serial->RBR;//Read RBR to discard char that caused error
            break;
        case 0x4: //RDA
            //Note: read one less char than HARDWARE_RX_QUEUE_LENGTH as the
            //CTI interrupt only occurs if there's at least one character in
            //the buffer, and we always want the CTI interrupt
            for(int i=0;i<hwRxQueueLen-1;i++)
                if(rxQueue.tryPut(serial->RBR)==false) /*fifo overflow*/;
            wakeup=true;
            idle=false;
            break;
        case 0xc: //CTI
            while(serial->LSR & (1<<0))
                if(rxQueue.tryPut(serial->RBR)==false) /*fifo overflow*/;
            wakeup=true;
            idle=true;
            break;
        case 0x2: //THRE
            for(int i=0;i<hwTxQueueLen;i++)
            {
                //If software queue empty, stop
                if(txQueue.IRQget(c,hppw)==false) break;
                serial->THR=c;
            }
            break;
    }
    if(wakeup && rxWaiting)
    {
        rxWaiting->IRQwakeup();
        if(rxWaiting->IRQgetPriority()>
                Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true;
        rxWaiting=0;
    }
    if(hppw) Scheduler::IRQfindNextThread();
}

LPC2000Serial::~LPC2000Serial()
{
    waitSerialTxFifoEmpty();
    
    InterruptDisableLock dLock;
    //Disable UART0
    serial->LCR=0;//DLAB disabled
    serial->FCR=0;
    
    int id=0;
    if(ports[0]==this) id=0;
    else if(ports[1]==this) id=1;
    else errorHandler(UNEXPECTED);
    ports[id]=0;
    
    if(id==0)
    {
        //Disable VIC
        VICIntEnClr=(1<<6);
        //Disable PIN
        PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0
    } else {
        //Disable VIC
        VICIntEnClr=(1<<7);
        //Disable PIN
        PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0
    }
}

} //namespace miosix
