/*
 * \file stm32f10x_sflash.c
 * \brief
 *    Flash read/write functionality for STM32F10x
 *
 * This file is part of the driver project
 *
 * Copyright (C) 2014 Christos Choutouridis <houtouridis.ch@gmail.com>
 * All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Christos Choutouridis. The intellectual
 * and technical concepts contained herein are proprietary to
 * Christos Choutouridis and are protected by copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Christos Choutouridis.
 *
 * Author:     Christos Choutouridis <houtouridis.ch@gmail.com>
 * Date:       5/2014
 *
 */
#include <stm32f10x_sflash.h>

/*
 * ======= Private defines =============
 */
/* FLASH Keys */
#define RDP_Key                  ((uint16_t)0x00A5)
#define FLASH_KEY1               ((uint32_t)0x45670123)
#define FLASH_KEY2               ((uint32_t)0xCDEF89AB)

/* Flash Control Register bits */
#define CR_PG_Set                ((uint32_t)0x00000001)
#define CR_PG_Reset              ((uint32_t)0x00001FFE)
#define CR_PER_Set               ((uint32_t)0x00000002)
#define CR_PER_Reset             ((uint32_t)0x00001FFD)
#define CR_MER_Set               ((uint32_t)0x00000004)
#define CR_MER_Reset             ((uint32_t)0x00001FFB)
#define CR_OPTPG_Set             ((uint32_t)0x00000010)
#define CR_OPTPG_Reset           ((uint32_t)0x00001FEF)
#define CR_OPTER_Set             ((uint32_t)0x00000020)
#define CR_OPTER_Reset           ((uint32_t)0x00001FDF)
#define CR_STRT_Set              ((uint32_t)0x00000040)
#define CR_LOCK_Set              ((uint32_t)0x00000080)

#define CR_LOCK_Set              ((uint32_t)0x00000080)

/* Delay definition */
#define ERASE_TIMEOUT            ((uint32_t)0x0000FFFF)
#define PROGRAM_TIMEOUT          ((uint32_t)0x00000800)

/*
 * ========= Private function prototypes ============
 */
static           void _delay (void);
static FLASH_status_t _get_status (void);
static FLASH_status_t _wait (uint32_t timeout);
static           void _unlock (void);
static           void _lock (void);
static FLASH_status_t _erase_page (FLASH_add_t page);



/*=================    FLASH MODULE   ===================*/

/*!
 * \brief  Inserts a time delay.
 * \param  None
 * \return None
 */
static void _delay(void) {
   int16_t i = 0;
   for(i = 0xFF; i >= 0; --i);
}

/*!
 * \brief  Returns the FLASH Status.
 * \param  None
 * \return The status as enumerator of FLASH_status_t
 *    \arg FLASH_BUSY,
 *    \arg FLASH_ERROR_PG,
 *    \arg FLASH_ERROR_WRP or
 *    \arg FLASH_COMPLETE
 */
FLASH_status_t _get_status(void)
{
   if((FLASH->SR & FLASH_FLAG_BSY) == FLASH_FLAG_BSY) {
      return FLASH_BUSY;
   }
   else if((FLASH->SR & FLASH_FLAG_PGERR) != 0) {
      return FLASH_ERROR_PG;
   }
   else if((FLASH->SR & FLASH_FLAG_WRPRTERR) != 0 ) {
      return FLASH_ERROR_WRP;
   }
   else {
      return FLASH_COMPLETE;
   }
}

/*!
 * \brief
 *    Waits for a Flash operation to complete or a TIMEOUT to occur.
 *
 * \param  timeout   FLASH programming Timeout
 * \return           The status as enumerator of type FLASH_status_t
 *    \arg FLASH_ERROR_PG
 *    \arg FLASH_ERROR_WRP
 *    \arg FLASH_COMPLETE
 *    \arg FLASH_TIMEOUT
 */
FLASH_status_t _wait(uint32_t timeout)
{
   FLASH_status_t status = FLASH_COMPLETE;
   
   /* Wait for a Flash operation to complete or a TIMEOUT to occur */
   while ( ((status = _get_status()) != FLASH_COMPLETE) && timeout) {
      _delay();
      --timeout;
   }
   if (!timeout)
      return (status = FLASH_TIMEOUT);
   return status;

}

/*!
 * \brief  Unlock the FLASH to permit write operations
 * \param  none
 * \return none
 */
static __INLINE void _unlock(void) {
  /* Authorize the FPEC Access */
  FLASH->KEYR = FLASH_KEY1;
  FLASH->KEYR = FLASH_KEY2;

#ifdef STM32F10X_XL
  /* Authorize the FPEC of Bank2 Access */
  FLASH->KEYR2 = FLASH_KEY1;
  FLASH->KEYR2 = FLASH_KEY2;
#endif /* STM32F10X_XL */
}

/*!
 * \brief  Lock the FLASH to refuse write operations
 * \param  none
 * \return none
 */
static __INLINE void _lock(void) {
   /* Set the Lock Bit to lock the FPEC and the CR of  Bank1 */
   FLASH->CR |= CR_LOCK_Set;

 #ifdef STM32F10X_XL
   /* Set the Lock Bit to lock the FPEC and the CR of  Bank2 */
   FLASH->CR2 |= CR_LOCK_Set;
 #endif /* STM32F10X_XL */
}

/*!
 * \brief
 *    Erases a specified FLASH page.
 *
 * \param  page   The page address to be erased.
 * \return        The returned value can be:
 *    \arg FLASH_BUSY
 *    \arg FLASH_ERROR_PG
 *    \arg FLASH_ERROR_WRP
 *    \arg FLASH_COMPLETE
 *    \arg FLASH_TIMEOUT
 */
static FLASH_status_t _erase_page (FLASH_add_t page)
{
   FLASH_status_t status;

#ifdef STM32F10X_XL
   if(Page_Address < FLASH_BANK1_END_ADDRESS)
   {
      /* Wait for last operation to be completed */
      status = _wait (ERASE_TIMEOUT);
      if(status == FLASH_COMPLETE) {
         /* if the previous operation is completed, proceed to erase the page */
         FLASH->CR|= CR_PER_Set;
         FLASH->AR = page;
         FLASH->CR|= CR_STRT_Set;
    
         /* Wait for last operation to be completed */
         status = _wait (ERASE_TIMEOUT);

         /* Disable the PER Bit */
         FLASH->CR &= CR_PER_Reset;
      }
   }
   else {
      /* Wait for last operation to be completed */
      status = _wait (ERASE_TIMEOUT);
      if(status == FLASH_COMPLETE) {
         /* if the previous operation is completed, proceed to erase the page */
         FLASH->CR2|= CR_PER_Set;
         FLASH->AR2 = page;
         FLASH->CR2|= CR_STRT_Set;

         /* Wait for last operation to be completed */
         status = _wait (ERASE_TIMEOUT);

         /* Disable the PER Bit */
         FLASH->CR2 &= CR_PER_Reset;
      }
   }
#else
  /* Wait for last operation to be completed */
  status = _wait (ERASE_TIMEOUT);

  if(status == FLASH_COMPLETE) {
     /* if the previous operation is completed, proceed to erase the page */
     FLASH->CR|= CR_PER_Set;
     FLASH->AR = page;
     FLASH->CR|= CR_STRT_Set;

     /* Wait for last operation to be completed */
     status = _wait (ERASE_TIMEOUT);

     /* Disable the PER Bit */
     FLASH->CR &= CR_PER_Reset;
  }
#endif /* STM32F10X_XL */

   // Return the Erase Status
   return status;
}

/*
 * ========== Public API ================
 */



/*!
 * \brief
 *    Programs half word at a time to specified address the data pointed by pdata.
 *
 * \param  address   Specifies the address to be programmed.
 * \param  pdata     Specifies the data to be programmed.
 * \param  size      The size of data to write.
 * \return           The status of the operation
 *    \arg DRV_READY
 *    \arg DRV_BUSY
 *    \arg DRV_ERROR
 */
drv_status_en FLASH_write (FLASH_add_t address, void *pdata, int size)
{
   drv_status_en r;
   FLASH_status_t status;
   uint16_t written_bts = 0;

   _unlock ();
    // Check if the previous operation is completed
   status = _wait (PROGRAM_TIMEOUT);
    if (status == FLASH_COMPLETE)
       FLASH->CR |= CR_PG_Set;
    else {
       _lock ();
      return DRV_BUSY;
    }

   //proceed to program the new data
   while ( ((status = _wait (PROGRAM_TIMEOUT)) == FLASH_COMPLETE) && (written_bts < size)  ) {
      *(FLASH_data_t*)address = *(FLASH_data_t*)pdata;
      pdata += sizeof (FLASH_data_t);
      address += sizeof (FLASH_data_t);
      written_bts += sizeof (FLASH_data_t);
   }
   if(status != FLASH_TIMEOUT)
      // if the program operation is completed, disable the PG Bit
      r = DRV_READY;
   else
      r = DRV_ERROR;

   FLASH->CR &= CR_PG_Reset;
   _lock ();
   return r;
}

/*!
 * \brief
 *    Reads a half word at a time from a specified address to pdata
 *
 * \param  address   specifies the address to read from.
 * \param  pdata     specifies the data address to be written.
 * \param  size      The size of data to load
 * \return           The status of the operation
 *    \arg DRV_READY
 *    \arg DRV_BUSY
 *    \arg DRV_ERROR
 */
drv_status_en FLASH_read (FLASH_add_t address, void *pdata, int size)
{
   int bytes = 0;
   
   //proceed to read the data
   while ( bytes < size  ) {
      *(FLASH_data_t*)pdata = *(FLASH_data_t*)address;
      pdata += sizeof (FLASH_data_t);
      address += sizeof (FLASH_data_t);
      bytes += sizeof (FLASH_data_t);
   }
   return DRV_READY;
}

/*!
 * \brief
 *    FLASH ioctl function
 *
 * \param  cmd   specifies the command to FLASH
 *    \arg CTRL_GET_STATUS
 *    \arg CTRL_LOCK
 *    \arg CTRL_CMD_LOCK
 *    \arg CTRL_CMD_UNLOCK
 *    \arg CTRL_ERASE_PAGE
 * \param  buf   pointer to buffer for ioctl
 * \return           The status of the operation
 *    \arg DRV_READY
 *    \arg DRV_BUSY
 *    \arg DRV_ERROR
 */
drv_status_en FLASH_ioctl (ioctl_cmd_t cmd, ioctl_buf_t buf)
{
   FLASH_add_t add;
   FLASH_status_t st;

   /*
    * Command dispatcher
    */
   switch (cmd) {
      case CTRL_GET_STATUS:
         // Always return READY state
         return DRV_READY;
      case CTRL_LOCK:
         // Check buffer for actual command
         if (buf)    _lock();
         else        _unlock();
         return DRV_READY;
      case CTRL_CMD_LOCK:
         // Lock and return
         _lock();
         return DRV_READY;
      case CTRL_CMD_UNLOCK:
         // Unlock and return
         _unlock();
         return DRV_READY;
      case CTRL_ERASE_PAGE:
         _unlock();
         add = *(FLASH_add_t*)buf;
         st = _erase_page (add);
         _lock();
         switch (st) {
            case FLASH_COMPLETE: return DRV_READY;
            case FLASH_BUSY:     return DRV_BUSY;
            default:             return DRV_ERROR;
         }
         break;
      default:
         // Unknown command, error
         return DRV_ERROR;
   }
}

