/*==========================================================================
//
//      stm32_misc.c
//
//      Cortex-M STM32 HAL functions
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2008, 2009 Free Software Foundation, Inc.                        
//
// eCos 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 or (at your option) any later      
// version.                                                                 
//
// eCos 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.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// 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 section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    nickg
// Date:         2008-07-30
// Description:  
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/hal.h>
#include <pkgconf/hal_cortexm.h>
#include <pkgconf/hal_cortexm_stm32.h>
#ifdef CYGPKG_KERNEL
#include <pkgconf/kernel.h>
#endif

#include <cyg/infra/diag.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_trac.h>         // tracing macros
#include <cyg/infra/cyg_ass.h>          // assertion macros

#include <cyg/hal/hal_arch.h>           // HAL header
#include <cyg/hal/hal_intr.h>           // HAL header
#include <cyg/hal/hal_if.h>             // HAL header

//==========================================================================
// Clock Initialization values

#if CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 1
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_1
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 2
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_2
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 4
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_4
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 8
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_8
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 16
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_16
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 64
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_64
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 128
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_128
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 256
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_256
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV == 512
# define CYGHWR_HAL_STM32_RCC_CFGR_HPRE CYGHWR_HAL_STM32_RCC_CFGR_HPRE_512
#endif

#if CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 1
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE1 CYGHWR_HAL_STM32_RCC_CFGR_PPRE1_1
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 2
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE1 CYGHWR_HAL_STM32_RCC_CFGR_PPRE1_2
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 4
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE1 CYGHWR_HAL_STM32_RCC_CFGR_PPRE1_4
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 8
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE1 CYGHWR_HAL_STM32_RCC_CFGR_PPRE1_8
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 16
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE1 CYGHWR_HAL_STM32_RCC_CFGR_PPRE1_16
#endif

#if CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 1
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE2 CYGHWR_HAL_STM32_RCC_CFGR_PPRE2_1
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 2
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE2 CYGHWR_HAL_STM32_RCC_CFGR_PPRE2_2
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 4
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE2 CYGHWR_HAL_STM32_RCC_CFGR_PPRE2_4
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 8
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE2 CYGHWR_HAL_STM32_RCC_CFGR_PPRE2_8
#elif CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 16
# define CYGHWR_HAL_STM32_RCC_CFGR_PPRE2 CYGHWR_HAL_STM32_RCC_CFGR_PPRE2_16
#endif


//==========================================================================
// Clock frequencies
//
// These are set to the frequencies of the various system clocks.

cyg_uint32 hal_stm32_sysclk;
cyg_uint32 hal_stm32_hclk;
cyg_uint32 hal_stm32_pclk1;
cyg_uint32 hal_stm32_pclk2;
cyg_uint32 hal_cortexm_systick_clock;

void hal_start_clocks( void );
cyg_uint32 hal_exti_isr( cyg_uint32 vector, CYG_ADDRWORD data );

//==========================================================================

void hal_variant_init( void )
{

#if 1 //!defined(CYG_HAL_STARTUP_RAM)
    hal_start_clocks();
#endif

    // Attach EXTI springboard to interrupt vectors
    HAL_INTERRUPT_ATTACH( CYGNUM_HAL_INTERRUPT_EXTI9_5,   hal_exti_isr, 0, 0 );
    HAL_INTERRUPT_ATTACH( CYGNUM_HAL_INTERRUPT_EXTI15_10, hal_exti_isr, 0, 0 );
    
#ifdef CYGSEM_HAL_VIRTUAL_VECTOR_SUPPORT
    hal_if_init();
#endif
}

//==========================================================================
// Setup up system clocks
//
// Set up clocks from configuration. In the future this should be extended so
// that clock rates can be changed at runtime.

void hal_start_clocks( void )
{
    CYG_ADDRESS rcc = CYGHWR_HAL_STM32_RCC;
    cyg_uint32 cr, cfgr;
    
    // Reset RCC

    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, 0x00000001 );
    
    // Start up HSE clock
    
    HAL_READ_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    cr &= CYGHWR_HAL_STM32_RCC_CR_HSEON|CYGHWR_HAL_STM32_RCC_CR_HSEBYP;
    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );

    HAL_READ_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    cr |= CYGHWR_HAL_STM32_RCC_CR_HSEON;
    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    
    // Wait for HSE clock to startup
    
    do
    {
        HAL_READ_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    } while( !(cr & CYGHWR_HAL_STM32_RCC_CR_HSERDY) );

    // Configure clocks
    
    hal_stm32_sysclk = CYGARC_HAL_CORTEXM_STM32_INPUT_CLOCK;
    
    cfgr = 0;

#if defined(CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_SOURCE_HSE)
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_PLLSRC_HSE;
#elif defined(CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_SOURCE_HSE_HALF)
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_PLLSRC_HSE |
            CYGHWR_HAL_STM32_RCC_CFGR_PLLXTPRE;
    hal_stm32_sysclk /= 2;
#elif defined(CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_SOURCE_HSI_HALF)
    hal_stm32_sysclk /= 2;
#endif
    
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_PLLMUL(CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_MUL);
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_HPRE;
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_PPRE1;
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_PPRE2;

    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CFGR, cfgr );

    // Enable the PLL and wait for it to lock
    
    cr |= CYGHWR_HAL_STM32_RCC_CR_PLLON;

    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    do
    {
        HAL_READ_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CR, cr );
    } while( !(cr & CYGHWR_HAL_STM32_RCC_CR_PLLRDY) );

    // Now switch to use PLL as SYSCLK
    
    cfgr |= CYGHWR_HAL_STM32_RCC_CFGR_SW_PLL;

    HAL_WRITE_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CFGR, cfgr );
    do
    {
        HAL_READ_UINT32( rcc+CYGHWR_HAL_STM32_RCC_CFGR, cfgr );
    } while( (cfgr & CYGHWR_HAL_STM32_RCC_CFGR_SWS_XXX) != 
             CYGHWR_HAL_STM32_RCC_CFGR_SWS_PLL );

    // Calculate clocks from configuration

    hal_stm32_sysclk *= CYGHWR_HAL_CORTEXM_STM32_CLOCK_PLL_MUL;
    hal_stm32_hclk = hal_stm32_sysclk / CYGHWR_HAL_CORTEXM_STM32_CLOCK_HCLK_DIV;
    hal_stm32_pclk1 = hal_stm32_hclk / CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV;
    hal_stm32_pclk2 = hal_stm32_hclk / CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV;
    hal_cortexm_systick_clock = hal_stm32_hclk / 8;
}

//==========================================================================
// ISR springboard
//
// This is attached to the ISR table entries for EXTI9_5 and EXTI15_10
// to decode the contents of the EXTI registers and deliver the
// interrupt to the correct ISR.

cyg_uint32 hal_exti_isr( cyg_uint32 vector, CYG_ADDRWORD data )
{
    CYG_ADDRESS base = CYGHWR_HAL_STM32_EXTI;
    cyg_uint32 imr, pr;

    // Get EXTI pending and interrupt mask registers
    HAL_READ_UINT32( base+CYGHWR_HAL_STM32_EXTI_IMR, imr );
    HAL_READ_UINT32( base+CYGHWR_HAL_STM32_EXTI_PR, pr );

    // Mask PR by IMR and lose ls 5 bits
    pr &= imr;
    pr &= 0xFFFFFFE0;

    // Isolate LS pending bit and translate into interrupt vector
    // number.
    HAL_LSBIT_INDEX( vector, pr );
    vector += CYGNUM_HAL_INTERRUPT_EXTI5 - 5;

    // Deliver it
    hal_deliver_interrupt( vector );

    return 0;
}

//==========================================================================
// GPIO support
//
// These functions provide configuration and IO for GPIO pins.

__externC void hal_stm32_gpio_set( cyg_uint32 pin )
{
    cyg_uint32 port = CYGHWR_HAL_STM32_GPIO_PORT(pin);
    int bit = CYGHWR_HAL_STM32_GPIO_BIT(pin);
    cyg_uint32 cm = CYGHWR_HAL_STM32_GPIO_CFG(pin);
    cyg_uint32 cr;

    if( pin == CYGHWR_HAL_STM32_GPIO_NONE )
        return;
    
    if( bit > 7 ) port += 4, bit -= 8;
    HAL_READ_UINT32( port, cr );
    cr &= ~(0xF<<(bit*4));
    cr |= cm<<(bit*4);
    HAL_WRITE_UINT32( port, cr );

    // If this is a pullup/down input, set the ODR bit to switch on
    // the appropriate pullup/down resistor.
    if( cm == (CYGHWR_HAL_STM32_GPIO_MODE_IN|CYGHWR_HAL_STM32_GPIO_CNF_PULL) )
    {
        cyg_uint32 odr;
        port = CYGHWR_HAL_STM32_GPIO_PORT( pin );
        bit = CYGHWR_HAL_STM32_GPIO_BIT(pin);
        HAL_READ_UINT32( port+CYGHWR_HAL_STM32_GPIO_ODR, odr );
        if( pin & CYGHWR_HAL_STM32_GPIO_PULLUP )
            odr |= (1<<bit);
        else
            odr &= ~(1<<bit);
        HAL_WRITE_UINT32( port+CYGHWR_HAL_STM32_GPIO_ODR, odr );
    }
}
    
__externC void hal_stm32_gpio_out( cyg_uint32 pin, int val )
{
    cyg_uint32 port = CYGHWR_HAL_STM32_GPIO_PORT(pin);
    int bit = CYGHWR_HAL_STM32_GPIO_BIT(pin);
    
    port += CYGHWR_HAL_STM32_GPIO_BSRR;
    if( (val&1) == 0 ) port += 4;
    HAL_WRITE_UINT32( port, 1<<bit );
}
    
__externC void hal_stm32_gpio_in ( cyg_uint32 pin, int *val )
{
    cyg_uint32 port = CYGHWR_HAL_STM32_GPIO_PORT(pin);
    int bit = CYGHWR_HAL_STM32_GPIO_BIT(pin);
    cyg_uint32 pd;
    
    HAL_READ_UINT32( port+CYGHWR_HAL_STM32_GPIO_IDR, pd );
    *val = (pd>>bit)&1;
}

//==========================================================================
// Clock support.
//
// These functions provide support for enabling and disabling clock
// control bits.

__externC void hal_stm32_clock_enable( cyg_uint32 desc )
{
    cyg_uint32 r;
    cyg_uint32 reg = CYGHWR_HAL_STM32_RCC+CYGHWR_HAL_STM32_CLOCK_REG(desc);
    HAL_READ_UINT32( reg, r );
    r |= BIT_(CYGHWR_HAL_STM32_CLOCK_PIN(desc));
    HAL_WRITE_UINT32( reg, r );
}

__externC void hal_stm32_clock_disable( cyg_uint32 desc )
{
    cyg_uint32 r;
    cyg_uint32 reg = CYGHWR_HAL_STM32_RCC+CYGHWR_HAL_STM32_CLOCK_REG(desc);
    HAL_READ_UINT32( reg, r );
    r &= ~BIT_(CYGHWR_HAL_STM32_CLOCK_PIN(desc));
    HAL_WRITE_UINT32( reg, r );
}

//==========================================================================
// Backup domain

void hal_stm32_bd_protect( int protect )
{
    CYG_ADDRESS pwr = CYGHWR_HAL_STM32_PWR;
    cyg_uint32 cr;
    
    HAL_READ_UINT32( pwr+CYGHWR_HAL_STM32_PWR_CR, cr );
    if( protect )
        cr &= ~CYGHWR_HAL_STM32_PWR_CR_DBP;
    else
        cr |= CYGHWR_HAL_STM32_PWR_CR_DBP;
    HAL_WRITE_UINT32( pwr+CYGHWR_HAL_STM32_PWR_CR, cr );
}

//==========================================================================
// UART baud rate
//
// Set the baud rate divider of a UART based on the requested rate and
// the current APB clock settings.

void hal_stm32_uart_setbaud( cyg_uint32 base, cyg_uint32 baud )
{
    cyg_uint32 apbclk = hal_stm32_pclk1;
    cyg_uint32 int_div, frac_div;
    cyg_uint32 brr;
    
    if( base == CYGHWR_HAL_STM32_UART1 )
        apbclk = hal_stm32_pclk2;

    int_div = (25 * apbclk ) / (4 * baud );
    brr = ( int_div / 100 ) << 4;
    frac_div = int_div - (( brr >> 4 ) * 100 );

    brr |= (((frac_div * 16 ) + 50 ) / 100) & 0xF;

    HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_UART_BRR, brr );
}

//==========================================================================
// Timer clock rate
//
// Returns the current timer clock rate of a timer.

cyg_uint32 hal_stm32_timer_clock( CYG_ADDRESS base )
{
    if( base == CYGHWR_HAL_STM32_TIM1 ||
        base == CYGHWR_HAL_STM32_TIM8 )
    {
#if CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK2_DIV == 1
        return hal_stm32_pclk2;
#else
        return hal_stm32_pclk2 << 1;
#endif
    } else {
#if CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV == 1
        return hal_stm32_pclk1;
#else
        return hal_stm32_pclk1 << 1;
#endif
    }
}

//==========================================================================
// Profiling timer

#ifdef CYGPKG_PROFILE_GPROF

#include <cyg/profile/profile.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/drv_api.h>

// -------------------------------------------------------------------------
// These defines select the timer to be used. By default we use
// Timer6, which is one of the basic timers.  These can be changes to
// the values for any other timer.  Note that timers 1 and 8 are
// connected to PCLK2, all others use PCLK1.

#define CYGHWR_HAL_STM32_TIM                    CYGHWR_HAL_STM32_TIM6
#define CYGNUM_HAL_INTERRUPT_TIMER              CYGNUM_HAL_INTERRUPT_TIM6
#define HAL_STM32_PCLK                          hal_stm32_pclk1
#define CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK_DIV CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK1_DIV

// -------------------------------------------------------------------------
// Interrupt object

static cyg_interrupt    profile_interrupt;
static cyg_handle_t     profile_handle;

// -------------------------------------------------------------------------
// Saved state pointer.
//
// If break or Ctrl-C support is enabled, this is defined
// elsewhere. If not then we must define it ourself.

#if defined(CYGDBG_HAL_DEBUG_GDB_BREAK_SUPPORT) || \
    defined(CYGDBG_HAL_DEBUG_GDB_CTRLC_SUPPORT)

__externC HAL_SavedRegisters *hal_saved_interrupt_state;

#else

HAL_SavedRegisters *hal_saved_interrupt_state;

#endif

// -------------------------------------------------------------------------
// Profile DSR. This is not actually called.

static  void
profile_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    CYG_UNUSED_PARAM(cyg_vector_t, vector);
    CYG_UNUSED_PARAM(cyg_ucount32, count);
    CYG_UNUSED_PARAM(cyg_addrword_t, data);
}

// -------------------------------------------------------------------------
// Profile ISR.

static cyg_uint32
profile_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    CYG_ADDRESS base = CYGHWR_HAL_STM32_TIM;    
    HAL_SavedRegisters *regs = hal_saved_interrupt_state;

    // Clear the interrupt
    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_SR, 0 );

    __profile_hit(regs->u.interrupt.pc);
    HAL_INTERRUPT_ACKNOWLEDGE(vector);
    return CYG_ISR_HANDLED;
}

// -------------------------------------------------------------------------
// Enable profile timer.
//
// The requested resolution is in microseconds. We divide PCLK by
// 1000000 to give ticks per microsecond, and program that as the
// prescaler divider to provide a 1MHz clock to the timer. Then the
// timer counter can be programmed directly with the selected
// resolution.

int
hal_enable_profile_timer(int resolution)
{
    CYG_ADDRESS base = CYGHWR_HAL_STM32_TIM;
    cyg_uint32 period;

    // Set initial period to selected PCLK
    period = HAL_STM32_PCLK;

    // If the PCLK divider is 1 then the timers are fed directly with
    // PCLK, otherwise they are fed twice the divided frequency.
    if( CYGHWR_HAL_CORTEXM_STM32_CLOCK_PCLK_DIV != 1 )
        period *= 2;
    
    // Divide by 1000000 to generate a divider which will give an
    // input clock of 1MHz.
    period = period / 1000000;
    
    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_PSC, period-1 );

    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_CR2, 0 );

    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_DIER, CYGHWR_HAL_STM32_TIM_DIER_UIE );

    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_ARR, resolution );
    
    // Create interrupt object.
    cyg_drv_interrupt_create( CYGNUM_HAL_INTERRUPT_TIMER, 1, 0, (cyg_ISR_t*) &profile_isr, &profile_dsr, &profile_handle, &profile_interrupt);
    cyg_drv_interrupt_attach(profile_handle);
    cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_TIMER);

    // Start the timer counting
    HAL_WRITE_UINT32(base+CYGHWR_HAL_STM32_TIM_CR1, CYGHWR_HAL_STM32_TIM_CR1_CEN);
    
    return resolution;
}

#endif // CYGPKG_PROFILE_GPROF

//==========================================================================
// EOF stm32_misc.c
