/***************************************************************************
 *   Copyright (C) 2010-2018 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_stm32.h"
#include "kernel/sync.h"
#include "kernel/scheduler/scheduler.h"
#include "interfaces/portability.h"
#include "filesystem/ioctl.h"
#include "core/cache_cortexMx.h"

using namespace std;
using namespace miosix;

static const int numPorts=3; //Supporting only USART1, USART2, USART3

//A nice feature of the stm32 is that the USART are connected to the same
//GPIOS in all families, stm32f1, f2, f4 and l1. Additionally, USART1 is
//always connected to the APB2, while USART2 and USART3 are always on APB1
//Unfortunately, this does not hold with DMA.
typedef Gpio<GPIOA_BASE,9>  u1tx;
typedef Gpio<GPIOA_BASE,10> u1rx;
typedef Gpio<GPIOA_BASE,11> u1cts;
typedef Gpio<GPIOA_BASE,12> u1rts;

typedef Gpio<GPIOA_BASE,2>  u2tx;
typedef Gpio<GPIOA_BASE,3>  u2rx;
typedef Gpio<GPIOA_BASE,0>  u2cts;
typedef Gpio<GPIOA_BASE,1>  u2rts;

typedef Gpio<GPIOB_BASE,10> u3tx;
typedef Gpio<GPIOB_BASE,11> u3rx;
typedef Gpio<GPIOB_BASE,13> u3cts;
typedef Gpio<GPIOB_BASE,14> u3rts;

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

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

/**
 * \internal interrupt routine for usart1
 */
void __attribute__((naked)) USART1_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z13usart1irqImplv");
    restoreContext();
}

#if !defined(STM32_NO_SERIAL_2_3)

/**
 * \internal interrupt routine for usart2 actual implementation
 */
void __attribute__((noinline)) usart2irqImpl()
{
   if(ports[1]) ports[1]->IRQhandleInterrupt();
}

/**
 * \internal interrupt routine for usart2
 */
void __attribute__((naked)) USART2_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z13usart2irqImplv");
    restoreContext();
}

#if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
/**
 * \internal interrupt routine for usart3 actual implementation
 */
void __attribute__((noinline)) usart3irqImpl()
{
   if(ports[2]) ports[2]->IRQhandleInterrupt();
}

/**
 * \internal interrupt routine for usart3
 */
#if !defined(STM32F072xB)
void __attribute__((naked)) USART3_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z13usart3irqImplv");
    restoreContext();
}
#else  //!defined(STM32F072xB)
void __attribute__((naked)) USART3_4_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z13usart3irqImplv");
    restoreContext();
}
#endif //!defined(STM32F072xB)
#endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
#endif //!defined(STM32_NO_SERIAL_2_3)

#ifdef SERIAL_1_DMA

/**
 * \internal USART1 DMA tx actual implementation
 */
void __attribute__((noinline)) usart1txDmaImpl()
{
    #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
     || defined(_ARCH_CORTEXM4_STM32L4)
    DMA1->IFCR=DMA_IFCR_CGIF4;
    DMA1_Channel4->CCR=0; //Disable DMA
    #else //stm32f2 and f4
    DMA2->HIFCR=DMA_HIFCR_CTCIF7
              | DMA_HIFCR_CTEIF7
              | DMA_HIFCR_CDMEIF7
              | DMA_HIFCR_CFEIF7;
    #endif
    if(ports[0]) ports[0]->IRQhandleDMAtx();
}

/**
 * \internal USART1 DMA rx actual implementation
 */
void __attribute__((noinline)) usart1rxDmaImpl()
{
    if(ports[0]) ports[0]->IRQhandleDMArx();
}

#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
 || defined(_ARCH_CORTEXM4_STM32L4)
/**
 * \internal DMA1 Channel 4 IRQ (configured as USART1 TX)
 */
void __attribute__((naked)) DMA1_Channel4_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart1txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA1 Channel 5 IRQ (configured as USART1 RX)
 */
void __attribute__((naked)) DMA1_Channel5_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart1rxDmaImplv");
    restoreContext();
}

#else //stm32f2 and stm32f4

/**
 * \internal DMA2 stream 7 IRQ (configured as USART1 TX)
 */
void __attribute__((naked)) DMA2_Stream7_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart1txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA2 stream 5 IRQ (configured as USART1 RX)
 */
void __attribute__((naked)) DMA2_Stream5_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart1rxDmaImplv");
    restoreContext();
}
#endif
#endif //SERIAL_1_DMA

#if defined(SERIAL_2_DMA) && !defined(STM32_NO_SERIAL_2_3)

/**
 * \internal USART2 DMA tx actual implementation
 */
void __attribute__((noinline)) usart2txDmaImpl()
{
    #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
     || defined(_ARCH_CORTEXM4_STM32L4)
    DMA1->IFCR=DMA_IFCR_CGIF7;
    DMA1_Channel7->CCR=0; //Disable DMA
    #else //stm32f2 and f4
    DMA1->HIFCR=DMA_HIFCR_CTCIF6
              | DMA_HIFCR_CTEIF6
              | DMA_HIFCR_CDMEIF6
              | DMA_HIFCR_CFEIF6;
    #endif
    if(ports[1]) ports[1]->IRQhandleDMAtx();
}

/**
 * \internal USART2 DMA rx actual implementation
 */
void __attribute__((noinline)) usart2rxDmaImpl()
{
    if(ports[1]) ports[1]->IRQhandleDMArx();
}

#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
 || defined(_ARCH_CORTEXM4_STM32L4)
/**
 * \internal DMA1 Channel 7 IRQ (configured as USART2 TX)
 */
void __attribute__((naked)) DMA1_Channel7_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart2txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA1 Channel 6 IRQ (configured as USART2 RX)
 */
void __attribute__((naked)) DMA1_Channel6_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart2rxDmaImplv");
    restoreContext();
}

#else //stm32f2 and stm32f4

/**
 * \internal DMA1 stream 6 IRQ (configured as USART2 TX)
 */
void __attribute__((naked)) DMA1_Stream6_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart2txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA1 stream 5 IRQ (configured as USART2 RX)
 */
void __attribute__((naked)) DMA1_Stream5_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart2rxDmaImplv");
    restoreContext();
}
#endif
#endif //SERIAL_2_DMA

#if defined(SERIAL_3_DMA) && !defined(STM32_NO_SERIAL_2_3)

/**
 * \internal USART3 DMA tx actual implementation
 */
void __attribute__((noinline)) usart3txDmaImpl()
{
    #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
     || defined(_ARCH_CORTEXM4_STM32L4)
    DMA1->IFCR=DMA_IFCR_CGIF2;
    DMA1_Channel2->CCR=0; //Disable DMA
    #else //stm32f2 and f4
    DMA1->LIFCR=DMA_LIFCR_CTCIF3
              | DMA_LIFCR_CTEIF3
              | DMA_LIFCR_CDMEIF3
              | DMA_LIFCR_CFEIF3;
    #endif
    if(ports[2]) ports[2]->IRQhandleDMAtx();
}

/**
 * \internal USART3 DMA rx actual implementation
 */
void __attribute__((noinline)) usart3rxDmaImpl()
{
    if(ports[2]) ports[2]->IRQhandleDMArx();
}

#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \
 || defined(_ARCH_CORTEXM4_STM32L4)
/**
 * \internal DMA1 Channel 2 IRQ (configured as USART3 TX)
 */
void __attribute__((naked)) DMA1_Channel2_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart3txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA1 Channel 3 IRQ (configured as USART3 RX)
 */
void __attribute__((naked)) DMA1_Channel3_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart3rxDmaImplv");
    restoreContext();
}

#else //stm32f2 and stm32f4

/**
 * \internal DMA1 stream 3 IRQ (configured as USART3 TX)
 */
void __attribute__((naked)) DMA1_Stream3_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart3txDmaImplv");
    restoreContext();
}

/**
 * \internal DMA1 stream 1 IRQ (configured as USART3 RX)
 */
void __attribute__((naked)) DMA1_Stream1_IRQHandler()
{
    saveContext();
    asm volatile("bl _Z15usart3rxDmaImplv");
    restoreContext();
}
#endif
#endif //SERIAL_3_DMA

namespace miosix {

#ifdef SERIAL_DMA
#if defined(_ARCH_CORTEXM4_STM32F4) || defined(_ARCH_CORTEXM4_STM32F3) \
 || defined(_ARCH_CORTEXM4_STM32L4)
/**
 * The STM3F3, STM32F4 and STM32L4 have an ugly quirk of having 64KB RAM area
 * called CCM that can only be accessed by the processor and not be the DMA.
 * \param x pointer to check
 * \return true if the pointer is inside the CCM, and thus it isn't possible
 * to use it for DMA transfers
 */
static bool isInCCMarea(const void *x)
{
    unsigned int ptr=reinterpret_cast<const unsigned int>(x);
    return (ptr>=0x10000000) && (ptr<(0x10000000+64*1024));
}
#else //_ARCH_CORTEXM4_STM32F4 and _ARCH_CORTEXM4_STM32F3
static inline bool isInCCMarea(const void *x) { return false; }
#endif // _ARCH_CORTEXM4_STM32F4 and _ARCH_CORTEXM4_STM32F3
#endif //SERIAL_DMA

//
// class STM32Serial
//

// 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
STM32Serial::STM32Serial(int id, int baudrate, FlowCtrl flowControl)
        : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500),
          flowControl(flowControl==RTSCTS), portId(id)
{
    #if !defined(_ARCH_CORTEXM3_STM32)
    //stm32f2, f4, l4, l1, f7, h7 require alternate function mapping
    //stm32f0 family has different alternate function mapping
    //with respect to the other families
    switch(id)
    {
        case 1:
            #if !defined(_ARCH_CORTEXM0_STM32)
            u1tx::alternateFunction(7);
            u1rx::alternateFunction(7);
            if(flowControl)
            {
                u1rts::alternateFunction(7);
                u1cts::alternateFunction(7);
            }
            #else  //!defined(_ARCH_CORTEXM0_STM32)
            u1tx::alternateFunction(1);
            u1rx::alternateFunction(1);
            if(flowControl)
            {
                u1rts::alternateFunction(1);
                u1cts::alternateFunction(1);
            }
            #endif //!defined(_ARCH_CORTEXM0_STM32)
            break;
        case 2:
            #if !defined(_ARCH_CORTEXM0_STM32)
            u2tx::alternateFunction(7);
            u2rx::alternateFunction(7);
            if(flowControl)
            {
                u2rts::alternateFunction(7);
                u2cts::alternateFunction(7);
            }
            #else  //!defined(_ARCH_CORTEXM0_STM32)
            u2tx::alternateFunction(1);
            u2rx::alternateFunction(1);
            if(flowControl)
            {
                u2rts::alternateFunction(1);
                u2cts::alternateFunction(1);
            }
            #endif //!defined(_ARCH_CORTEXM0_STM32)            
            break;
        case 3:
            #if !defined(_ARCH_CORTEXM0_STM32)
            u3tx::alternateFunction(7);
            u3rx::alternateFunction(7);
            if(flowControl)
            {
                u3rts::alternateFunction(7);
                u3cts::alternateFunction(7);
            }
            #else  //!defined(_ARCH_CORTEXM0_STM32)
            u3tx::alternateFunction(4);
            u3rx::alternateFunction(4);
            if(flowControl)
            {
                u3rts::alternateFunction(4);
                u3cts::alternateFunction(4);
            }
            #endif //!defined(_ARCH_CORTEXM0_STM32)            
            break;
    }
    #endif //_ARCH_CORTEXM3_STM32
    
    switch(id)
    {
        case 1:
            commonInit(id,baudrate,u1tx::getPin(),u1rx::getPin(),
                       u1rts::getPin(),u1cts::getPin());
            break;
        case 2:
            commonInit(id,baudrate,u2tx::getPin(),u2rx::getPin(),
                       u2rts::getPin(),u2cts::getPin());
            break;
        case 3:
            commonInit(id,baudrate,u3tx::getPin(),u3rx::getPin(),
                       u3rts::getPin(),u3cts::getPin());
            break;
    }
}

STM32Serial::STM32Serial(int id, int baudrate, GpioPin tx, GpioPin rx)
    : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500),
      flowControl(false), portId(id)
{
    commonInit(id,baudrate,tx,rx,tx,rx); //The last two args will be ignored
}

STM32Serial::STM32Serial(int id, int baudrate, GpioPin tx, GpioPin rx,
    miosix::GpioPin rts, miosix::GpioPin cts)
    : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500),
      flowControl(true), portId(id)
{
    commonInit(id,baudrate,tx,rx,rts,cts);
}

void STM32Serial::commonInit(int id, int baudrate, GpioPin tx, GpioPin rx,
                             GpioPin rts, GpioPin cts)
{
    #ifdef SERIAL_DMA
    dmaTx=0;
    dmaRx=0;
    txWaiting=0;
    dmaTxInProgress=false;
    #endif //SERIAL_DMA
    InterruptDisableLock dLock;
    if(id<1|| id>numPorts || ports[id-1]!=0) errorHandler(UNEXPECTED);
    ports[id-1]=this;
    unsigned int freq=SystemCoreClock;
    //Quirk the position of the PPRE1 and PPRE2 bitfields in RCC->CFGR changes
    //STM32F0 does not have ppre1 and ppre2, in this case the variables are not
    //defined in order to avoid "unused variable" warning
    #if defined(_ARCH_CORTEXM3_STM32)   || defined(_ARCH_CORTEXM3_STM32L1) \
     || defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4)
    const unsigned int ppre1=8;
    const unsigned int ppre2=11;
    #elif !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32)
    const unsigned int ppre1=10;
    const unsigned int ppre2=13;
    #endif
    switch(id)
    {
        case 1:
            port=USART1;
            RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
            RCC_SYNC();
            #ifdef SERIAL_1_DMA
            #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
             || defined(_ARCH_CORTEXM4_STM32L4)
            #ifdef _ARCH_CORTEXM4_STM32L4
            RCC->AHB1ENR |= RCC_AHBENR_DMA1EN;
            DMA1_CSELR->CSELR  |= (2 << DMA_CSELR_C4S_Pos) // Assign DMA1_CH4 to USART1_TX
                               |  (2 << DMA_CSELR_C5S_Pos);// Assign DMA1_CH5 to USART1_RX
            #else 
            RCC->AHBENR |= RCC_AHBENR_DMA1EN;
            #endif
            RCC_SYNC();
            NVIC_SetPriority(DMA1_Channel4_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA1_Channel4_IRQn);
            dmaTx=DMA1_Channel4;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA1_Channel5_IRQn,14);
            NVIC_EnableIRQ(DMA1_Channel5_IRQn);
            dmaRx=DMA1_Channel5;
            #else //stm32f2, stm32f4
            RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
            RCC_SYNC();
            NVIC_SetPriority(DMA2_Stream7_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA2_Stream7_IRQn);
            dmaTx=DMA2_Stream7;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA2_Stream5_IRQn,14);
            NVIC_EnableIRQ(DMA2_Stream5_IRQn);
            dmaRx=DMA2_Stream5;
            #endif
            port->CR3=USART_CR3_DMAT | USART_CR3_DMAR;
            #endif //SERIAL_1_DMA
            NVIC_SetPriority(USART1_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(USART1_IRQn);
            #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32)
            if(RCC->CFGR & RCC_CFGR_PPRE2_2) freq/=1<<(((RCC->CFGR>>ppre2) & 0x3)+1);
            #elif defined(_ARCH_CORTEXM0_STM32)
            // STM32F0 family has only PPRE2 register
            if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1);
            #else
            //rcc_hclk3 = SystemCoreClock / HPRE
            //rcc_pclk2 = rcc_hclk1 / D2PPRE2
            //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal?
            //usart1 clock is rcc_pclk2
            if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3)
                freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1);
            if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE2_2)
                freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE2_Pos) & 0x3)+1);
            #endif //_ARCH_CORTEXM7_STM32H7
            break;
        
        #if !defined(STM32_NO_SERIAL_2_3)
        case 2:
            port=USART2;
            #ifndef _ARCH_CORTEXM7_STM32H7
            #ifndef _ARCH_CORTEXM4_STM32L4
            RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
            #else  //_ARCH_CORTEXM4_STM32L4
            RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
            #endif //_ARCH_CORTEXM4_STM32L4
            #else  //_ARCH_CORTEXM7_STM32H7
            RCC->APB1LENR |= RCC_APB1LENR_USART2EN;
            #endif //_ARCH_CORTEXM7_STM32H7
            RCC_SYNC();
            #ifdef SERIAL_2_DMA
            #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
             || defined(_ARCH_CORTEXM4_STM32L4)
            #ifdef _ARCH_CORTEXM4_STM32L4
            RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
            DMA1_CSELR->CSELR  |= (2 << DMA_CSELR_C7S_Pos) // Assign DMA1_CH7 to USART2_TX
                               |  (2 << DMA_CSELR_C6S_Pos);// Assign DMA1_CH6 to USART2_RX
            #else 
            RCC->AHBENR |= RCC_AHBENR_DMA1EN;
            #endif
            RCC_SYNC();
            NVIC_SetPriority(DMA1_Channel7_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA1_Channel7_IRQn);
            dmaTx=DMA1_Channel7;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA1_Channel6_IRQn,14);
            NVIC_EnableIRQ(DMA1_Channel6_IRQn);
            dmaRx=DMA1_Channel6;
            #else //stm32f2, stm32f4
            RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
            RCC_SYNC();
            NVIC_SetPriority(DMA1_Stream6_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA1_Stream6_IRQn);
            dmaTx=DMA1_Stream6;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA1_Stream5_IRQn,14);
            NVIC_EnableIRQ(DMA1_Stream5_IRQn);
            dmaRx=DMA1_Stream5;
            #endif
            port->CR3=USART_CR3_DMAT | USART_CR3_DMAR;
            #endif //SERIAL_2_DMA
            NVIC_SetPriority(USART2_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(USART2_IRQn);
            #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32)
            if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/=1<<(((RCC->CFGR>>ppre1) & 0x3)+1);
            #elif defined(_ARCH_CORTEXM0_STM32)
            // STM32F0 family has only PPRE2 register
            if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1);
            #else //_ARCH_CORTEXM7_STM32H7
            //rcc_hclk3 = SystemCoreClock / HPRE
            //rcc_pclk1 = rcc_hclk1 / D2PPRE1
            //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal?
            //usart2 clock is rcc_pclk1
            if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3)
                freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1);
            if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_2)
                freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE1_Pos) & 0x3)+1);
            #endif //_ARCH_CORTEXM7_STM32H7
            break;
        #if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
        case 3:
            port=USART3;
            #ifndef _ARCH_CORTEXM7_STM32H7
            #ifndef _ARCH_CORTEXM4_STM32L4
            RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
            #else  //_ARCH_CORTEXM4_STM32L4
            RCC->APB1ENR1 |= RCC_APB1ENR1_USART3EN;
            #endif //_ARCH_CORTEXM4_STM32L4
            #else
            RCC->APB1LENR |= RCC_APB1LENR_USART3EN;
            #endif //_ARCH_CORTEXM7_STM32H7
            RCC_SYNC();
            #ifdef SERIAL_3_DMA
            #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
             || defined(_ARCH_CORTEXM4_STM32L4)
            #ifdef _ARCH_CORTEXM4_STM32L4
            RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
            DMA1_CSELR->CSELR  |= (2 << DMA_CSELR_C2S_Pos) // Assign DMA1_CH2 to USART2_TX
                               |  (2 << DMA_CSELR_C3S_Pos);// Assign DMA1_CH3 to USART2_RX
            #else 
            RCC->AHBENR |= RCC_AHBENR_DMA1EN;
            #endif
            RCC_SYNC();
            NVIC_SetPriority(DMA1_Channel2_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA1_Channel2_IRQn);
            dmaTx=DMA1_Channel2;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA1_Channel3_IRQn,14);
            NVIC_EnableIRQ(DMA1_Channel3_IRQn);
            dmaRx=DMA1_Channel3;
            #else //stm32f2, stm32f4
            RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
            RCC_SYNC();
            NVIC_SetPriority(DMA1_Stream3_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(DMA1_Stream3_IRQn);
            dmaTx=DMA1_Stream3;
            //Higher priority to ensure IRQhandleDMArx() is called before
            //IRQhandleInterrupt(), so that idle is set correctly
            NVIC_SetPriority(DMA1_Stream1_IRQn,14);
            NVIC_EnableIRQ(DMA1_Stream1_IRQn);
            dmaRx=DMA1_Stream1;
            #endif
            port->CR3=USART_CR3_DMAT | USART_CR3_DMAR;
            #endif //SERIAL_3_DMA
            #if !defined(STM32F072xB)
            NVIC_SetPriority(USART3_IRQn,15);//Lowest priority for serial
            NVIC_EnableIRQ(USART3_IRQn);
            #else  //STM32F072xB
            NVIC_SetPriority(USART3_4_IRQn,15);
            NVIC_EnableIRQ(USART3_4_IRQn);
            #endif //STM32F072xB
            #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32)
            if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/=1<<(((RCC->CFGR>>ppre1) & 0x3)+1);
            #elif defined(_ARCH_CORTEXM0_STM32)
            // STM32F0 family has only PPRE2 register
            if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1);
            #else //_ARCH_CORTEXM7_STM32H7
            //rcc_hclk3 = SystemCoreClock / HPRE
            //rcc_pclk1 = rcc_hclk1 / D2PPRE1
            //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal?
            //usart2 clock is rcc_pclk1
            if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3)
                freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1);
            if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_2)
                freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE1_Pos) & 0x3)+1);
            #endif //_ARCH_CORTEXM7_STM32H7
            break;
        #endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
        #endif //!defined(STM32_NO_SERIAL_2_3)
    }
    //Quirk: stm32f1 rx pin has to be in input mode, while stm32f2 and up want
    //it in ALTERNATE mode. Go figure...
    #ifdef _ARCH_CORTEXM3_STM32
    Mode::Mode_ rxPinMode=Mode::INPUT;
    #else //_ARCH_CORTEXM3_STM32
    Mode::Mode_ rxPinMode=Mode::ALTERNATE;
    #endif //_ARCH_CORTEXM3_STM32
    tx.mode(Mode::ALTERNATE);
    rx.mode(rxPinMode);
    if(flowControl)
    {
        rts.mode(Mode::ALTERNATE);
        cts.mode(rxPinMode);
    }
    const unsigned int quot=2*freq/baudrate; //2*freq for round to nearest
    port->BRR=quot/2 + (quot & 1);           //Round to nearest
    if(flowControl==false) port->CR3 |= USART_CR3_ONEBIT;
    else port->CR3 |= USART_CR3_ONEBIT | USART_CR3_RTSE | USART_CR3_CTSE;
    //Enabled, 8 data bit, no parity, interrupt on character rx
    #ifdef SERIAL_DMA
    if(dmaTx)
    {
        port->CR1 = USART_CR1_UE     //Enable port
                  | USART_CR1_IDLEIE //Interrupt on idle line
                  | USART_CR1_TE     //Transmission enbled
                  | USART_CR1_RE;    //Reception enabled
        IRQdmaReadStart();
        return;
    }
    #endif //SERIAL_DMA
    port->CR1 = USART_CR1_UE     //Enable port
              | USART_CR1_RXNEIE //Interrupt on data received
              | USART_CR1_IDLEIE //Interrupt on idle line
              | USART_CR1_TE     //Transmission enbled
              | USART_CR1_RE;    //Reception enabled
}

ssize_t STM32Serial::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 STM32Serial::writeBlock(const void *buffer, size_t size, off_t where)
{
    Lock<FastMutex> l(txMutex);
    const char *buf=reinterpret_cast<const char*>(buffer);
    #ifdef SERIAL_DMA
    if(dmaTx)
    {
        size_t remaining=size;
        if(isInCCMarea(buf)==false)
        {
            //Use zero copy for all but the last txBufferSize bytes, if possible
            while(remaining>txBufferSize)
            {
                //DMA is limited to 64K
                size_t transferSize=min<size_t>(remaining-txBufferSize,65535);
                waitDmaTxCompletion();
                writeDma(buf,transferSize);
                buf+=transferSize;
                remaining-=transferSize;
            }
        }
        while(remaining>0)
        {
            size_t transferSize=min(remaining,static_cast<size_t>(txBufferSize));
            waitDmaTxCompletion();
            //Copy to txBuffer only after DMA xfer completed, as the previous
            //xfer may be using the same buffer
            memcpy(txBuffer,buf,transferSize);
            writeDma(txBuffer,transferSize);
            buf+=transferSize;
            remaining-=transferSize;
        }
        return size;
    }
    #endif //SERIAL_DMA
    for(size_t i=0;i<size;i++)
    {
        #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
         && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
         && !defined(_ARCH_CORTEXM4_STM32L4)
        while((port->SR & USART_SR_TXE)==0) ;
        port->DR=*buf++;
        #else //_ARCH_CORTEXM7_STM32F7/H7
        while((port->ISR & USART_ISR_TXE)==0) ;
        port->TDR=*buf++;
        #endif //_ARCH_CORTEXM7_STM32F7/H7
    }
    return size;
}

void STM32Serial::IRQwrite(const char *str)
{
    // We can reach here also with only kernel paused, so make sure
    // interrupts are disabled. This is important for the DMA case
    bool interrupts=areInterruptsEnabled();
    if(interrupts) fastDisableInterrupts();
    #ifdef SERIAL_DMA
    if(dmaTx)
    {
        #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
         || defined(_ARCH_CORTEXM4_STM32L4)
        //If no DMA transfer is in progress bit EN is zero. Otherwise wait until
        //DMA xfer ends, by waiting for the TC (or TE) interrupt flag
        static const unsigned int irqMask[]=
        {
            (DMA_ISR_TCIF4 | DMA_ISR_TEIF4),
            (DMA_ISR_TCIF7 | DMA_ISR_TEIF7),
            (DMA_ISR_TCIF2 | DMA_ISR_TEIF2)
        };
        #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4)
        // Workaround for ST messing up with flag definitions...
        constexpr unsigned int DMA_CCR4_EN = DMA_CCR_EN;
        #endif
        while((dmaTx->CCR & DMA_CCR4_EN) && !(DMA1->ISR & irqMask[getId()-1])) ;
        #else //_ARCH_CORTEXM3_STM32
        //Wait until DMA xfer ends. EN bit is cleared by hardware on transfer end
        while(dmaTx->CR & DMA_SxCR_EN) ;
        #endif //_ARCH_CORTEXM3_STM32
    }
    #endif //SERIAL_DMA
    while(*str)
    {
        #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
         && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
         && !defined(_ARCH_CORTEXM4_STM32L4)
        while((port->SR & USART_SR_TXE)==0) ;
        port->DR=*str++;
        #else //_ARCH_CORTEXM7_STM32F7/H7
        while((port->ISR & USART_ISR_TXE)==0) ;
        port->TDR=*str++;
        #endif //_ARCH_CORTEXM7_STM32F7/H7
    }
    waitSerialTxFifoEmpty();
    if(interrupts) fastEnableInterrupts();
}

int STM32Serial::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 | (flowControl ? CRTSCTS : 0);
            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 STM32Serial::IRQhandleInterrupt()
{
    #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
     && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
     && !defined(_ARCH_CORTEXM4_STM32L4)
    unsigned int status=port->SR;
    #else //_ARCH_CORTEXM7_STM32F7/H7
    unsigned int status=port->ISR;
    constexpr unsigned int USART_SR_RXNE=USART_ISR_RXNE;
    constexpr unsigned int USART_SR_IDLE=USART_ISR_IDLE;
    constexpr unsigned int USART_SR_FE  =USART_ISR_FE;
    #endif //_ARCH_CORTEXM7_STM32F7/H7
    char c;
    #ifdef SERIAL_DMA
    if(dmaRx==0 && (status & USART_SR_RXNE))
    #else //SERIAL_DMA
    if(status & USART_SR_RXNE)
    #endif //SERIAL_DMA
    {
        //Always read data, since this clears interrupt flags
        #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
         && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
         && !defined(_ARCH_CORTEXM4_STM32L4)
        c=port->DR;
        #else //_ARCH_CORTEXM7_STM32F7/H7
        c=port->RDR;
        #endif //_ARCH_CORTEXM7_STM32F7/H7
        //If no error put data in buffer
        if((status & USART_SR_FE)==0)
            if(rxQueue.tryPut(c)==false) /*fifo overflow*/;
        idle=false;
    }
    if(status & USART_SR_IDLE)
    {
        #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
         && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
         && !defined(_ARCH_CORTEXM4_STM32L4)
        c=port->DR; //clears interrupt flags
        #else //_ARCH_CORTEXM7_STM32F7/H7
        port->ICR=USART_ICR_IDLECF; //clears interrupt flags
        #endif //_ARCH_CORTEXM7_STM32F7/H7
        #ifdef SERIAL_DMA
        if(dmaRx) IRQreadDma();
        #endif //SERIAL_DMA
        idle=true;
    }
    if((status & USART_SR_IDLE) || rxQueue.size()>=rxQueueMin)
    {
        //Enough data in buffer or idle line, awake thread
        if(rxWaiting)
        {
            rxWaiting->IRQwakeup();
            if(rxWaiting->IRQgetPriority()>
                Thread::IRQgetCurrentThread()->IRQgetPriority())
                    Scheduler::IRQfindNextThread();
            rxWaiting=0;
        }
    }
}

#ifdef SERIAL_DMA
void STM32Serial::IRQhandleDMAtx()
{
    dmaTxInProgress=false;
    if(txWaiting==0) return;
    txWaiting->IRQwakeup();
    if(txWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
        Scheduler::IRQfindNextThread();
    txWaiting=0;
}

void STM32Serial::IRQhandleDMArx()
{
    IRQreadDma();
    idle=false;
    if(rxWaiting==0) return;
    rxWaiting->IRQwakeup();
    if(rxWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
        Scheduler::IRQfindNextThread();
    rxWaiting=0;
}
#endif //SERIAL_DMA

STM32Serial::~STM32Serial()
{
    waitSerialTxFifoEmpty();
    {
        InterruptDisableLock dLock;
        port->CR1=0;
        int id=getId();
        ports[id-1]=0;
        switch(id)
        {
            case 1:
                #ifdef SERIAL_1_DMA
                IRQdmaReadStop();
                #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
                 || defined(_ARCH_CORTEXM4_STM32L4)
                NVIC_DisableIRQ(DMA1_Channel4_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel4_IRQn);
                NVIC_DisableIRQ(DMA1_Channel5_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel5_IRQn);
                #else //stm32f2, stm32f4
                NVIC_DisableIRQ(DMA2_Stream7_IRQn);
                NVIC_ClearPendingIRQ(DMA2_Stream7_IRQn);
                NVIC_DisableIRQ(DMA2_Stream5_IRQn);
                NVIC_ClearPendingIRQ(DMA2_Stream5_IRQn);
                #endif
                #endif //SERIAL_1_DMA
                NVIC_DisableIRQ(USART1_IRQn);
                NVIC_ClearPendingIRQ(USART1_IRQn);
                RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN;
                break;
                
            #if !defined(STM32_NO_SERIAL_2_3)    
            case 2:
                #ifdef SERIAL_2_DMA
                IRQdmaReadStop();
                #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
                 || defined(_ARCH_CORTEXM4_STM32L4)
                NVIC_DisableIRQ(DMA1_Channel7_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel7_IRQn);
                NVIC_DisableIRQ(DMA1_Channel6_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel6_IRQn);
                #else //stm32f2, stm32f4
                NVIC_DisableIRQ(DMA1_Stream6_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Stream6_IRQn);
                NVIC_DisableIRQ(DMA1_Stream5_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Stream5_IRQn);
                #endif
                #endif //SERIAL_2_DMA
                NVIC_DisableIRQ(USART2_IRQn);
                NVIC_ClearPendingIRQ(USART2_IRQn);
                #ifndef _ARCH_CORTEXM7_STM32H7
                #ifdef _ARCH_CORTEXM4_STM32L4
                RCC->APB1ENR1 &= ~RCC_APB1ENR1_USART2EN;
                #else
                RCC->APB1ENR &= ~RCC_APB1ENR_USART2EN;
                #endif
                #else //_ARCH_CORTEXM7_STM32H7
                RCC->APB1LENR &= ~RCC_APB1LENR_USART2EN;
                #endif //_ARCH_CORTEXM7_STM32H7
                break;
            #if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
            case 3:
                #ifdef SERIAL_3_DMA
                IRQdmaReadStop();
                #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
                 || defined(_ARCH_CORTEXM4_STM32L4)
                NVIC_DisableIRQ(DMA1_Channel2_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel2_IRQn);
                NVIC_DisableIRQ(DMA1_Channel3_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Channel3_IRQn);
                #else //stm32f2, stm32f4
                NVIC_DisableIRQ(DMA1_Stream3_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Stream3_IRQn);
                NVIC_DisableIRQ(DMA1_Stream1_IRQn);
                NVIC_ClearPendingIRQ(DMA1_Stream1_IRQn);
                #endif
                #endif //SERIAL_3_DMA
                #if !defined(STM32F072xB)
                NVIC_SetPriority(USART3_IRQn,15);//Lowest priority for serial
                NVIC_EnableIRQ(USART3_IRQn);
                #else  //STM32F072xB
                NVIC_SetPriority(USART3_4_IRQn,15);
                NVIC_EnableIRQ(USART3_4_IRQn);
                #endif //STM32F072xB
                #ifndef _ARCH_CORTEXM7_STM32H7
                #ifdef _ARCH_CORTEXM4_STM32L4
                RCC->APB1ENR1 &= ~RCC_APB1ENR1_USART3EN;
                #else
                RCC->APB1ENR &= ~RCC_APB1ENR_USART3EN;
                #endif
                #else //_ARCH_CORTEXM7_STM32H7
                RCC->APB1LENR &= ~RCC_APB1LENR_USART3EN;
                #endif //_ARCH_CORTEXM7_STM32H7
                break;
            #endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC)
            #endif //!defined(STM32_NO_SERIAL_2_3)
        }
    }
}

#ifdef SERIAL_DMA
void STM32Serial::waitDmaTxCompletion()
{
    FastInterruptDisableLock dLock;
    // If a previous DMA xfer is in progress, wait
    if(dmaTxInProgress)
    {
        txWaiting=Thread::IRQgetCurrentThread();
        do {
            Thread::IRQwait();
            {
                FastInterruptEnableLock eLock(dLock);
                Thread::yield();
            }
        } while(txWaiting);
    }
}

void STM32Serial::writeDma(const char *buffer, size_t size)
{
    markBufferBeforeDmaWrite(buffer,size);
    //Quirk: DMA messes up the TC bit, and causes waitSerialTxFifoEmpty() to
    //return prematurely, causing characters to be missed when rebooting
    //immediatley a write. You can just clear the bit manually, but doing that
    //is dangerous, as if you clear the bit but for any reason the serial
    //write doesn't start (think an invalid buffer, or another thread crashing),
    //then TC will never be set and waitSerialTxFifoEmpty() deadlocks!
    //The only way to clear it safely is to first read SR and then write to
    //DR (thus the bit is cleared at the same time a transmission is started,
    //and the race condition is eliminated). This is the purpose of this
    //instruction, it reads SR. When we start the DMA, the DMA controller
    //writes to DR and completes the TC clear sequence.
    #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
     && !defined(_ARCH_CORTEXM0_STM32)   && !defined(_ARCH_CORTEXM4_STM32F3) \
     && !defined(_ARCH_CORTEXM4_STM32L4)
    while((port->SR & USART_SR_TXE)==0) ;
    #else //_ARCH_CORTEXM7_STM32F7/H7
    while((port->ISR & USART_ISR_TXE)==0) ;
    #endif //_ARCH_CORTEXM7_STM32F7/H7
    
    dmaTxInProgress=true;
    #if defined(_ARCH_CORTEXM3_STM32)
    dmaTx->CPAR=reinterpret_cast<unsigned int>(&port->DR);
    dmaTx->CMAR=reinterpret_cast<unsigned int>(buffer);
    dmaTx->CNDTR=size;
    dmaTx->CCR=DMA_CCR4_MINC  //Increment RAM pointer
             | DMA_CCR4_DIR   //Memory to peripheral
             | DMA_CCR4_TEIE  //Interrupt on transfer error
             | DMA_CCR4_TCIE  //Interrupt on transfer complete
             | DMA_CCR4_EN;   //Start DMA
    #else
    #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4)
    dmaTx->CPAR=reinterpret_cast<unsigned int>(&port->TDR);
    dmaTx->CMAR=reinterpret_cast<unsigned int>(buffer);
    dmaTx->CNDTR=size;
    dmaTx->CCR=DMA_CCR_MINC  //Increment RAM pointer
             | DMA_CCR_DIR   //Memory to peripheral
             | DMA_CCR_TEIE  //Interrupt on transfer error
             | DMA_CCR_TCIE  //Interrupt on transfer complete
             | DMA_CCR_EN;   //Start DMA
    #else  //_ARCH_CORTEXM4_STM32F3
    #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
     && !defined(_ARCH_CORTEXM0_STM32)
    dmaTx->PAR=reinterpret_cast<unsigned int>(&port->DR);
    #else //_ARCH_CORTEXM7_STM32F7/H7
    dmaTx->PAR=reinterpret_cast<unsigned int>(&port->TDR);
    #endif //_ARCH_CORTEXM7_STM32F7/H7
    dmaTx->M0AR=reinterpret_cast<unsigned int>(buffer);
    dmaTx->NDTR=size;
    //Quirk: not enabling DMA_SxFCR_FEIE because the USART seems to
    //generate a spurious fifo error. The code was tested and the
    //transfer completes successfully even in the presence of this fifo
    //error
    dmaTx->FCR=DMA_SxFCR_DMDIS;//Enable fifo
    dmaTx->CR=DMA_SxCR_CHSEL_2 //Select channel 4 (USART_TX)
                   | DMA_SxCR_MINC    //Increment RAM pointer
                   | DMA_SxCR_DIR_0   //Memory to peripheral
                   | DMA_SxCR_TCIE    //Interrupt on completion
                   | DMA_SxCR_TEIE    //Interrupt on transfer error
                   | DMA_SxCR_DMEIE   //Interrupt on direct mode error
                   | DMA_SxCR_EN;     //Start the DMA
    #endif //_ARCH_CORTEXM4_STM32F3
    #endif //_ARCH_CORTEXM3_STM32
}

void STM32Serial::IRQreadDma()
{
    int elem=IRQdmaReadStop();
    markBufferAfterDmaRead(rxBuffer,rxQueueMin);
    for(int i=0;i<elem;i++)
        if(rxQueue.tryPut(rxBuffer[i])==false) /*fifo overflow*/;
    IRQdmaReadStart();
}

void STM32Serial::IRQdmaReadStart()
{
    #if defined(_ARCH_CORTEXM3_STM32)
    dmaRx->CPAR=reinterpret_cast<unsigned int>(&port->DR);
    dmaRx->CMAR=reinterpret_cast<unsigned int>(rxBuffer);
    dmaRx->CNDTR=rxQueueMin;
    dmaRx->CCR=DMA_CCR4_MINC  //Increment RAM pointer
             | 0              //Peripheral to memory
             | DMA_CCR4_TEIE  //Interrupt on transfer error
             | DMA_CCR4_TCIE  //Interrupt on transfer complete
             | DMA_CCR4_EN;   //Start DMA
    #else
    #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4)
    dmaRx->CPAR=reinterpret_cast<unsigned int>(&port->RDR);
    dmaRx->CMAR=reinterpret_cast<unsigned int>(rxBuffer);
    dmaRx->CNDTR=rxQueueMin;
    dmaRx->CCR=DMA_CCR_MINC  //Increment RAM pointer
             | 0              //Peripheral to memory
             | DMA_CCR_TEIE  //Interrupt on transfer error
             | DMA_CCR_TCIE  //Interrupt on transfer complete
             | DMA_CCR_EN;   //Start DMA
    #else //_ARCH_CORTEXM4_STM32F3
    #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \
     && !defined(_ARCH_CORTEXM0_STM32)
    dmaRx->PAR=reinterpret_cast<unsigned int>(&port->DR);
    #else //_ARCH_CORTEXM7_STM32F7/H7
    dmaRx->PAR=reinterpret_cast<unsigned int>(&port->RDR);
    #endif //_ARCH_CORTEXM7_STM32F7/H7
    dmaRx->M0AR=reinterpret_cast<unsigned int>(rxBuffer);
    dmaRx->NDTR=rxQueueMin;
    dmaRx->CR=DMA_SxCR_CHSEL_2 //Select channel 4 (USART_RX)
                   | DMA_SxCR_MINC    //Increment RAM pointer
                   | 0                //Peripheral to memory
                   | DMA_SxCR_HTIE    //Interrupt on half transfer
                   | DMA_SxCR_TEIE    //Interrupt on transfer error
                   | DMA_SxCR_DMEIE   //Interrupt on direct mode error
                   | DMA_SxCR_EN;     //Start the DMA
    #endif //_ARCH_CORTEXM4_STM32F3
    #endif //_ARCH_CORTEXM3_STM32
}

int STM32Serial::IRQdmaReadStop()
{
    #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \
     || defined(_ARCH_CORTEXM4_STM32L4)
    dmaRx->CCR=0;
    static const unsigned int irqMask[]=
    {
        DMA_IFCR_CGIF5,
        DMA_IFCR_CGIF6,
        DMA_IFCR_CGIF3   
    };
    DMA1->IFCR=irqMask[getId()-1];
    return rxQueueMin-dmaRx->CNDTR;
    #else //_ARCH_CORTEXM3_STM32
    //Stop DMA and wait for it to actually stop
    dmaRx->CR &= ~DMA_SxCR_EN;
    while(dmaRx->CR & DMA_SxCR_EN) ;
    static const unsigned int irqMask[]=
    {
        (DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5),
        (DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5),
        (DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1)
    };
    static volatile unsigned long * const irqRegs[]=
    {
        &DMA2->HIFCR,
        &DMA1->HIFCR,
        &DMA1->LIFCR
    };
    *irqRegs[getId()-1]=irqMask[getId()-1];
    return rxQueueMin-dmaRx->NDTR;
    #endif //_ARCH_CORTEXM3_STM32
}
#endif //SERIAL_DMA

} //namespace miosix
